最新消息:XAMPP默认安装之后是很不安全的,我们只需要点击左方菜单的 "安全"选项,按照向导操作即可完成安全设置。

ngrx/store 完成篇

XAMPP下载 文, 员 46浏览 0评论
 ngrx/store 完成篇
今天開始 今天完成

今天目標:完成會員報告
前次已經將會員報告 store 的部分完成,接下來我們來修改跟報告有關的元件 (components) 跟服務,在修改之前,我們先來做一件事,還記得當初我們設定會員模組時的路由設定
src/app/member/member-routing.module.ts

//…省略
const routes: Routes = [
{
path: '',
children: [
{ path: 'report-list', component: ReportListComponent },
{ path: 'report/:rptId', component: ReportComponent },
{ path: '', redirectTo: 'report-list', pathMatch: 'full' }
]
}
]
//… 省略
這些路徑下的元件實際上都會用到狀態樹裡的報告,我們希望確定在使用者進入這些元件前,報告已經從後端存到我們的 store 中, Angular 的路由防護 (Route Guard),很適合這種用途,因為沒有報告的話,進入這些元件也就沒有意義,寫法跟之前的 Auth.guard.ts 類似

報告防護
第一步:建立目錄及檔案在 src/app/member 下,順便註冊到 member 模組

mkdir guards
cd guards
ng generate guard report –module member
第二步:修改 report.guard.ts

import { Injectable } from ’@angular/core’;
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, CanActivateChild } from ’@angular/router’;
import { Observable } from ’rxjs/Observable’;

import { Store } from ’@ngrx/store’;
import * as fromStore from ’../../store’;
import { of } from ’rxjs/observable/of’;
import { tap, take, switchMap, catchError } from ’rxjs/operators’;

@Injectable()
export class ReportGuard implements CanActivate, CanActivateChild {
constructor(
private store: Store<fromStore.State>
) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this.checkStore().pipe(
switchMap(() => of(true)),
catchError(() => of(false))
);
}
canActivateChild(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this.checkStore().pipe(
switchMap(() => of(true)),
catchError(() => of(false))
);
}
// helper function
checkStore(): Observable<boolean> {
return this.store.select(fromStore.getReports)
.pipe(
tap(res => {
if (res.length === 0) {
this.store.dispatch(new fromStore.getReportAction());
}
}),
switchMap(res => of(res.length !== 0)),
take(1)
);
}
}
協議建立 CanActivate 跟 CanActivateChild 介面
constructor 導入 store
建立一個幫忙函數 checkStore(),先看看 store 中的報告存在嗎?狀態樹中的報告會初始為空陣列,如果陣列是空的,分派一個動作 store.dispatch(new fromStore.getReportAction()),透過 repot.effect 經過 report.service 從後端拿資料
等後端(非同步)拿到資料後 take(1) 函數返回
canActivate 跟 canActivateChild 直接呼叫 checkStore()
第三步: 修改報告服務 src/app/memberrvices/reports.service.ts

import { Injectable } from ’@angular/core’;
import { HttpClient, HttpErrorResponse } from ’@angular/commontp’;

import { AppConfig } from ’../..‘;
import { Report, Response } from ’../../models’;

import { Observable } from ’rxjs/Observable’;

@Injectable()
export class ReportsService {

constructor(
private appConfig: AppConfig,
private http: HttpClient
) {
}

// get report from server
getReportsFromServer(): Observable<Response> {
return this.http.get<Response>(this.appConfig.apiUrl + ’/reports’);
}
}
跟之前使用者服務一樣,刪除所有跟狀態有關的變數跟函數,只留下跟後端連結的部分
第四步:因為我們現在的報告狀態直接在 StoreModule.forRoot 下,也就是程式一開始就會初始的部分,而這個部分會用到報告服務(report.service),也就是我們需要將報告服務從會員模組移到 app.module.ts 下。

另外一種選擇則是在會員模組下使用 StoreModule.forFeature() 將報告的部分載入,這時候報告模組就會完整的存在會員模組中,而一旦載入會員模組,這個部分的狀態也會接回原本的狀態樹中,但篇幅關係,我們先省略這個部分,直接將報告服務改到 app.module.ts 下,並從 member.module.ts 下刪除
src/app/app.module.ts

//…省略
import { ReportsService } from ’./memberrvices/reports.service’;
//…
@NgModule({
//… 省略
providers: [
AuthGuard,
UtilsService,
ReportsService,
StartupService,
//... 省略
最後來修改元件

報告摘要元件
src/app/member/report-list/report-list.component.ts

import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';

import { Report } from '../../models';
import { Observable } from 'rxjs/Observable';

import { Store } from '@ngrx/store';
import * as fromStore from '../../store';

@Component({
selector: 'app-report-list',
templateUrl: './report-list.component.html',
styleUrls: ['./report-list.component.css'].
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ReportListComponent implements OnInit {
reports$: Observable<Report[]>;
constructor(
private store: Store<fromStore.State>
) { }
ngOnInit() {
this.reports$ = this.store.select(fromStore.getReports);
}
onClick(report: Report) {
this.store.dispatch(new fromStore.Go({ path: ['/member/report', report.id

] }));
}
}
導入 Store,刪除報告跟 Router 服務
加入 ChangeDetectionStrategy,將 changeDection 從 .Default 改為 .OnPush,官方文件,這篇文章可以參考 ANGULAR CHANGE DETECTION EXPLAINED 這個意思是當 Angular 載入一個元件時,有一個複雜的流程判斷它以下的 DOM 或者子元件有沒有變化,有變化的話要隨時更新虛擬 DOM, 如果我們告訴 Angular,不用檢查了,我的資料來自於 Observable 的 push,那會節省很多時間來做載入的動作,而現在我們的資料都來自於 store ,所以我們告訴 Angular 不用幫我檢查
將 this.report$ 直接指向 store.select(fromStore.getReports)
當使用者點擊一份摘要時,用 store.dispatch(new fromStore.Go()) 移轉路徑到報告下
報告元件
修改 src/app/member/report/report.component.ts

import { Component, OnInit, ChangeDetectionStrategy } from ’@angular/core’;
import { Report } from ’../../models’;
import { Observable } from ’rxjs/Observable’;

import { Store } from ’@ngrx/store’;
import * as fromStore from ’../../store’;

@Component({
selector: ’app-report’,
templateUrl: ’./report.component.html’,
styleUrls: ['./report.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ReportComponent implements OnInit {
report$: Observable<Report>;
constructor(
private store: Store<fromStore.State>
) { }
ngOnInit() {
this.report$ = this.store.select(fromStore.getSelectedReport);
}
}
一樣導入 Store 刪除服務
加入 ChangeDetectionStrategy 變為 .OnPush
將報告指向我們之前在 selector 做的 Join 狀態 store.select(fromStore.getSelectedReport)
結語
如之前規劃,我們將 store 導入之後,所有元件的資料來自於 store,而服務則專門對應後端

QQ截图20181011160333
 當然就像之前講的,這樣的架構比較適合中大型的專案,畢竟需要很多額外的程式碼,但是帶來的好處,就像之前提的

可追蹤:因為有了Chrome extention 的 Redux DevTool,您可以清楚了解狀態改變的流程,以進行對可能存在的程式錯誤做除錯。
增加效能:對一些 Component 的 ChangeDetectionStrategy 為 OnPush
易於測試:因為 Reducer 是純函數,便於測試
很可惜篇幅關係,我們沒有談到單元測試的部分,也沒有談到 store forFeature 的做法,讓我先休息一下,可能的話會在我的部落格 將這個部分做個整理

转载请注明:XAMPP中文组官网 » ngrx/store 完成篇