使用 Angular 和 ngrx 的声明式标题更新器

使用ANGLE的Title服务,更新HTMLTitleElement很容易。SPA中的每条路由都有不同的标题,这是很常见的。这通常是在路径组件的ngOnInit生命周期中手动完成的。然而,在这篇文章中,我们将以声明的方式使用@ngrx/路由器存储的功能和自定义的RouterStateSerializer和@ngrx/Effects来完成这项工作。

其概念如下:

  • 在路线定义的数据中有一个标题属性。
  • 使用@ngrx/store跟踪应用程序状态。
  • 使用带有自定义RouterStateSerializer的@ngrx/router-store将所需标题添加到应用程序状态。
  • 每次路径更改时,使用@ngrx/Effects来更新HTMLTitleElement,从而创建一个updateTitle效果。

项目设置

为了快速简单的设置,我们将使用@angular/angular。

1# Install @angular-cli if you don't already have it
2npm install @angular/cli -g
3
4# Create the example with routing
5ng new title-updater --routing

定义一些路径

创建两个组件:

1ng generate component gators
2ng generate component crocs

并定义他们的路线:

 1[label title-updater/src/app/app-routing.module.ts]
 2import { NgModule } from '@angular/core';
 3import { Routes, RouterModule } from '@angular/router';
 4import { GatorsComponent } from './gators/gators.component';
 5import { CrocsComponent } from './crocs/crocs.component';
 6
 7const routes: Routes = [
 8  {
 9    path: 'gators',
10    component: GatorsComponent,
11    data: { title: 'Alligators'}
12  },
13  {
14    path: 'crocs',
15    component: CrocsComponent,
16    data: { title: 'Crocodiles'}
17  }
18];

请注意每个路由定义中的标题属性,它将用于更新HTMLTitleElement。

添加状态管理

@ngrx是管理应用程序状态的一个很棒的库。对于这个示例应用程序,我们将使用@ngrx/router-store将路由器序列化到@ngrx/store中,这样我们就可以监听路由更改并相应地更新标题。

<$>[注意]我们将使用@ngrx>4.0来利用新的RouterStateSerializer<$>

-对不起,先生.

1npm install @ngrx/store @ngrx/router-store --save

创建自定义RouterStateSerializer以将所需标题添加到州:

 1[label title-updater/src/app/shared/utils.ts]
 2import { RouterStateSerializer } from '@ngrx/router-store';
 3import { RouterStateSnapshot } from '@angular/router';
 4
 5export interface RouterStateTitle {
 6  title: string;
 7}
 8export class CustomRouterStateSerializer
 9 implements RouterStateSerializer<RouterStateTitle> {
10  serialize(routerState: RouterStateSnapshot): RouterStateTitle {
11    let childRoute = routerState.root;
12    while (childRoute.firstChild) {
13      childRoute = childRoute.firstChild;
14    }
15// Use the most specific title
16const title = childRoute.data['title'];
17return { title };

定义路由器减少器:

 1[label title-updater/src/app/reducers/index.ts]
 2import * as fromRouter from '@ngrx/router-store';
 3import { RouterStateTitle } from '../shared/utils';
 4import { createFeatureSelector } from '@ngrx/store';
 5
 6export interface State {
 7  router: fromRouter.RouterReducerState<RouterStateTitle>;
 8}
 9export const reducers = {
10  router: fromRouter.routerReducer
11};

每次@ngrx/store调度操作(路由器导航操作由StoreRouterConnectingModule发送)时,Reducer需要处理该操作并相应地更新状态。上面,我们将应用程序状态定义为具有路由器属性,并使用CustomRouterStateSerializer保持序列化的路由器状态。

还需要最后一步才能把这一切联系起来:

 1[label title-updater/src/app/app.module.ts]
 2import { NgModule } from '@angular/core';
 3import { BrowserModule } from '@angular/platform-browser';
 4import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store';
 5import { StoreModule } from '@ngrx/store';
 6
 7import { AppRoutingModule } from './app-routing.module';
 8import { AppComponent } from './app.component';
 9import { CrocsComponent } from './crocs/crocs.component';
10import { GatorsComponent } from './gators/gators.component';
11import { reducers } from './reducers/index';
12import { CustomRouterStateSerializer } from './shared/utils';
13@NgModule({
14  declarations: [
15    AppComponent,
16    CrocsComponent,
17    GatorsComponent
18  ],
19  imports: [
20    BrowserModule,
21    AppRoutingModule,
22    StoreModule.forRoot(reducers),
23StoreRouterConnectingModule
24  ],
25  providers: [
26    /**

洒入魔法@ngrx/effect

现在,当我们切换路线时,我们的@ngrx/store将拥有我们想要的标题。要更新标题,我们现在所要做的就是监听ROUTER_NAVICATION操作并在状态上使用标题。我们可以使用@ngrx/Effects来实现这一点。

-对不起,先生.

1npm install @ngrx/effects --save

创建效果:

 1[label title-updater/src/app/effects/title-updater.ts]
 2import { Title } from '@angular/platform-browser';
 3import { Actions, Effect } from '@ngrx/effects';
 4import { ROUTER_NAVIGATION, RouterNavigationAction } from '@ngrx/router-store';
 5import 'rxjs/add/operator/do';
 6import { RouterStateTitle } from '../shared/utils';
 7
 8@Injectable()
 9export class TitleUpdaterEffects {
10  @Effect({ dispatch: false })
11  updateTitle$ = this.actions
12    .ofType(ROUTER_NAVIGATION)
13    .do((action: RouterNavigationAction<RouterStateTitle>) => {
14      this.titleService.setTitle(action.payload.routerState.title);
15    });

最后,通过导入updateTitle效果来连接updateTitle效果,这将在通过订阅所有@Effect()来创建模块时开始监听效果:

 1[label title-updater/src/app/app.module.ts]
 2import { NgModule } from '@angular/core';
 3import { BrowserModule } from '@angular/platform-browser';
 4import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store';
 5import { StoreModule } from '@ngrx/store';
 6
 7import { AppRoutingModule } from './app-routing.module';
 8import { AppComponent } from './app.component';
 9import { CrocsComponent } from './crocs/crocs.component';
10import { GatorsComponent } from './gators/gators.component';
11import { reducers } from './reducers/index';
12import { CustomRouterStateSerializer } from './shared/utils';
13import { EffectsModule } from '@ngrx/effects';
14import { TitleUpdaterEffects } from './effects/title-updater';

就是这样!您现在可以在路线定义中定义标题,它们将在路线更改时自动更新!

更进一步,从静态到动态⚡️

静态标题对于大多数用例来说都很好,但是如果你想通过名称欢迎用户或者显示通知计数呢?我们可以将路由数据中的title属性修改为接受上下文的函数。

下面是一个潜在的示例,如果商店上有通知计数:

 1[label title-updater/src/app/app-routing.module.ts]
 2import { NgModule } from '@angular/core';
 3import { Routes, RouterModule } from '@angular/router';
 4import { GatorsComponent } from './gators/gators.component';
 5import { CrocsComponent } from './crocs/crocs.component';
 6import { InboxComponent } from './inbox/inbox.component';
 7
 8const routes: Routes = [
 9  {
10    path: 'gators',
11    component: GatorsComponent,
12    data: { title: () => 'Alligators' }
13  },
14  {
15    path: 'crocs',
16    component: CrocsComponent,
17    data: { title: () => 'Crocodiles' }
18  },
19  {
20  path: 'inbox',
21  component: InboxComponent,
22  data: {
23    // A dynamic title that shows the current notification count!
24    title: (ctx) => {
25      let t = 'Inbox';
26      if(ctx.notificationCount > 0) {
27        t += (${ctx.notificationCount});
28      }
29      return t;
30    }
31  }
32}
33];
 1[label title-updater/src/app/effects/title-updater.ts]
 2import { Title } from '@angular/platform-browser';
 3import { Actions, Effect } from '@ngrx/effects';
 4import { ROUTER_NAVIGATION, RouterNavigationAction } from '@ngrx/router-store';
 5import { Store } from '@ngrx/store';
 6import 'rxjs/add/operator/combineLatest';
 7import { getNotificationCount } from '../selectors.ts';
 8import { RouterStateTitle } from '../shared/utils';
 9
10@Injectable()
11export class TitleUpdaterEffects {
12  // Update title every time route or context changes, pulling the notificationCount from the store.
13  @Effect({ dispatch: false })
14  updateTitle$ = this.actions
15    .ofType(ROUTER_NAVIGATION)
16    .combineLatest(this.store.select(getNotificationCount),
17      (action: RouterNavigationAction<RouterStateTitle>, notificationCount: number) => {
18        // The context we will make available for the title functions to use as they please.
19        const ctx = { notificationCount };
20        this.titleService.setTitle(action.payload.routerState.title(ctx));
21    });

现在,当加载收件箱路线时,用户还可以看到他们的通知计数也是实时更新的!💌

🚀继续试验和探索定制的RouterStateSerializers和@ngrx!

Published At
Categories with 技术
Tagged with
comments powered by Disqus