Angular 2.x 从0到1 (五)史上最简单的Angular2教程
第一节:Angular 2.0 从0到1 (一) 第五节:多用户版本的待办事项应用第四节我们完成的Todo的基本功能看起来还不错,但是有个大问题,就是每个用户看到的都是一样的待办事项,我们希望的是每个用户拥有自己的待办事项列表。我们来分析一下怎么做,如果每个todo对象带一个UserId属性是不是可以解决呢?好像可以,逻辑大概是这样:用户登录后转到/todo,TodoComponent得到当前用户的UserId,然后调用TodoService中的方法,传入当前用户的UserId,TodoService中按UserId去筛选当前用户的Todos。 数据驱动开发按之前我们分析的,给todo加一个userId属性,我们手动给我们目前的数据加上userId属性吧。更改 { "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",{ "id": "0b1f6614-1def-3346-f070-d6d39c02d6b7","desc": "test",{ "id": "c1e02a43-6364-5515-1652-a772f0fab7b3","desc": "This is a te","userId": 1 } ] } 如果你还没有启动json-server的话让我们启动它: [ { "id": "5894a12f-dae1-5ab0-5761-1371ba4f703e","userId": 2 },{ "id": "0b1f6614-1def-3346-f070-d6d39c02d6b7","userId": 2 } ] 有兴趣的话可以再试试 { "id": 1,"username": "wang","password": "1234" } 当然这个表现形式有很多问题,比如密码是明文的,这些问题我们先不管,但大概样子是类似的。那么现在如果要建立User数据库的话,我们应该新建一个 { "users": [ { "id": 1,"password": "1234" },{ "id": 2,"username": "peng","password": "5678" } ] } 但这样做的话感觉单独为其建一个文件有点不值得,我们干脆把user和todo数据都放在一个文件吧,现在删除 //srcappdata.json { "todos": [ { "id": "bf75769b-4810-64e9-d154-418ff2dbf55e","userId": 1 } ],"users": [ { "id": 1,"password": "5678" } ] } 当然有了数据,我们就得有对应的对象,基于同样的理由,我们把所有的entity对象都放在一个文件:删除 export class Todo { id: string; desc: string; completed: boolean; userId: number; } export class User { id: number; username: string; password: string; } 验证用户账户的流程我们来梳理一下用户验证的流程
看上去我们需要实现
核心模块根据这个逻辑流程,我们来组织一下代码。开始之前我们想把认证相关的代码组织在一个新的模块下,我们暂时叫它 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'); } } 注意到这个模块和其他模块不太一样,原因是我们希望只在应用启动时导入它一次,而不会在其它地方导入它。在模块的构造函数中我们会要求Angular把CoreModule注入自身,这看起来像一个危险的循环注入。不过, 路由守卫首先我们来看看Angular内建的路由守卫机制,在实际工作中我们常常会碰到下列需求:
我们可以往路由配置中添加守卫,来处理这些场景。守卫返回 路由器支持多种守卫:
在分层路由的每个级别上,我们都可以设置多个守卫。路由器会先按照从最深的子路由由下往上检查的顺序来检查 本例中我们希望用户未登录前不能访问todo,那么需要使用 import { AuthGuardService } from '../core/auth-guard.service'; const routes: Routes = [ { path: 'todo/:filter',canActivate: [AuthGuardService],component: TodoComponent } ]; 当然光这么写是没有用的,下面我们来建立一个 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 { //如果用户已经登录就放行 if (localStorage.getItem('userId') !== null) { return true; } //否则,存储要访问的URl到本地 localStorage.setItem('redirectUrl',url); //然后导航到登陆页面 this.router.navigate(['/login']); //返回false,取消导航 return false; } } 观察上面代码,我们发现本地存储的userId的存在与否决定了用户是否已登录的状态,这当然是一个漏洞百出的实现,但我们暂且不去管它。现在我们要在登录时把这个状态值写进去。我们新建一个登录鉴权的 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<Auth> { 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<any> { console.error('An error occurred',error); // for demo purposes only return Promise.reject(error.message || error); } } 注意到我们返回了一个Auth对象,这是因为我们要知道几件事:
这个Auth对象同样在 export class Auth { user: User; hasError: boolean; errMsg: string; redirectUrl: string; } 当然我们还得实现UserService: import { Injectable } from '@angular/core'; import { Http,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<User> { 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<any> { console.error('An error occurred',error); // for demo purposes only return Promise.reject(error.message || error); } } 这段代码比较简单,就不细讲了。下面我们改造一下 <div *ngIf="usernameRef.errors?.required">this is required</div> <div *ngIf="usernameRef.errors?.minlength">should be at least 3 charactors</div> <!--add the code below--> <div *ngIf="auth?.hasError">{{auth.errMsg}}</div> 当然我们还得改造 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模块中声明我们的服务 import { ModuleWithProviders,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'); } } } 最后我们得改写一下 //todo.service.ts代码片段 // POST /todos addTodo(desc:string): Promise<Todo> { //“+”是一个简易方法可以把string转成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<Todo[]>{ 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<Todo[]> { 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(); } } 现在应该已经ok了,我们来看看效果: 路由模块化Angular团队推荐把路由模块化,这样便于使业务逻辑和路由松耦合。虽然目前在我们的应用中感觉用处不大,但按官方推荐的方式还是和大家一起改造一下吧。删掉原有的 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 {} 以及 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',component: TodoComponent } ]; @NgModule({ imports: [ RouterModule.forChild(routes) ],exports: [ RouterModule ] }) export class TodoRoutingModule { } 并分别在AppModule和TodoModule中引入路由模块。 用VSCode进行调试有读者问如何用vscode进行debug,这章我们来介绍一下。首先需要安装一个vscode插件,点击左侧最下面的图标或者“在查看菜单中选择命令面板,输入install,选择扩展:安装扩展”,然后输入“debugger for chrome”回车,点击安装即可。 { "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:tempchromeDummyDir","sourceMapPathOverrides": { "webpack:///C:*":"C:/*" //use "webpack:///*": "/*" on Linux/OSX } },{ "name": "Attach to Chrome,"request": "attach","port": 9222,"sourceMapPathOverrides": { "webpack:///C:*":"C:/*" } } ] } 现在你可以试着在源码中设置一个断点,点击debug视图中的debug按钮,可以尝试右键点击变量把它放到监视中看看变量值或者逐步调试应用。 本章完整代码见: https://github.com/wpcfan/awe... 第一节:Angular 2.0 从0到1 (一) (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |