Page 中通过构造函数注入 Store,基于 Store 进行数据操作。
注意 Component 使用了 changeDetection: ChangeDetectionStrategy.OnPush.
OnPush
means that the change detector's mode will be set to CheckOnce
during hydration.
/app/containers/collection-page.ts
import 'rxjs/add/operator/let'; import { Component, ChangeDetectionStrategy } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs/Observable'; import * as fromRoot from '../reducers'; import { Book } from '../models/book'; @Component({ selector: 'bc-collection-page', changeDetection: ChangeDetectionStrategy.OnPush, template: ` <md-card> <md-card-title>My Collection</md-card-title> </md-card> <bc-book-preview-list [books]="books$ | async"></bc-book-preview-list> `, /** * Container components are permitted to have just enough styles * to bring the view together. If the number of styles grow, * consider breaking them out into presentational * components. */ styles: [` md-card-title { display: flex; justify-content: center; } `] }) export class CollectionPageComponent { books$: Observable<Book[]>; constructor(store: Store<fromRoot.State>) { this.books$ = store.select(fromRoot.getBookCollection); } }
/app/containers/find-book-page.ts
import 'rxjs/add/operator/let'; import 'rxjs/add/operator/take'; import { Component, ChangeDetectionStrategy } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs/Observable'; import * as fromRoot from '../reducers'; import * as book from '../actions/book'; import { Book } from '../models/book'; @Component({ selector: 'bc-find-book-page', changeDetection: ChangeDetectionStrategy.OnPush, template: ` <bc-book-search [query]="searchQuery$ | async" [searching]="loading$ | async" (search)="search($event)"></bc-book-search> <bc-book-preview-list [books]="books$ | async"></bc-book-preview-list> ` }) export class FindBookPageComponent { searchQuery$: Observable<string>; books$: Observable<Book[]>; loading$: Observable<boolean>; constructor(private store: Store<fromRoot.State>) { this.searchQuery$ = store.select(fromRoot.getSearchQuery).take(1); this.books$ = store.select(fromRoot.getSearchResults); this.loading$ = store.select(fromRoot.getSearchLoading); } search(query: string) { this.store.dispatch(new book.SearchAction(query)); } }
注意,点击搜索之后,我们回派发一个 Search 的 Action,但是,在 Book 的 Reducer 中并不处理这个 Action, @ngrx/effect 将会监控这个 Action,进行异步处理。
/app/containers/not-found-page.ts
import { Component, ChangeDetectionStrategy } from '@angular/core'; @Component({ selector: 'bc-not-found-page', changeDetection: ChangeDetectionStrategy.OnPush, template: ` <md-card> <md-card-title>404: Not Found</md-card-title> <md-card-content> <p>Hey! It looks like this page doesn't exist yet.</p> </md-card-content> <md-card-actions> <button md-raised-button color="primary" routerLink="/">Take Me Home</button> </md-card-actions> </md-card> `, styles: [` :host { text-align: center; } `] }) export class NotFoundPageComponent { }
通过 @Input() 参数将数据从页面传递给下面的 Component,事件从底层 Component 冒泡上来。
/app/containers/selected-book-page.ts
import { Component, ChangeDetectionStrategy } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs/Observable'; import * as fromRoot from '../reducers'; import * as collection from '../actions/collection'; import { Book } from '../models/book'; @Component({ selector: 'bc-selected-book-page', changeDetection: ChangeDetectionStrategy.OnPush, template: ` <bc-book-detail [book]="book$ | async" [inCollection]="isSelectedBookInCollection$ | async" (add)="addToCollection($event)" (remove)="removeFromCollection($event)"> </bc-book-detail> ` }) export class SelectedBookPageComponent { book$: Observable<Book>; isSelectedBookInCollection$: Observable<boolean>; constructor(private store: Store<fromRoot.State>) { this.book$ = store.select(fromRoot.getSelectedBook); this.isSelectedBookInCollection$ = store.select(fromRoot.isSelectedBookInCollection); } addToCollection(book: Book) { this.store.dispatch(new collection.AddBookAction(book)); } removeFromCollection(book: Book) { this.store.dispatch(new collection.RemoveBookAction(book)); } }
/app/containers/view-book-page.ts
import '@ngrx/core/add/operator/select'; import 'rxjs/add/operator/map'; import { Component, OnDestroy, ChangeDetectionStrategy } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; import { Subscription } from 'rxjs/Subscription'; import * as fromRoot from '../reducers'; import * as book from '../actions/book'; /** * Note: Container components are also reusable. Whether or not * a component is a presentation component or a container * component is an implementation detail. * * The View Book Page's responsibility is to map router params * to a 'Select' book action. Actually showing the selected * book remains a responsibility of the * SelectedBookPageComponent */ @Component({ selector: 'bc-view-book-page', changeDetection: ChangeDetectionStrategy.OnPush, template: ` <bc-selected-book-page></bc-selected-book-page> ` }) export class ViewBookPageComponent implements OnDestroy { actionsSubscription: Subscription; constructor(store: Store<fromRoot.State>, route: ActivatedRoute) { this.actionsSubscription = route.params .select<string>('id') .map(id => new book.SelectAction(id)) .subscribe(store); } ngOnDestroy() { this.actionsSubscription.unsubscribe(); } }
import 'rxjs/add/operator/let'; import { Observable } from 'rxjs/Observable'; import { Component, ChangeDetectionStrategy } from '@angular/core'; import { Store } from '@ngrx/store'; import * as fromRoot from '../reducers'; import * as layout from '../actions/layout'; @Component({ selector: 'bc-app', changeDetection: ChangeDetectionStrategy.OnPush, template: ` <bc-layout> <bc-sidenav [open]="showSidenav$ | async"> <bc-nav-item (activate)="closeSidenav()" routerLink="/" icon="book" hint="View your book collection"> My Collection </bc-nav-item> <bc-nav-item (activate)="closeSidenav()" routerLink="/book/find" icon="search" hint="Find your next book!"> Browse Books </bc-nav-item> </bc-sidenav> <bc-toolbar (openMenu)="openSidenav()"> Book Collection </bc-toolbar> <router-outlet></router-outlet> </bc-layout> ` }) export class AppComponent { showSidenav$: Observable<boolean>; constructor(private store: Store<fromRoot.State>) { /** * Selectors can be applied with the `select` operator which passes the state * tree to the provided selector */ this.showSidenav$ = this.store.select(fromRoot.getShowSidenav); } closeSidenav() { /** * All state updates are handled through dispatched actions in 'container' * components. This provides a clear, reproducible history of state * updates and user interaction through the life of our * application. */ this.store.dispatch(new layout.CloseSidenavAction()); } openSidenav() { this.store.dispatch(new layout.OpenSidenavAction()); } }