Angular

Angular — это MVC-фреймворк для создания одностраничных приложений в Javascript. 

В этой статье мы узнаем, как установить заголовок браузера (страницы/документа) для всего приложения и как изменить заголовок браузера при перемещении по приложению.

Пример проекта

Пример проекта для демонстрации этой функции. Вы можете скопировать этот проект и запустить его на своем компьютере.

git clone https://github.com/bbachi/angular_title_example.git
// install dependencies and run the project
npm install
npm start

Как установить заголовок браузера для приложения

В каждом одностраничном приложении есть только один файл index.html, который загружается в браузер до появления в нем элементов фреймворка. В Angular нужно загрузить файл index.html, который содержит компонент root, минимизированные js-файлы и файлы CSS.

Это первый файл, который загружается в браузер для приложения Angular. Устанавливаем заголовок для всего приложения в теге title.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Title for the whole app</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
</body>
</html>

Это снимок браузера. Заголовок не меняется при изменении страницы или перемещении по приложению.

Заголовок браузера

Что такое TitleService

Привязка данных или интерполяция в Angular не работает, поскольку тег title находится вне тела, а Angular не имеет к нему доступа. Доступ к объекту Document можно получить напрямую и установить title вручную. Однако Angular не рекомендует использовать этот способ.

Angular предоставляет TitleService для управления заголовком документа. Это простой API для получения и установки заголовка. Сервис предоставляет два метода.

class Title {
  
  // получить заголовок
  getTitle(): string
  
  // установить заголовок
  setTitle(newTitle: string)

}

TitleService в действии

Рассмотрим использование TitleService для чтения и установки заголовка текущего документа.

Сервис Title нужно импортировать из angular/platform-browser и добавить в массив Providers. После добавления сервис доступен для использования в любом из компонентов.

import { BrowserModule, Title } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [
    Title
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Теперь импортируем TitleService в компонент app и добавляем сервис в конструктор, чтобы он был доступен для использования в компоненте.

import { Component } from '@angular/core';
import { Title } from '@angular/platform-browser';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'angulartitle';

  constructor(private titleService: Title) {}

  setDocTitle(title: string) {
     console.log('current title:::::' + this.titleService.getTitle());
     this.titleService.setTitle(title);
  }
}

Теперь помещаем несколько ссылок в файл шаблона компонента приложения. Метод setDocTitle используется для чтения и установки заголовка документа.

<h2>Quick Links: </h2>
<ul>
  <li><a (click)="setDocTitle( 'About' )">About</a>.</li>
  <li><a (click)="setDocTitle( 'Dashboard' )">Dashboard</a>.</li>
  <li><a (click)="setDocTitle( 'Profile' )">Profile</a>.</li>
  <li><a (click)="setDocTitle( 'My Account' )">My Account</a>.</li>
</ul>

Теперь заголовок изменяется, а в консоли появляются выходные данные при нажатии на ссылки в разделе Quick Links. Вы можете проверить этот результат, используя копию указанного выше проекта.

Заголовок браузера и вывод в консоли

Как установить заголовок браузера для каждой страницы

Проблема с вышеупомянутым подходом заключается в том, что эти методы нужно поместить в компонент и вызвать для каждой ссылки, находящейся в шаблонах компонентов.

Однако есть более удобный способ сделать это с помощью модуля Angular Routing и Router API. В примере ниже название браузера изменяется по мере загрузки определенных страниц при нажатии на ссылки в заголовке.

Демонстрация изменения заголовка в браузере

Посмотрим, как можно это реализовать. Во-первых, нужно определить модуль маршрутизации для приложения с объектом data. В этом месте нужно разместить заголовок.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AboutComponent } from './about/about.component';
import { MyprofileComponent } from './myprofile/myprofile.component';
import { MyaccountComponent } from './myaccount/myaccount.component';
import { DashboardComponent } from './dashboard/dashboard.component';

const routes: Routes = [
  { path: 'about', component: AboutComponent, data: {title: 'About'}},
  { path: 'profile', component: MyprofileComponent, data: {title: 'Profile'} },
  { path: 'myaccount', component: MyaccountComponent, data: {title: 'My Account'} },
  { path: 'dashboard', component: DashboardComponent, data: {title: 'Dashboard'} }
];

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

Теперь в компоненте App нужно импортировать Router и ActivatedRoute, чтобы подписаться на события маршрутизатора и изменить заголовок с помощью Title service.

Подписываемся на события маршрутизатора, получаем доступ к данным с помощью ActivatedRoute и устанавливаем заголовок с помощью TitleService.

import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { Router, NavigationEnd, ActivatedRoute } from '@angular/router';
import { filter, map } from 'rxjs/operators';

// Опущено для краткости
export class AppComponent implements OnInit {
  title = 'angulartitle';

  // Опущено для краткости

  ngOnInit() {
    const appTitle = this.titleService.getTitle();
    this.router
      .events.pipe(
        filter(event => event instanceof NavigationEnd),
        map(() => {
          const child = this.activatedRoute.firstChild;
          if (child.snapshot.data['title']) {
            return child.snapshot.data['title'];
          }
          return appTitle;
        })
      ).subscribe((ttl: string) => {
        this.titleService.setTitle(ttl);
      });
  }
}

В файле выше мы также читаем текущий заголовок, если заголовок не определен в маршрутах. Это можно проверить, удалив объект data в одном из маршрутов в примере проекта.

С модулями

Большую часть времени выполняется отложенная загрузка модулей в целях повышения производительности. Нужно внести небольшие изменения из-за наличия вложенных дочерних элементов.

Я создал модуль Users для отложенной загрузки. Загружаем этот модуль для пути users и ProfileComponent для пути profile. Получаем следующий маршрут: /users/profile.

const routes: Routes = [
  { path: 'about', component: AboutComponent },
  { path: 'profile', component: MyprofileComponent, data: {title: 'Profile'} },
  { path: 'myaccount', component: MyaccountComponent, data: {title: 'My Account'} },
  { path: 'dashboard', component: DashboardComponent, data: {title: 'Dashboard'} },
  { path: 'users',  loadChildren: './users/users.module#UsersModule' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ProfileComponent } from '../profile/profile.component';
import { ListusersComponent } from '../listusers/listusers.component';

const routes: Routes = [
  { path: 'profile', component: ProfileComponent, data: {title: 'User Profile'} },
  { path: 'myaccount', component: ListusersComponent, data: {title: 'List Users'} }
];

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

Цикл while должен выполняться до тех пор, пока не будет найден последний дочерний элемент.

ngOnInit() {
    const appTitle = this.titleService.getTitle();
    this.router
      .events.pipe(
        filter(event => event instanceof NavigationEnd),
        map(() => {
          let child = this.activatedRoute.firstChild;
          while (child.firstChild) {
            child = child.firstChild;
          }
          if (child.snapshot.data['title']) {
            return child.snapshot.data['title'];
          }
          return appTitle;
        })
      ).subscribe((ttl: string) => {
        this.titleService.setTitle(ttl);
      });
  }

Вывод в браузере:

Компонент User Profile

Выводы:

  • В каждом одностраничном приложении есть index.html.
  • Заголовок для всего приложения можно установить с тегом title в разделе head файла index.html.
  • Тег title не доступен через привязку данных Angular.
  • Angular предоставляет TitleService для получения и установки текущего заголовка документа.
  • Получить и установить заголовок для каждой страницы можно в одном месте, подписавшись на маршруты Angular.
  • Для вложенных модулей необходимо выполнять цикл до нахождения последнего дочернего элемента.

Заключение

TitleService — это удобный метод для чтения и установки заголовка документа. Angular не рекомендует обращаться к объекту Document вручную для получения и установки заголовка.

Читайте также:


Перевод статьи Bhargav Bachina: Dynamic Page Titles in Angular