今天的目標:將會員有關的元件及服務從使用者服務轉為向 ngrx/store 做 dispatch 跟 select
第一步:修改 src/app/user/login/login.component.ts
// ... 省略
import { Store } from '@ngrx/store';
import * as fromStore from '../../store';
//... 省略
export class LoginComponent implements OnInit {
public form: FormGroup;
constructor(
private fb: FormBuilder,
private store: Store<fromStore.State>,
private router: Router,
private snackbar: MatSnackBar
) { }
login() {
this.store.dispatch(new fromStore.LoginAction(this.form.value));
this.store.select(fromStore.getIsLogin)
.subscribe(res => {
if (res) {
this.snackbar.open('登入成功', 'OK', { duration: 3000 });
this.router.navigate(['/member']);
} else {
this.snackbar.open('請檢查使用者名稱及密碼', 'OK', { duration: 3000 });
}
})
}
}
userService
的部分換成 store.disptch(new fromStore.LoginAction()
,接著用 store.select(fromStore.getIsLogin)
來選擇狀態樹中 login 的狀態檢查一下瀏覽器,一且順利的話,畫面截圖如下
可以看到 Redux devtools
已經看到會員狀態已經改變,但是頁面還沒轉到會員,接下來
修改 src/app/guards/auth.guards.ts
//...省略
import { Store } from '@ngrx/store';
import * as fromStore from '../store';
import { tap, take } from 'rxjs/operators';
import { of } from 'rxjs/observable/of';
@Injectable()
export class AuthGuard implements CanActivate, CanLoad {
loginStatus$: Observable<boolean>
constructor(
private store: Store<fromStore.State>,
private router: Router
) {
this.loginStatus$ = store.select(fromStore.getIsLogin);
}
//... 省略
一樣將使用者服務刪除,導入 Store,在 constructor 將 loginStatus$
變為 store.select(fromStore.getIsLogin)
就完成
現在登入後會轉到會員畫面,但是瀏覽列還沒變,我們來修改一下
src/app/navbar/navbar.component.ts
//... 省略
import { Store } from '@ngrx/store';
import * as fromStore from '../store';
import { User } from '../models';
//... 省略
export class NavbarComponent implements OnInit {
login$: Observable<boolean>;
user$: Observable<string>;
constructor(
private store: Store<fromStore.State>,
private router: Router
) { }
ngOnInit() {
this.login$ = this.store.select(fromStore.getIsLogin);
this.user$ = this.store.select(fromStore.getCurrentUser);
}
logout() {
this.store.dispatch(new fromStore.LogoutAction());
this.router.navigate(['/'])
}
}
login$
跟 user$
換成用 store.select()
logout
時用 store.dispatch(new fromStore.LogoutAction())
來改變狀態接下來,修改開始服務
src/app/services/startup.service.ts
//... 省略
import { UtilsService } from '.';
import { Store } from '@ngrx/store';
import * as fromStore from '../store';
@Injectable()
export class StartupService {
constructor(
private injector: Injector,
private utils: UtilsService,
private store: Store<fromStore.State>
) { }
load(): Promise<any> {
return new Promise((resolve, reject) => {
if (!this.utils.isTokenExpired()) {
this.store.dispatch(new fromStore.GetUserAction());
return this.store.select(fromStore.getIsLogin)
.pipe(
filter(status => status)
)
.subscribe(res => {
if (res) {
setInterval(() => {
this.checkStatus();
}, 1000 * 60 * 5) // check current status every 5 min
}
resolve(res);
}, err => {
console.log(err);
reject(err);
});
} else {
resolve('no token or token expired');
}
});
}
checkStatus() {
if (this.utils.isTokenExpired()) { // if token expired or not exist
this.store.dispatch(new fromStore.LogoutAction());
const router = this.injector.get(Router);
router.navigate(['/']);
}
}
}
load()
加入判斷 token 有沒有過期store.dispatch(new fromStore.GetUserAction())
來從後端取得使用者資訊,這裡是非同步,我們用 filter
來等後端資訊完成,因為 token 未過期,所以 status 一定要是真checkStatus()
當 token 過期時,使用 store.dispatch(new fromStore.LogoutAction())
來改變狀態src/app/user/service/user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { AppConfig } from '../../share';
import { User, Response } from '../../models';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { of } from 'rxjs/observable/of';
import { UtilsService } from '../../services';
@Injectable()
export class UserService {
constructor(
private http: HttpClient,
private appConfig: AppConfig,
private utils: UtilsService
) { }
loginServer(loginData): Observable<Response> {
let username = loginData.username.trim();
let password = loginData.password.trim();
return this.http.post<Response>(this.appConfig.apiUrl + '/users/authenticate', { username: username, password: password });
}
// get user from server
getUserFromServer(): Observable<User> {
if (!this.utils.isTokenExpired()) {
const token = this.utils.getToken();
return this.http.post(this.appConfig.apiUrl + '/users/currentUser', { 'token': token })
.map((res: Response) => {
if (res.success) {
return { username: res.payload };
} else {
return null;
}
})
} else {
return of(null);
}
}
}
loginServer()
跟 getUserFromServer()
getUserFromServer()
,這裡有一個 Bug, 應該要回 { username: res.payload }
而不是 res.payload
因為後端的 res.payload 只是一個 string我們已經完成會員的部分,也就是所有跟會員有關的元件,現在只對 Store,不再經由使用者服務。
接下來在做跟報告有關的元件之前,也們先來看一下路由的部分,路由 (Router) 也可以當成是狀態的一部份,因為在報告的模組中,我們會用到 /member/report/3
這樣的路徑,而之前我們用報告服務來抓這個單獨的報告,現在我們想從 Store 的狀態樹中,結合報告狀態跟路由狀態來取得這份單獨的報告,所以我們先來做路由 (Router) 的 ngrx/store