使用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!