摘要:如果該構(gòu)造函數(shù)在我們所期望的中運行,就沒有任何祖先注入器能夠提供的實例,于是注入器會放棄查找。但裝飾器表示找不到該服務(wù)也無所謂。用處理導(dǎo)航到子路由的情況。路由器會先按照從最深的子路由由下往上檢查的順序來檢查守護條件。
第一節(jié):Angular 2.0 從0到1 (一)
第二節(jié):Angular 2.0 從0到1 (二)
第三節(jié):Angular 2.0 從0到1 (三)
第四節(jié):Angular 2.0 從0到1 (四)
第五節(jié):Angular 2.0 從0到1 (五)
第四節(jié)我們完成的Todo的基本功能看起來還不錯,但是有個大問題,就是每個用戶看到的都是一樣的待辦事項,我們希望的是每個用戶擁有自己的待辦事項列表。我們來分析一下怎么做,如果每個todo對象帶一個UserId屬性是不是可以解決呢?好像可以,邏輯大概是這樣:用戶登錄后轉(zhuǎn)到/todo,TodoComponent得到當(dāng)前用戶的UserId,然后調(diào)用TodoService中的方法,傳入當(dāng)前用戶的UserId,TodoService中按UserId去篩選當(dāng)前用戶的Todos。
但可惜我們目前的LoginComponent還是個實驗品,很多功能的缺失,我們是先去做Login呢,還是利用現(xiàn)有的Todo對象先試驗一下呢?我個人的習(xí)慣是先進行試驗。
按之前我們分析的,給todo加一個userId屬性,我們手動給我們目前的數(shù)據(jù)加上userId屬性吧。更改todo odo-data.json為下面的樣子:
{ "todos": [ { "id": "bf75769b-4810-64e9-d154-418ff2dbf55e", "desc": "getting up", "completed": false, "userId": 1 }, { "id": "5894a12f-dae1-5ab0-5761-1371ba4f703e", "desc": "have breakfast", "completed": true, "userId": 2 }, { "id": "0d2596c4-216b-df3d-1608-633899c5a549", "desc": "go to school", "completed": true, "userId": 1 }, { "id": "0b1f6614-1def-3346-f070-d6d39c02d6b7", "desc": "test", "completed": false, "userId": 2 }, { "id": "c1e02a43-6364-5515-1652-a772f0fab7b3", "desc": "This is a te", "completed": false, "userId": 1 } ] }
如果你還沒有啟動json-server的話讓我們啟動它: json-server ./src/app/todo/todo-data.json,然后打開瀏覽器在地址欄輸入http://localhost:3000/todos/?userId=2你會看到只有userId=2的json被輸出了
[ { "id": "5894a12f-dae1-5ab0-5761-1371ba4f703e", "desc": "have breakfast", "completed": true, "userId": 2 }, { "id": "0b1f6614-1def-3346-f070-d6d39c02d6b7", "desc": "test", "completed": false, "userId": 2 } ]
有興趣的話可以再試試http://localhost:3000/todos/?userId=2&completed=false或其他組合查詢。現(xiàn)在todo有了userId字段,但我們還沒有User對象,User的json表現(xiàn)形式看起來應(yīng)該是這樣:
{ "id": 1, "username": "wang", "password": "1234" }
當(dāng)然這個表現(xiàn)形式有很多問題,比如密碼是明文的,這些問題我們先不管,但大概樣子是類似的。那么現(xiàn)在如果要建立User數(shù)據(jù)庫的話,我們應(yīng)該新建一個user-data.json
{ "users": [ { "id": 1, "username": "wang", "password": "1234" }, { "id": 2, "username": "peng", "password": "5678" } ] }
但這樣做的話感覺多帶帶為其建一個文件有點不值得,我們干脆把user和todo數(shù)據(jù)都放在一個文件吧,現(xiàn)在刪除./src/app/todo/todo-data.json刪除,在srcapp下面新建一個data.json
//srcappdata.json { "todos": [ { "id": "bf75769b-4810-64e9-d154-418ff2dbf55e", "desc": "getting up", "completed": false, "userId": 1 }, { "id": "5894a12f-dae1-5ab0-5761-1371ba4f703e", "desc": "have breakfast", "completed": true, "userId": 2 }, { "id": "0d2596c4-216b-df3d-1608-633899c5a549", "desc": "go to school", "completed": true, "userId": 1 }, { "id": "0b1f6614-1def-3346-f070-d6d39c02d6b7", "desc": "test", "completed": false, "userId": 2 }, { "id": "c1e02a43-6364-5515-1652-a772f0fab7b3", "desc": "This is a te", "completed": false, "userId": 1 } ], "users": [ { "id": 1, "username": "wang", "password": "1234" }, { "id": 2, "username": "peng", "password": "5678" } ] }
當(dāng)然有了數(shù)據(jù),我們就得有對應(yīng)的對象,基于同樣的理由,我們把所有的entity對象都放在一個文件:刪除srcapp odo odo.model.ts,在srcapp下新建一個目錄domain,然后在domain下新建一個entities.ts,請別忘了更新所有的引用。
export class Todo { id: string; desc: string; completed: boolean; userId: number; } export class User { id: number; username: string; password: string; }驗證用戶賬戶的流程
我們來梳理一下用戶驗證的流程
存儲要訪問的URL
根據(jù)本地的已登錄標(biāo)識判斷是否此用戶已經(jīng)登錄,如果已登錄就直接放行
如果未登錄導(dǎo)航到登錄頁面 用戶填寫用戶名和密碼進行登錄
系統(tǒng)根據(jù)用戶名查找用戶表中是否存在此用戶,如果不存在此用戶,返回錯誤
如果存在對比填寫的密碼和存儲的密碼是否一致,如果不一致,返回錯誤
如果一致,存儲此用戶的已登錄標(biāo)識到本地
導(dǎo)航到原本要訪問的URL即第一步中存儲的URL,刪掉本地存儲的URL
看上去我們需要實現(xiàn)
UserService:用于通過用戶名查找用戶并返回用戶
AuthService:用于認證用戶,其中需要利用UserService的方法
AuthGuard:路由攔截器,用于攔截到路由后通過AuthService來知道此用戶是否有權(quán)限訪問該路由,根據(jù)結(jié)果導(dǎo)航到不同路徑。
看到這里,你可能有些疑問,為什么我們不把UserService和AuthService合并呢?這是因為UserService是用于對用戶的操作的,不光認證流程需要用到它,我們未來要實現(xiàn)的一系列功能都要用到它,比如注冊用戶,后臺用戶管理,以及主頁要顯示用戶名稱等。
根據(jù)這個邏輯流程,我們來組織一下代碼。開始之前我們想把認證相關(guān)的代碼組織在一個新的模塊下,我們暫時叫它core吧。在srcapp下新建一個core目錄,然后在core下面新建一個core.module.ts
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from "@angular/core"; import { CommonModule } from "@angular/common"; @NgModule({ imports: [ CommonModule ] }) export class CoreModule { constructor (@Optional() @SkipSelf() parentModule: CoreModule) { if (parentModule) { throw new Error( "CoreModule is already loaded. Import it in the AppModule only"); } }
注意到這個模塊和其他模塊不太一樣,原因是我們希望只在應(yīng)用啟動時導(dǎo)入它一次,而不會在其它地方導(dǎo)入它。在模塊的構(gòu)造函數(shù)中我們會要求Angular把CoreModule注入自身,這看起來像一個危險的循環(huán)注入。不過,@SkipSelf裝飾器意味著在當(dāng)前注入器的所有祖先注入器中尋找CoreModule。如果該構(gòu)造函數(shù)在我們所期望的AppModule中運行,就沒有任何祖先注入器能夠提供CoreModule的實例,于是注入器會放棄查找。默認情況下,當(dāng)注入器找不到想找的提供商時,會拋出一個錯誤。 但@Optional裝飾器表示找不到該服務(wù)也無所謂。 于是注入器會返回null,parentModule參數(shù)也就被賦成了空值,而構(gòu)造函數(shù)沒有任何異常。
那么我們在什么時候會需要這樣一個模塊?比如在這個模塊中我們可能會要提供用戶服務(wù)(UserService),這樣的服務(wù)系統(tǒng)各個地方都需要,但我們不希望它被創(chuàng)建多次,希望它是一個單例。再比如某些只應(yīng)用于AppComponent模板的一次性組件,沒有必要共享它們,然而如果把它們留在根目錄,還是顯得太亂了。我們可以通過這種形式隱藏它們的實現(xiàn)細節(jié)。然后通過根模塊AppModule導(dǎo)入CoreModule來獲取其能力。
首先我們來看看Angular內(nèi)建的路由守衛(wèi)機制,在實際工作中我們常常會碰到下列需求:
該用戶可能無權(quán)導(dǎo)航到目標(biāo)組件。 導(dǎo)航前需要用戶先登錄(認證)。
在顯示目標(biāo)組件前,我們可能得先獲取某些數(shù)據(jù)。
在離開組件前,我們可能要先保存修改。
我們可能要詢問用戶:你是否要放棄本次更改,而不用保存它們?
我們可以往路由配置中添加守衛(wèi),來處理這些場景。守衛(wèi)返回true,導(dǎo)航過程會繼續(xù);返回false,導(dǎo)航過程會終止,且用戶會留在原地(守衛(wèi)還可以告訴路由器導(dǎo)航到別處,這樣也取消當(dāng)前的導(dǎo)航)。
路由器支持多種守衛(wèi):
用CanActivate來處理導(dǎo)航到某路由的情況。
用CanActivateChild處理導(dǎo)航到子路由的情況。
用CanDeactivate來處理從當(dāng)前路由離開的情況。
用Resolve在路由激活之前獲取路由數(shù)據(jù)。
用CanLoad來處理異步導(dǎo)航到某特性模塊的情況。
在分層路由的每個級別上,我們都可以設(shè)置多個守衛(wèi)。路由器會先按照從最深的子路由由下往上檢查的順序來檢查CanDeactivate守護條件。然后它會按照從上到下的順序檢查CanActivate守衛(wèi)。如果任何守衛(wèi)返回false,其它尚未完成的守衛(wèi)會被取消,這樣整個導(dǎo)航就被取消了。
本例中我們希望用戶未登錄前不能訪問todo,那么需要使用CanActivate
import { AuthGuardService } from "../core/auth-guard.service"; const routes: Routes = [ { path: "todo/:filter", canActivate: [AuthGuardService], component: TodoComponent } ];
當(dāng)然光這么寫是沒有用的,下面我們來建立一個AuthGuardService,命令行中鍵入ng g s core/auth-guard(angular-cli對于Camel寫法的文件名是采用-來分隔每個大寫的詞)。
import { Injectable, Inject } from "@angular/core"; import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router"; @Injectable() export class AuthGuardService implements CanActivate { constructor(private router: Router) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { //取得用戶訪問的URL let url: string = state.url; return this.checkLogin(url); } checkLogin(url: string): boolean { //如果用戶已經(jīng)登錄就放行 if (localStorage.getItem("userId") !== null) { return true; } //否則,存儲要訪問的URl到本地 localStorage.setItem("redirectUrl", url); //然后導(dǎo)航到登陸頁面 this.router.navigate(["/login"]); //返回false,取消導(dǎo)航 return false; } }
觀察上面代碼,我們發(fā)現(xiàn)本地存儲的userId的存在與否決定了用戶是否已登錄的狀態(tài),這當(dāng)然是一個漏洞百出的實現(xiàn),但我們暫且不去管它。現(xiàn)在我們要在登錄時把這個狀態(tài)值寫進去。我們新建一個登錄鑒權(quán)的AuthService:ng g s core/auth
import { Injectable, Inject } from "@angular/core"; import { Http, Headers, Response } from "@angular/http"; import "rxjs/add/operator/toPromise"; import { Auth } from "../domain/entities"; @Injectable() export class AuthService { constructor(private http: Http, @Inject("user") private userService) { } loginWithCredentials(username: string, password: string): Promise{ return this.userService .findUser(username) .then(user => { let auth = new Auth(); localStorage.removeItem("userId"); let redirectUrl = (localStorage.getItem("redirectUrl") === null)? "/": localStorage.getItem("redirectUrl"); auth.redirectUrl = redirectUrl; if (null === user){ auth.hasError = true; auth.errMsg = "user not found"; } else if (password === user.password) { auth.user = Object.assign({}, user); auth.hasError = false; localStorage.setItem("userId",user.id); } else { auth.hasError = true; auth.errMsg = "password not match"; } return auth; }) .catch(this.handleError); } private handleError(error: any): Promise { console.error("An error occurred", error); // for demo purposes only return Promise.reject(error.message || error); } }
注意到我們返回了一個Auth對象,這是因為我們要知道幾件事:
用戶最初要導(dǎo)航的頁面URL
用戶對象
如果發(fā)生錯誤的話,是什么錯誤,我們需要反饋給用戶
這個Auth對象同樣在srcappdomainentities.ts中聲明
export class Auth { user: User; hasError: boolean; errMsg: string; redirectUrl: string; }
當(dāng)然我們還得實現(xiàn)UserService:ng g s user
import { Injectable } from "@angular/core"; import { Http, Headers, Response } from "@angular/http"; import "rxjs/add/operator/toPromise"; import { User } from "../domain/entities"; @Injectable() export class UserService { private api_url = "http://localhost:3000/users"; constructor(private http: Http) { } findUser(username: string): Promise{ const url = `${this.api_url}/?username=${username}`; return this.http.get(url) .toPromise() .then(res => { let users = res.json() as User[]; return (users.length>0)?users[0]:null; }) .catch(this.handleError); } private handleError(error: any): Promise { console.error("An error occurred", error); // for demo purposes only return Promise.reject(error.message || error); } }
這段代碼比較簡單,就不細講了。下面我們改造一下srcapploginlogin.component.html,在原來用戶名的驗證信息下加入,用于顯示用戶不存在或者密碼不對的情況
this is requiredshould be at least 3 charactors{{auth.errMsg}}
當(dāng)然我們還得改造srcapploginlogin.component.ts
import { Component, OnInit, Inject } from "@angular/core"; import { Router, ActivatedRoute, Params } from "@angular/router"; import { Auth } from "../domain/entities"; @Component({ selector: "app-login", templateUrl: "./login.component.html", styleUrls: ["./login.component.css"] }) export class LoginComponent implements OnInit { username = ""; password = ""; auth: Auth; constructor(@Inject("auth") private service, private router: Router) { } ngOnInit() { } onSubmit(formValue){ this.service .loginWithCredentials(formValue.login.username, formValue.login.password) .then(auth => { let redirectUrl = (auth.redirectUrl === null)? "/": auth.redirectUrl; if(!auth.hasError){ this.router.navigate([redirectUrl]); localStorage.removeItem("redirectUrl"); } else { this.auth = Object.assign({}, auth); } }); } }
然后我們別忘了在core模塊中聲明我們的服務(wù)srcappcorecore.module.ts
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from "@angular/core"; import { CommonModule } from "@angular/common"; import { AuthService } from "./auth.service"; import { UserService } from "./user.service"; import { AuthGuardService } from "./auth-guard.service"; @NgModule({ imports: [ CommonModule ], providers: [ { provide: "auth", useClass: AuthService }, { provide: "user", useClass: UserService }, AuthGuardService ] }) export class CoreModule { constructor (@Optional() @SkipSelf() parentModule: CoreModule) { if (parentModule) { throw new Error( "CoreModule is already loaded. Import it in the AppModule only"); } } }
最后我們得改寫一下TodoService,因為我們訪問的URL變了,要傳遞的數(shù)據(jù)也有些變化
//todo.service.ts代碼片段 // POST /todos addTodo(desc:string): Promise{ //“+”是一個簡易方法可以把string轉(zhuǎn)成number const userId:number = +localStorage.getItem("userId"); let todo = { id: UUID.UUID(), desc: desc, completed: false, userId }; return this.http .post(this.api_url, JSON.stringify(todo), {headers: this.headers}) .toPromise() .then(res => res.json() as Todo) .catch(this.handleError); } // GET /todos getTodos(): Promise { const userId = +localStorage.getItem("userId"); const url = `${this.api_url}/?userId=${userId}`; return this.http.get(url) .toPromise() .then(res => res.json() as Todo[]) .catch(this.handleError); } // GET /todos?completed=true/false filterTodos(filter: string): Promise { const userId:number = +localStorage.getItem("userId"); const url = `${this.api_url}/?userId=${userId}`; switch(filter){ case "ACTIVE": return this.http .get(`${url}&completed=false`) .toPromise() .then(res => res.json() as Todo[]) .catch(this.handleError); case "COMPLETED": return this.http .get(`${url}&completed=true`) .toPromise() .then(res => res.json() as Todo[]) .catch(this.handleError); default: return this.getTodos(); } }
現(xiàn)在應(yīng)該已經(jīng)ok了,我們來看看效果:
用戶密碼不匹配時,顯示password not match
用戶不存在時,顯示user not found
直接在瀏覽器地址欄輸入http://localhost:4200/todo,你會發(fā)現(xiàn)被重新導(dǎo)航到了login。輸入正確的用戶名密碼后,我們被導(dǎo)航到了todo,現(xiàn)在每個用戶都可以創(chuàng)建屬于自己的待辦事項了。
Angular團隊推薦把路由模塊化,這樣便于使業(yè)務(wù)邏輯和路由松耦合。雖然目前在我們的應(yīng)用中感覺用處不大,但按官方推薦的方式還是和大家一起改造一下吧。刪掉原有的app.routes.ts和todo.routes.ts。添加app-routing.module.ts:
import { NgModule } from "@angular/core"; import { Routes, RouterModule } from "@angular/router"; import { LoginComponent } from "./login/login.component"; const routes: Routes = [ { path: "", redirectTo: "login", pathMatch: "full" }, { path: "login", component: LoginComponent }, { path: "todo", redirectTo: "todo/ALL" } ]; @NgModule({ imports: [ RouterModule.forRoot(routes) ], exports: [ RouterModule ] }) export class AppRoutingModule {}
以及srcapp odo odo-routing.module.ts
import { NgModule } from "@angular/core"; import { Routes, RouterModule } from "@angular/router"; import { TodoComponent } from "./todo.component"; import { AuthGuardService } from "../core/auth-guard.service"; const routes: Routes = [ { path: "todo/:filter", canActivate: [AuthGuardService], component: TodoComponent } ]; @NgModule({ imports: [ RouterModule.forChild(routes) ], exports: [ RouterModule ] }) export class TodoRoutingModule { }
并分別在AppModule和TodoModule中引入路由模塊。
用VSCode進行調(diào)試有讀者問如何用vscode進行debug,這章我們來介紹一下。首先需要安裝一個vscode插件,點擊左側(cè)最下面的圖標(biāo)或者“在查看菜單中選擇命令面板,輸入install,選擇擴展:安裝擴展”,然后輸入“debugger for chrome”回車,點擊安裝即可。
然后點擊最左邊的倒數(shù)第二個按鈕
如果是第一次使用的話,齒輪圖標(biāo)上會有個紅點,點擊選擇debugger for chrome,vscode會幫你創(chuàng)建一個配置文件,這個文件位于.vscodelaunch.json是debugger的配置文件,請改寫成下面的樣子。注意如果是MacOSX或者Linux,請把userDataDir替換成對應(yīng)的臨時目錄,另外把"webpack:///C:*":"C:/*"替換成"webpack:///*": "/*",這句是因為angular-cli是采用webpack打包的,如果沒有使用angular-cli不需要添加這句。
{ "version": "0.2.0", "configurations": [ { "name": "Launch Chrome against localhost, with sourcemaps", "type": "chrome", "request": "launch", "url": "http://localhost:4200", "sourceMaps": true, "runtimeArgs": [ "--disable-session-crashed-bubble", "--disable-infobars" ], "diagnosticLogging": true, "webRoot": "${workspaceRoot}/src", //windows setup "userDataDir": "C: empchromeDummyDir", "sourceMapPathOverrides": { "webpack:///C:*":"C:/*" //use "webpack:///*": "/*" on Linux/OSX } }, { "name": "Attach to Chrome, with sourcemaps", "type": "chrome", "request": "attach", "port": 9222, "sourceMaps": true, "diagnosticLogging": true, "webRoot": "${workspaceRoot}/src", "sourceMapPathOverrides": { "webpack:///C:*":"C:/*" } } ] }
現(xiàn)在你可以試著在源碼中設(shè)置一個斷點,點擊debug視圖中的debug按鈕,可以嘗試右鍵點擊變量把它放到監(jiān)視中看看變量值或者逐步調(diào)試應(yīng)用。
本章完整代碼見: https://github.com/wpcfan/awe...
第一節(jié):Angular 2.0 從0到1 (一)
第二節(jié):Angular 2.0 從0到1 (二)
第三節(jié):Angular 2.0 從0到1 (三)
第四節(jié):Angular 2.0 從0到1 (四)
第五節(jié):Angular 2.0 從0到1 (五)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/86823.html
摘要:官方支持微軟出品,是的超集,是的強類型版本作為首選編程語言,使得開發(fā)腳本語言的一些問題可以更早更方便的找到。第一個組件那么我們來為我們的增加一個吧,在命令行窗口輸入。引導(dǎo)過程通過在中引導(dǎo)來啟動應(yīng)用。它們的核心就是。 第一節(jié):Angular 2.0 從0到1 (一)第二節(jié):Angular 2.0 從0到1 (二)第三節(jié):Angular 2.0 從0到1 (三) 第一章:認識Angular...
摘要:下面我們看看如果使用是什么樣子的,首先我們需要在組件的修飾器中配置,然后在組件的構(gòu)造函數(shù)中使用參數(shù)進行依賴注入。 第一節(jié):Angular 2.0 從0到1 (一)第二節(jié):Angular 2.0 從0到1 (二)第三節(jié):Angular 2.0 從0到1 (三) 第二節(jié):用Form表單做一個登錄控件 對于login組件的小改造 在 hello-angularsrcapploginlogin...
摘要:如何在中使用動畫前端掘金本文講一下中動畫應(yīng)用的部分。與的快速入門指南推薦前端掘金是非常棒的框架,能夠創(chuàng)建功能強大,動態(tài)功能的。自發(fā)布以來,已經(jīng)廣泛應(yīng)用于開發(fā)中。 如何在 Angular 中使用動畫 - 前端 - 掘金本文講一下Angular中動畫應(yīng)用的部分。 首先,Angular本生不提供動畫機制,需要在項目中加入Angular插件模塊ngAnimate才能完成Angular的動畫機制...
摘要:而且此時我們注意到其實沒有任何一個地方目前還需引用了,這就是說我們可以安全地把從組件中的修飾符中刪除了。 第一節(jié):Angular 2.0 從0到1 (一)第二節(jié):Angular 2.0 從0到1 (二)第三節(jié):Angular 2.0 從0到1 (三) 作者:王芃 wpcfan@gmail.com 第四節(jié):進化!模塊化你的應(yīng)用 一個復(fù)雜組件的分拆 上一節(jié)的末尾我偷懶的甩出了大量代碼,可能...
摘要:接下來繼續(xù)介紹三種架構(gòu)模式,分別是查詢分離模式微服務(wù)模式多級緩存模式。分布式應(yīng)用程序可以基于實現(xiàn)諸如數(shù)據(jù)發(fā)布訂閱負載均衡命名服務(wù)分布式協(xié)調(diào)通知集群管理選舉分布式鎖和分布式隊列等功能。 SpringCloud 分布式配置 SpringCloud 分布式配置 史上最簡單的 SpringCloud 教程 | 第九篇: 服務(wù)鏈路追蹤 (Spring Cloud Sleuth) 史上最簡單的 S...
閱讀 2396·2021-09-30 09:47
閱讀 1381·2021-09-28 09:35
閱讀 3260·2021-09-22 15:57
閱讀 2504·2021-09-22 14:59
閱讀 3653·2021-09-07 10:25
閱讀 3085·2021-09-03 10:48
閱讀 3048·2021-08-26 14:14
閱讀 952·2019-08-30 15:55