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