Веб

Проверка user-agent в Angular 21 SSR: canMatch guard

Пошаговое руководство по созданию сервиса проверки user-agent в Angular 21 SSR. Настройка guard canMatch для маршрутизации на основе типа устройства.

3 ответа 1 просмотр

Как реализовать сервис для проверки user-agent в Angular v21 SSR, чтобы guard canMatch мог выбирать между мобильными и десктопными маршрутами исключительно на сервере?

В Angular v21 SSR для реализации сервиса проверки user-agent, который будет работать исключительно на сервере и использоваться в guard canMatch, вам необходимо создать сервис, который инжектирует токен REQUEST из @angular/platform-server для доступа к HTTP-запросу и его заголовкам. Сервис будет анализировать user-agent и определять тип устройства, а guard canMatch будет использовать этот сервис для выбора соответствующего маршрута.


Содержание


Введение в Angular v21 SSR и проверку user-agent

Angular v21 продолжает развивать возможности серверного рендеринга (SSR), предоставляя разработчикам мощные инструменты для создания веб-приложений с улучшенной производительностью и SEO. Когда речь заходит о маршрутизации на основе типа устройства (мобильные vs десктопные), важно выполнять эту логику исключительно на сервере, чтобы избежать флеша контента и обеспечить мгновенную загрузку правильной версии интерфейса.

Компоненты angular в SSR контексте имеют доступ к серверным сервисам, что позволяет им получать информацию о клиенте до рендеринга. Angular 21 улучшила интеграцию с SSR, сделав процесс работы с HTTP-запросами более интуитивным и безопасным.

Важным аспектом является проверка user-agent, которая определяет, с какого устройства зашел пользователь. Эта информация критична для выбора правильного маршрута, так как мобильные и десктопные версии приложения могут иметь разную структуру и функциональность.


Создание сервиса для определения устройства в Angular SSR

Для начала создадим сервис, который будет анализировать user-agent и определять тип устройства. Этот сервис будет работать исключительно на сервере, используя токен REQUEST.

typescript
// src/app/core/services/device-detector.service.ts
import { Injectable, Inject } from '@angular/core';
import { REQUEST } from '@angular/platform-server';

@Injectable({
 providedIn: 'root'
})
export class DeviceDetectorService {
 constructor(@Inject(REQUEST) private request: any) {}

 isMobile(): boolean {
 if (!this.request || !this.request.headers) {
 return false;
 }

 const userAgent = this.request.headers['user-agent'] || '';
 
 // Мобильные user-agent паттерны
 const mobileRegex = /Mobile|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
 return mobileRegex.test(userAgent);
 }

 isDesktop(): boolean {
 return !this.isMobile();
 }

 getDeviceType(): 'mobile' | 'desktop' {
 return this.isMobile() ? 'mobile' : 'desktop';
 }
}

Этот сервис использует инжекцию токена REQUEST для доступа к HTTP-запросу на сервере. Важно отметить, что в клиентской части этот сервис будет работать, но всегда будет возвращать false для isMobile(), так как токен REQUEST недоступен на клиенте.

При работе с angular component, который требует информации о типе устройства, вы можете использовать этот сервис в ngOnInit или в шаблоне, но для маршрутизации лучше использовать guards, которые будут работать на сервере.


Настройка guard canMatch для маршрутизации на основе устройства

Guard canMatch в Angular позволяет решить, может ли маршрут быть активирован, до загрузки его компонентов. Это идеально подходит для нашей задачи, так как guard будет работать на сервере во время SSR.

typescript
// src/app/guards/device.guard.ts
import { Injectable } from '@angular/core';
import { CanMatch, Router, Route } from '@angular/router';
import { DeviceDetectorService } from '../core/services/device-detector.service';

@Injectable({
 providedIn: 'root'
})
export class DeviceGuard implements CanMatch {
 constructor(
 private deviceDetector: DeviceDetectorService,
 private router: Router
 ) {}

 canMatch(route: Route): boolean {
 const isMobile = this.deviceDetector.isMobile();
 const expectedDeviceType = route.data?.['deviceType'];

 if (expectedDeviceType && isMobile && expectedDeviceType === 'desktop') {
 // Перенаправляем на мобильную версию
 this.router.navigate(['/mobile']);
 return false;
 }

 if (expectedDeviceType && !isMobile && expectedDeviceType === 'mobile') {
 // Перенаправляем на десктопную версию
 this.router.navigate(['/desktop']);
 return false;
 }

 return true;
 }
}

Теперь настроим маршруты, используя этот guard:

typescript
// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DeviceGuard } from './guards/device.guard';

const routes: Routes = [
 {
 path: 'mobile',
 component: MobileLayoutComponent,
 data: { deviceType: 'mobile' },
 canMatch: [DeviceGuard]
 },
 {
 path: 'desktop',
 component: DesktopLayoutComponent,
 data: { deviceType: 'desktop' },
 canMatch: [DeviceGuard]
 },
 {
 path: '',
 redirectTo: '/mobile',
 pathMatch: 'full'
 }
];

@NgModule({
 imports: [RouterModule.forRoot(routes)],
 exports: [RouterModule]
})
export class AppRoutingModule { }

Такая конфигурация гарантирует, что маршруты будут выбираться исключительно на сервере на основе user-agent, обеспечивая мгновенную загрузку правильной версии интерфейса.


Интеграция с токеном REQUEST для доступа к user-agent на сервере

Ключевым элементом нашей реализации является токен REQUEST из пакета @angular/platform-server. Этот токен предоставляет доступ к объекту HTTP-запроса на сервере, что позволяет нам извлечь user-agent из заголовков.

Для корректной работы с токеном REQUEST убедитесь, что вы правильно настроили серверную часть вашего angular приложения. В main.server.ts нужно убедиться, что токен доступен для инжекции:

typescript
// src/main.server.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { provideServerRendering } from '@angular/platform-server';
import { REQUEST } from '@angular/platform-server';

bootstrapApplication(AppComponent, {
 providers: [
 provideServerRendering(),
 { provide: REQUEST, useValue: globalThis['__request'] }
 ]
}).catch(err => console.error(err));

На серверной стороне (например, в server.ts Express.js) нужно передавать объект запроса в глобальную переменную:

typescript
// server.ts
import { ngExpressEngine } from '@nguniversal/express-engine';
import { REQUEST } from '@angular/platform-server';

// ...

app.engine('html', ngExpressEngine({
 bootstrap: AppServerModule,
 providers: [
 { provide: REQUEST, useValue: req }
 ]
}));

app.get('*', (req, res) => {
 globalThis['__request'] = req;
 res.render('index.html', { req });
});

Такая интеграция гарантирует, что ваш сервис DeviceDetectorService будет иметь доступ к реальному user-agent на сервере во время SSR.


Оптимизация производительности и обработка edge cases

При работе с user-agent в angular SSR важно учитывать несколько моментов для оптимизации и обработки edge cases:

  1. Кэширование результата: Поскольку user-агент не меняется в течение сессии, можно кэшировать результат определения типа устройства:
typescript
@Injectable({
 providedIn: 'root'
})
export class DeviceDetectorService {
 private deviceTypeCache: 'mobile' | 'desktop' | null = null;

 constructor(@Inject(REQUEST) private request: any) {}

 getDeviceType(): 'mobile' | 'desktop' {
 if (this.deviceTypeCache) {
 return this.deviceTypeCache;
 }

 const isMobile = this.isMobile();
 this.deviceTypeCache = isMobile ? 'mobile' : 'desktop';
 return this.deviceTypeCache;
 }
}
  1. Обработка планшетов: Некоторые планшеты могут иметь user-agent, похожий на десктоп, но сенсорный экран:
typescript
isMobile(): boolean {
 if (!this.request || !this.request.headers) {
 return false;
 }

 const userAgent = this.request.headers['user-agent'] || '';
 
 // Паттерны для мобильных устройств и планшетов
 const mobileRegex = /Mobile|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
 const tabletRegex = /iPad|Android(?!.*Mobile)/i;
 
 return mobileRegex.test(userAgent) || tabletRegex.test(userAgent);
}
  1. Проверка наличия токена REQUEST: Для предотвращения ошибок на клиенте:
typescript
isMobile(): boolean {
 // На клиенте всегда возвращаем false
 if (typeof window !== 'undefined') {
 return false;
 }
 
 if (!this.request || !this.request.headers) {
 return false;
 }

 // ... остальная логика
}
  1. Предоставление альтернативного маршрута: Для случаев, когда user-agent не определился:
typescript
canMatch(route: Route): boolean {
 const isMobile = this.deviceDetector.isMobile();
 const expectedDeviceType = route.data?.['deviceType'];

 if (expectedDeviceType) {
 if (isMobile && expectedDeviceType === 'desktop') {
 this.router.navigate(['/mobile']);
 return false;
 }

 if (!isMobile && expectedDeviceType === 'mobile') {
 this.router.navigate(['/desktop']);
 return false;
 }
 }

 // Если user-agent не определился, используем стандартный маршрут
 if (!isMobile && !this.deviceDetector.isDesktop()) {
 this.router.navigate(['/desktop']);
 return false;
 }

 return true;
}

Эти оптимизации помогут сделать ваше angular приложение более надежным и производительным при работе с SSR.


Полный пример реализации с кодом

Давайте соберем все вместе для полного примера реализации сервиса проверки user-agent в Angular v21 SSR:

  1. Создание сервиса DeviceDetectorService:
typescript
// src/app/core/services/device-detector.service.ts
import { Injectable, Inject } from '@angular/core';
import { REQUEST } from '@angular/platform-server';

@Injectable({
 providedIn: 'root'
})
export class DeviceDetectorService {
 private deviceTypeCache: 'mobile' | 'desktop' | null = null;

 constructor(@Inject(REQUEST) private request: any) {}

 isMobile(): boolean {
 // На клиенте всегда возвращаем false
 if (typeof window !== 'undefined') {
 return false;
 }
 
 if (!this.request || !this.request.headers) {
 return false;
 }

 const userAgent = this.request.headers['user-agent'] || '';
 const mobileRegex = /Mobile|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
 const tabletRegex = /iPad|Android(?!.*Mobile)/i;
 
 return mobileRegex.test(userAgent) || tabletRegex.test(userAgent);
 }

 isDesktop(): boolean {
 if (typeof window !== 'undefined') {
 return true; // На клиенте считаем десктопом по умолчанию
 }
 
 if (!this.request || !this.request.headers) {
 return true;
 }
 
 return !this.isMobile();
 }

 getDeviceType(): 'mobile' | 'desktop' {
 if (this.deviceTypeCache) {
 return this.deviceTypeCache;
 }

 const isMobile = this.isMobile();
 this.deviceTypeCache = isMobile ? 'mobile' : 'desktop';
 return this.deviceTypeCache;
 }
}
  1. Создание guard DeviceGuard:
typescript
// src/app/guards/device.guard.ts
import { Injectable } from '@angular/core';
import { CanMatch, Router, Route } from '@angular/router';
import { DeviceDetectorService } from '../core/services/device-detector.service';

@Injectable({
 providedIn: 'root'
})
export class DeviceGuard implements CanMatch {
 constructor(
 private deviceDetector: DeviceDetectorService,
 private router: Router
 ) {}

 canMatch(route: Route): boolean {
 const isMobile = this.deviceDetector.isMobile();
 const expectedDeviceType = route.data?.['deviceType'];

 if (expectedDeviceType) {
 if (isMobile && expectedDeviceType === 'desktop') {
 this.router.navigate(['/mobile']);
 return false;
 }

 if (!isMobile && expectedDeviceType === 'mobile') {
 this.router.navigate(['/desktop']);
 return false;
 }
 }

 // Если user-agent не определился, используем десктопную версию
 if (!isMobile && !this.deviceDetector.isDesktop()) {
 this.router.navigate(['/desktop']);
 return false;
 }

 return true;
 }
}
  1. Конфигурация маршрутов:
typescript
// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DeviceGuard } from './guards/device.guard';

const routes: Routes = [
 {
 path: 'mobile',
 loadChildren: () => import('./mobile/mobile.module').then(m => m.MobileModule),
 data: { deviceType: 'mobile' },
 canMatch: [DeviceGuard]
 },
 {
 path: 'desktop',
 loadChildren: () => import('./desktop/desktop.module').then(m => m.DesktopModule),
 data: { deviceType: 'desktop' },
 canMatch: [DeviceGuard]
 },
 {
 path: '',
 redirectTo: '/mobile',
 pathMatch: 'full'
 },
 {
 path: '**',
 redirectTo: '/mobile'
 }
];

@NgModule({
 imports: [RouterModule.forRoot(routes)],
 exports: [RouterModule]
})
export class AppRoutingModule { }
  1. Компоненты для мобильной и десктопной версий:
typescript
// src/app/mobile/mobile.component.ts
import { Component } from '@angular/core';
import DeviceDetectorService from '../../core/services/device-detector.service';

@Component({
 selector: 'app-mobile',
 template: `
 <h1>Мобильная версия</h1>
 <p>Вы используете мобильное устройство.</p>
 `
})
export class MobileComponent {
 constructor(private deviceDetector: DeviceDetectorService) {
 console.log('Тип устройства:', this.deviceDetector.getDeviceType());
 }
}
typescript
// src/app/desktop/desktop.component.ts
import { Component } from '@angular/core';
import DeviceDetectorService from '../../core/services/device-detector.service';

@Component({
 selector: 'app-desktop',
 template: `
 <h1>Десктопная версия</h1>
 <p>Вы используете десктопное устройство.</p>
 `
})
export class DesktopComponent {
 constructor(private deviceDetector: DeviceDetectorService) {
 console.log('Тип устройства:', this.deviceDetector.getDeviceType());
 }
}
  1. Настройка серверной части:
typescript
// src/main.server.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { provideServerRendering } from '@angular/platform-server';
import { REQUEST } from '@angular/platform-server';

bootstrapApplication(AppComponent, {
 providers: [
 provideServerRendering(),
 { provide: REQUEST, useValue: globalThis['__request'] }
 ]
}).catch(err => console.error(err));
typescript
// server.ts
import { ngExpressEngine } from '@nguniversal/express-engine';
import { REQUEST } from '@angular/platform-server';

// ...

app.engine('html', ngExpressEngine({
 bootstrap: AppServerModule,
 providers: [
 { provide: REQUEST, useValue: req }
 ]
}));

app.get('*', (req, res) => {
 globalThis['__request'] = req;
 res.render('index.html', { req });
});

Этот полный пример показывает, как реализовать проверку user-agent в Angular v21 SSR с использованием сервиса и guard для выбора правильных маршрутов исключительно на сервере. Такая реализация обеспечивает мгновенную загрузку правильной версии интерфейса без необходимости выполнения этой логики на клиенте.


Источники

  1. Angular Universal Guide - Официальное руководство по SSR в Angular: https://angular.io/guide/universal
  2. Angular REQUEST Token API - Документация по токену REQUEST для доступа к HTTP-запросу на сервере: https://angular.io/api/platform-server/REQUEST
  3. Angular Guards Documentation - Официальная документация по guards и canMatch в Angular: https://angular.io/guide/router-tutorial-toh#canmatch-guard
  4. Angular NgModule API - Документация по NgModule для настройки маршрутизации: https://angular.io/api/core/NgModule
  5. Angular Dependency Injection - Официальная документация по системе инжекции зависимостей: https://angular.io/guide/dependency-injection

Заключение

Реализация сервиса для проверки user-agent в Angular v21 SSR с использованием guard canMatch позволяет эффективно выбирать между мобильными и десктопными маршрутами исключительно на сервере. Ключевые элементы этой реализации включают:

  1. Создание сервиса DeviceDetectorService, который инжектирует токен REQUEST для доступа к HTTP-запросу
  2. Настройка guard canMatch для перенаправления на правильные маршруты на основе user-agent
  3. Корректная конфигурация серверной части для передачи объекта запроса в приложение
  4. Оптимизация производительности через кэширование и обработку edge cases

Такой подход обеспечивает мгновенную загрузку правильной версии интерфейса без необходимости выполнения проверки user-agent на клиенте, что улучшает пользовательский опыт и производительность вашего angular приложения. Интеграция с SSR в angular 21 делает этот процесс еще более надежным и простым в реализации.

A

Angular Universal позволяет запускать приложение на сервере (SSR), что критично для обработки user-agent до рендеринга. Для доступа к HTTP-запросу на сервере используется токен REQUEST из пакета @angular/platform-server. Это позволяет анализировать заголовки запроса (включая user-agent) в сервисах и guards. Серверный контекст Angular Universal предоставляет доступ к исходному HTTP-запросу через инъекцию токена REQUEST. Это позволяет обрабатывать заголовки запроса, такие как user-agent, на стороне сервера до активации маршрутов.

A

Токен REQUEST предоставляет доступ к объекту HttpRequest из Express.js на сервере. Это позволяет извлекать заголовок user-agent и передавать его в сервис для анализа. Токен REQUEST инъецируется в сервисы и guards для доступа к исходному HTTP-запроса. Пример использования: constructor(@Inject(REQUEST) private request: any) {}. В объекте request.headers['user-agent'] содержится строка с информацией о клиенте.

Авторы
A
Команда документации
Проверено модерацией
НейроОтветы
Модерация