angular+ 路由学习 (七)路由守卫--resolver和canDeactivate

  • resolver 可用来预先获取 将要导航去某路由之前 目的路由的数据,以便在组件渲染后,将数据显示出来,防止异步延迟带来的无数据用户体验
  • 这里接着官网的案例;
    ng g service crisis-center/crisis-detail-resolver // 创建resolver守卫服务
    
    // crisis-detail-resolver.service.ts 
    import { Injectable } from '@angular/core';
    import {
      Router, Resolve,
      RouterStateSnapshot,
      ActivatedRouteSnapshot
    } from '@angular/router';
    import { Observable, of, EMPTY } from 'rxjs';
    import { mergeMap, take } from 'rxjs/operators';
    
    import { CrisisService } from './crisis.service';
    import { Crisis } from './crisis';
    
    @Injectable({
      providedIn: 'root',
    })
    export class CrisisDetailResolverService implements Resolve<Crisis> {
      constructor(private cs: CrisisService, private router: Router) { }
    // 在导航到详情路由前,就将数据初始化以保证数据预先加载,提高用户体验(防止数据延迟导致无法即时显示)
    resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Crisis> | Observable<never> {
      let id = route.paramMap.get('id');
    
      return this.cs.getCrisis(id).pipe(
        take(1), // 确保这个可观察对象会从getCrisis()中取到第一个值就结束
        mergeMap(crisis => {
          if (crisis) {
            return of(crisis);
          } else { // id not found
            this.router.navigate(['/crisis-center']);
            return EMPTY;
          }
        })
      );
    }
      
    }

     

  • 将该服务导入到该模块路由中(crisis-center-routing.module.ts)
    // ....
    import { CrisisDetailResolverService } from './crisis-detail-resolver.service';
    
    const crisisCenterRoutes: Routes = [
      {
        path: 'crisis-center', component: CrisisCenterComponent, children: [
          { path: '', component: CrisisListComponent, children: [
            { path: ':id', component: CrisisDetailComponent, resolve: { crisis: CrisisDetailResolverService }},
            { path: '', component: CrisisCenterHomeComponent}
          ]}
        ]
      }
    ];
    
    // ...

     

  • 在crisis-detail.component.ts 中;因为有了这个预先获取数据的服务;只需要从这个路由的data中获取数据(ActivateRoute.data 里包含了预先处理的解析数据)
    import { Component, OnInit, Input } from '@angular/core';
    import { Router, ActivatedRoute, ParamMap } from '@angular/router'; // 导入类
    import { Observable } from 'rxjs';
    
    import { Crisis } from '../crisis';
    
    
    @Component({
      selector: 'app-crisis-detail',
      templateUrl: './crisis-detail.component.html',
      styleUrls: ['./crisis-detail.component.css']
    })
    export class CrisisDetailComponent implements OnInit {
    
      crisis: Crisis;
      editName: string;
      // 服务注入
      constructor(
        private router: Router,
        private activatedRoute: ActivatedRoute
      ) { }
    
      ngOnInit() { 
        // ActivatedRoute 的 data; 它是一个Observable对象,包含解析守卫resolve 解析的值;
        this.activatedRoute.data.subscribe((data: { crisis: Crisis }) => {
          console.log(data);
          this.editName = data.crisis.name;
          this.crisis = data.crisis;
        })
    
      }
      // 使用相对路径导航,需要提供当前的ActivatedRoute,让路由器知道当前处于路由树的位置
      // 使用: 链接参数数组后,添加relativeTo属性,并设置为当前的ActivatedRoute
      gotoCrises() {
        let crisisId = this.crisis ? this.crisis.id : null;
        this.router.navigate(['../', { id: crisisId, foo: 'foo' }], { relativeTo: this.activatedRoute }); // 相对路由跳转;
      }
      save() {
        this.crisis.name = this.editName;
        this.gotoCrises();
      }
      cancel() {
        this.gotoCrises();
      }
    
    }
    <!-- // crisis-detail.html  -->
    <h2>crisisES</h2>
    
    <div *ngIf="crisis">
        <h3>"{{ editName }}"</h3>
        <div>
          <label>Id: </label>{{ crisis.id }}</div>
        <div>
          <label>Name: </label>
          <input [(ngModel)]="editName" placeholder="name"/>
        </div>
        <p>
          <button (click)="save()">Save</button>
          <button (click)="cancel()">Cancel</button>
        </p>
      </div>
      

     

  • 现在可以对详情进行更改操作,但是如果我们离开这个页面,未点击保存事件的值将不会被保存,此时需要提供一个处理未保存的更改功能么,以提高用户体验;
  • CanDeactivate 守卫 可以对这个需求提供解决方法;
  • ng generate service dialog // 创建一个对话框服务,该服务处理用户确认或取消操作
    
    // 内容
    import { Injectable } from '@angular/core';
    import { Observable, of } from 'rxjs';
    @Injectable({
      providedIn: 'root'
    })
    export class DialogService {
    
      constructor() { }
      // 显示对话框,等待用户操作,返回observable ,当用户最终决定后,它会被解析;
      confirm(message?: string): Observable<boolean> {
        const confirmation = window.confirm(message || 'Is it OK?');
    
        return of(confirmation);
      }
    }

     

  • 创建一个守卫,以检查组件是否存在canDeactivate()方法,因为守卫可 不需要知道 (任意)组件确认退出的激活状态, 但是它只需要检查该组件是否有一个canDeactivate()方法,并调用这个方法即可;
    ng generate guard can-deactivate
    
    // 内容
    import { Injectable } from '@angular/core';
    import { CanDeactivate } from '@angular/router';
    import { Observable } from 'rxjs';
    
    export interface CanComponentDeactivate {
      canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
    }
    
    @Injectable({
      providedIn: 'root',
    })
    export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
      canDeactivate(component: CanComponentDeactivate) {
        return component.canDeactivate ? component.canDeactivate() : true;
      }
    }

     

  • 在crisis-center-routing.module.ts中配置守卫
    import { CanDeactivateGuard } from '../can-deactivate.guard';
    import { CrisisDetailResolverService } from './crisis-detail-resolver.service';
    
    
    const crisisCenterRoutes: Routes = [
      {
        path: 'crisis-center', component: CrisisCenterComponent, children: [
          { path: '', component: CrisisListComponent, children: [
            // tslint:disable-next-line: max-line-length
            { path: ':id', component: CrisisDetailComponent, canDeactivate: [CanDeactivateGuard], resolve: { crisis: CrisisDetailResolverService }},
            { path: '', component: CrisisCenterHomeComponent}
          ]}
        ]
      }
    ];

     

  • 在crisis-detail.component.ts 中使用canDeactivate
      // 处理未保存的更改确认
      // canDeactivate 方法可以同步返回,如果没有危机,或者没有未定的修改,它就立即返回 true。但是它也可以返回一个承诺(Promise)或可观察对象(Observable),路由器将等待它们被解析为真值(继续导航)或假值(留下)
      canDeactivate(): Observable<boolean> | boolean {
        if (!this.crisis || this.crisis.name === this.editName) {
          return true;
        }
        return this.dialogService.confirm('Discard changes?');
    }

     

  • 效果展示

 

posted @ 2019-07-31 10:50  抹茶奶盖xh  阅读(1246)  评论(0编辑  收藏  举报