使用Angular和ngrx的声明性标题更新器
使用 Angular 的 Title 服务可以轻松更新 HTMLTitleElement。 SPA 中的每条路线都有不同的标题是很常见的。 这通常在路由组件的 ngOnInit 生命周期中手动完成。 但是,在这篇文章中,我们将以声明的方式使用 @ngrx/router-store 的强大功能以及自定义 RouterStateSerializer 和 @ngrx/effects[X174X ]。
概念如下:
- 在路由定义的 data 中有一个 title 属性。
- 使用 @ngrx/store 跟踪应用程序状态。
- 使用 @ngrx/router-store 和自定义 RouterStateSerializer 将所需的 title 添加到应用程序状态。
- 使用 @ngrx/effects 创建一个 updateTitle 效果,以在每次路线更改时更新 HTMLTitleElement。
项目设置
为了快速简单的设置,我们将使用 @angular/cli。
# Install @angular-cli if you don't already have it npm install @angular/cli -g # Create the example with routing ng new title-updater --routing
定义一些路由
创建几个组件:
ng generate component gators ng generate component crocs
并定义他们的路线:
标题更新/src/app/app-routing.module.ts
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { GatorsComponent } from './gators/gators.component'; import { CrocsComponent } from './crocs/crocs.component'; const routes: Routes = [ { path: 'gators', component: GatorsComponent, data: { title: 'Alligators'} }, { path: 'crocs', component: CrocsComponent, data: { title: 'Crocodiles'} } ];
注意每个路由定义中的 title 属性,它将用于更新 HTMLTitleElement。
添加状态管理
@ngrx 是一个很好的管理应用程序状态的库。 对于这个示例应用程序,我们将使用 @ngrx/router-store 将路由器序列化为 @ngrx/store,以便我们可以监听路由更改并相应地更新标题。
我们将使用 @ngrx > 4.0 来利用新的 RouterStateSerializer
安装:
npm install @ngrx/store @ngrx/router-store --save
创建一个自定义的 RouterStateSerializer 以将所需的标题添加到状态:
标题更新程序/src/app/shared/utils.ts
import { RouterStateSerializer } from '@ngrx/router-store'; import { RouterStateSnapshot } from '@angular/router'; export interface RouterStateTitle { title: string; } export class CustomRouterStateSerializer implements RouterStateSerializer<RouterStateTitle> { serialize(routerState: RouterStateSnapshot): RouterStateTitle { let childRoute = routerState.root; while (childRoute.firstChild) { childRoute = childRoute.firstChild; } // Use the most specific title const title = childRoute.data['title']; return { title };
定义路由器减速器:
标题更新器/src/app/reducers/index.ts
import * as fromRouter from '@ngrx/router-store'; import { RouterStateTitle } from '../shared/utils'; import { createFeatureSelector } from '@ngrx/store'; export interface State { router: fromRouter.RouterReducerState<RouterStateTitle>; } export const reducers = { router: fromRouter.routerReducer };
每次 @ngrx/store 调度一个动作(路由器导航动作由 StoreRouterConnectingModule 发送),reducer 需要处理该动作并相应地更新状态。 上面我们将应用程序状态定义为具有路由器属性并使用 CustomRouterStateSerializer 将序列化的路由器状态保持在那里。
需要最后一步才能将其全部连接起来:
标题更新程序/src/app/app.module.ts
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store'; import { StoreModule } from '@ngrx/store'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { CrocsComponent } from './crocs/crocs.component'; import { GatorsComponent } from './gators/gators.component'; import { reducers } from './reducers/index'; import { CustomRouterStateSerializer } from './shared/utils'; @NgModule({ declarations: [ AppComponent, CrocsComponent, GatorsComponent ], imports: [ BrowserModule, AppRoutingModule, StoreModule.forRoot(reducers), StoreRouterConnectingModule ], providers: [ /**
洒在魔法@ngrx/效果
现在当我们切换路由时,我们的 @ngrx/store 将拥有我们想要的标题。 要更新标题,我们现在要做的就是监听 ROUTER_NAVIGATION 动作并在状态上使用标题。 我们可以使用 @ngrx/effects 来做到这一点。
安装:
npm install @ngrx/effects --save
创建效果:
标题更新器/src/app/效果/标题更新器.ts
import { Title } from '@angular/platform-browser'; import { Actions, Effect } from '@ngrx/effects'; import { ROUTER_NAVIGATION, RouterNavigationAction } from '@ngrx/router-store'; import 'rxjs/add/operator/do'; import { RouterStateTitle } from '../shared/utils'; @Injectable() export class TitleUpdaterEffects { @Effect({ dispatch: false }) updateTitle$ = this.actions .ofType(ROUTER_NAVIGATION) .do((action: RouterNavigationAction<RouterStateTitle>) => { this.titleService.setTitle(action.payload.routerState.title); });
最后,通过使用 EffectsModule.forRoot 导入来连接 updateTitle 效果,这将在通过订阅所有 @Effect()s 创建模块时开始监听效果:
标题更新程序/src/app/app.module.ts
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store'; import { StoreModule } from '@ngrx/store'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { CrocsComponent } from './crocs/crocs.component'; import { GatorsComponent } from './gators/gators.component'; import { reducers } from './reducers/index'; import { CustomRouterStateSerializer } from './shared/utils'; import { EffectsModule } from '@ngrx/effects'; import { TitleUpdaterEffects } from './effects/title-updater';
就是这样! 您现在可以在路线定义中定义 titles 并且它们会在路线更改时自动更新!
更进一步,从静态到动态⚡️
静态标题非常适合大多数用例,但如果您想按名称欢迎用户或同时显示通知计数怎么办? 我们可以将路由数据中的 title 属性修改为接受上下文的函数。
如果 notificationCount 在商店中,这是一个潜在的示例:
标题更新/src/app/app-routing.module.ts
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { GatorsComponent } from './gators/gators.component'; import { CrocsComponent } from './crocs/crocs.component'; import { InboxComponent } from './inbox/inbox.component'; const routes: Routes = [ { path: 'gators', component: GatorsComponent, data: { title: () => 'Alligators' } }, { path: 'crocs', component: CrocsComponent, data: { title: () => 'Crocodiles' } }, { path: 'inbox', component: InboxComponent, data: { // A dynamic title that shows the current notification count! title: (ctx) => { let t = 'Inbox'; if(ctx.notificationCount > 0) { t += (${ctx.notificationCount}); } return t; } } } ];
标题更新器/src/app/效果/标题更新器.ts
import { Title } from '@angular/platform-browser'; import { Actions, Effect } from '@ngrx/effects'; import { ROUTER_NAVIGATION, RouterNavigationAction } from '@ngrx/router-store'; import { Store } from '@ngrx/store'; import 'rxjs/add/operator/combineLatest'; import { getNotificationCount } from '../selectors.ts'; import { RouterStateTitle } from '../shared/utils'; @Injectable() export class TitleUpdaterEffects { // Update title every time route or context changes, pulling the notificationCount from the store. @Effect({ dispatch: false }) updateTitle$ = this.actions .ofType(ROUTER_NAVIGATION) .combineLatest(this.store.select(getNotificationCount), (action: RouterNavigationAction<RouterStateTitle>, notificationCount: number) => { // The context we will make available for the title functions to use as they please. const ctx = { notificationCount }; this.titleService.setTitle(action.payload.routerState.title(ctx)); });
现在,当 Inbox 路由加载时,用户可以看到他们的通知计数也实时更新! 💌
🚀 继续试验和探索自定义 RouterStateSerializers 和 @ngrx!