Angular8 路由守衛原理與使用

路由守衛

守衛,顧名思義,必須知足必定的條件獲得許可方可通行,不然拒絕訪問或者重定向。Angular中路由守衛能夠藉此處理一些權限問題,一般應用中存儲了用戶登陸和用戶權限信息,遇到路由導航時會進行驗證是否能夠跳轉。css

4種守衛類型

按照觸發順序依次爲:canload(加載)、canActivate(進入)、canActivateChild(進入子路由)和canDeactivate(離開)。
一個全部守衛都是經過的守衛類:html

import { Injectable } from '@angular/core';
import {
  CanActivate,
  Router,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  CanActivateChild,
  CanLoad,
  CanDeactivate
} from '@angular/router';
import { Route } from '@angular/compiler/src/core';
import { NewsComponent } from '../component/news/news.component';


@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate, CanActivateChild, CanLoad, CanDeactivate<any> {
  constructor(
    private router: Router
  ) {

  }
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    // 權限控制邏輯如 是否登陸/擁有訪問權限
    console.log('canActivate');
    return true;
  }
  canDeactivate(
    component: NewsComponent,
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState: RouterStateSnapshot) {
    console.log('canDeactivate');
    return true;
  }
  canActivateChild() {
    // 返回false則導航將失敗/取消
    // 也能夠寫入具體的業務邏輯
    console.log('canActivateChild');
    return true;
  }
  canLoad(route: Route) {
    // 是否能夠加載路由
    console.log('canload');
    return true;
  }
}

app-routing.module.ts瀏覽器

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ErrorComponent } from './error/error.component';
import { AuthGuard } from './core/auth-guard';

const routes: Routes = [
  // 通常狀況不多須要同時寫多個守衛,若是有也是分開幾個文件(針對複雜場景,不然通常使用canActivated足夠)
  {
    path: '',
    canLoad: [AuthGuard],
    canActivate: [AuthGuard],
    canActivateChild: [
      AuthGuard
    ],
    canDeactivate: [AuthGuard],
    loadChildren: () => import('./pages/pages.module').then(m => m.PagesModule)
  },
  {
    path: 'error',
    component: ErrorComponent,
    data: {
      title: '參數錯誤或者地址不存在'
    }
  },
  {
    path: '**',
    redirectTo: 'error',
    pathMatch: 'full'
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

使用場景分析

1.canLoad

默認值爲true,代表路由是否能夠被加載,通常不會認爲控制這個守衛邏輯,99.99%狀況下,默認全部app模塊下路由均容許canLoadapp

2.canActivate

是否容許進入該路由,此場景多爲權限限制的狀況下,好比客戶未登陸的狀況下查詢某些資料頁面,在此方法中去判斷客戶是否登錄,如未登陸則強制導航到登錄頁或者提示無權限,即將返回等信息提示。dom

3.canActivateChild

是否能夠導航子路由,同一個路由不會同時設置canActivate爲true,canActivateChild爲false的狀況,此外,這個使用場景很苛刻,尤爲是懶加載路由模式下,暫時未使用到設置爲false的場景。ide

4.CanDeactivate

路由離開的時候進行觸發的守衛,使用場景比較經典,一般是某些頁面好比表單頁面填寫的內容須要保存,客戶忽然跳轉其它頁面或者瀏覽器點擊後退等改變地址的操做,能夠在守衛中增長彈窗提示用戶正在試圖離開當前頁面,數據還未保存 等提示。ui

場景模擬

登陸判斷
前期準備:login組件;配置login路由
由於login是獨立一個頁面,因此app.component.html應該只會剩餘一個路由導航this

<!-- NG-ZORRO -->
<router-outlet></router-outlet>

取而代之的是pages.component.html頁面中要加入header和footer部分變爲以下:spa

<app-header></app-header>
<div nz-row class="main">
  <div nz-col nzSpan="24">
    <router-outlet></router-outlet>
  </div>
</div>
<app-footer></app-footer>

app-routing.module.ts 中路由配置2種模式分析:code

// 非懶加載模式
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ErrorComponent } from './error/error.component';
import { AuthGuard } from './core/auth-guard';
import { LoginComponent } from './component/login/login.component';
import { PagesComponent } from './pages/pages.component';
import { IndexComponent } from './component/index/index.component';

const routes: Routes = [
  // 通常狀況不多須要同時寫多個守衛,若是有也是分開幾個文件(針對複雜場景,不然通常使用canActivated足夠)
  {
    path: '',
    canLoad: [AuthGuard],
    canActivate: [AuthGuard],
    canActivateChild: [
      AuthGuard
    ],
    canDeactivate: [AuthGuard],
    component: PagesComponent,
    children: [
      {
        path: 'index',
        component: IndexComponent
      }
      //  ...
    ]
    // loadChildren: () => import('./pages/pages.module').then(m => m.PagesModule)
  },
  {
    path: 'login',
    component: LoginComponent,
    data: {
      title: '登陸'
    }
  },
  {
    path: 'error',
    component: ErrorComponent,
    data: {
      title: '參數錯誤或者地址不存在'
    }
  },
  {
    path: '**',
    redirectTo: 'error',
    pathMatch: 'full'
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

非懶加載模式下,想要pages組件可以正常顯示切換的路由和固定頭部足部,路由只能像上述這樣配置,也就是全部組件都在app模塊中聲明,顯然不是很推薦這種模式,切換回懶加載模式:

{
    path: '',
    canLoad: [AuthGuard],
    canActivate: [AuthGuard],
    canActivateChild: [
      AuthGuard
    ],
    canDeactivate: [AuthGuard],
    loadChildren: () => import('./pages/pages.module').then(m => m.PagesModule)
  },

pages-routing.module.ts
初始模板:

const routes: Routes = [
  {
    path: '',
    redirectTo: 'index',
    pathMatch: 'full'
  },
  {
    path: 'index',
    component: IndexComponent,
    data: {
      title: '公司主頁'
    }
  },
  {
    path: 'about',
    component: AboutComponent,
    data: {
      title: '關於咱們'
    }
  },
  {
    path: 'contact',
    component: ContactComponent,
    data: {
      title: '聯繫咱們'
    }
  },
  {
    path: 'news',
    canDeactivate: [AuthGuard],
    loadChildren: () => import('../component/news/news.module').then(m => m.NewsModule)
  },
]

瀏覽器截圖:

clipboard.png
明明咱們的html寫了頭部和底部組件卻沒顯示?
路由的本質:根據配置的path路徑去加載組件或者模塊,此處咱們是懶加載了路由,根據路由模塊再去加載不一樣組件,惟獨缺乏了加載了pages組件,其實理解整個並不難,index.html中有個<app-root></app-root>,這就代表app組件被直接插入了dom中,反觀pages組件,根本不存在直接插進dom的狀況,因此這個組件根本沒被加載,驗證咱們的猜測很簡單:

export class PagesComponent implements OnInit {

  constructor() { }

  ngOnInit() {
    alert();
  }

}

通過刷新頁面,alert()窗口並無出現~,可想而知,直接經過路由模塊去加載了對應組件;其實咱們想要的效果就是以前改造前的app.component.html效果,因此路由配置要參照更改:

const routes: Routes = [
  {
    path: '',
    component: PagesComponent,
    children: [
      {
        path: '',
        redirectTo: 'index',
        pathMatch: 'full'
      },
      {
        path: 'index',
        component: IndexComponent,
        data: {
          title: '公司主頁'
        }
      },
      {
        path: 'about',
        component: AboutComponent,
        data: {
          title: '關於咱們'
        }
      },
      {
        path: 'contact',
        component: ContactComponent,
        data: {
          title: '聯繫咱們'
        }
      },
      {
        path: 'news',
        canDeactivate: [AuthGuard],
        loadChildren: () => import('../component/news/news.module').then(m => m.NewsModule)
      },
    ]
  }
];

clipboard.png
這樣寫,pages組件就被加載了,重回正題,差點回不來,咱們在登陸組件中寫了簡單的登陸邏輯:

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
  loginForm: FormGroup;
  constructor(
    private fb: FormBuilder,
    private router: Router
  ) { }

  ngOnInit() {
    this.loginForm = this.fb.group({
      loginName: ['', [Validators.required]],
      password: ['', [Validators.required]]
    });
    console.log(this.loginForm);
  }

  loginSubmit(event, value) {
    if (this.loginForm.valid) {
      window.localStorage.setItem('loginfo', JSON.stringify(this.loginForm.value));
      this.router.navigateByUrl('index');
    }
  }
}

守衛中:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    // 權限控制邏輯如 是否登陸/擁有訪問權限
    console.log('canActivate', route);
    const isLogin = window.localStorage.getItem('loginfo') ? true : false;
    if (!isLogin) {
      console.log('login');
      this.router.navigateByUrl('login');
    }
    return true;
  }

clipboard.png

路由離開(選定應用的組件是contact組件):

canDeactivate(
    component: ContactComponent,
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    console.log('canDeactivate');
    return component.pageLeave();
  }
{
    path: 'contact',
    canDeactivate: [AuthGuard],
    component: ContactComponent,
    data: {
      title: '聯繫咱們'
    }
 }
pageLeave(): Observable<boolean> {
    return new Observable(ob => {
      if (!this.isSaven) {
        this.modal.warning({
          nzTitle: '正在離開,是否須要保存改動的數據?',
          nzOnOk: () => {
            // 保存數據
            ob.next(false);
            alert('is saving');
            this.isSaven = true;
          },
          nzCancelText: '取消',
          nzOnCancel: () => {
            ob.next(true);
          }
        });
      } else {
        ob.next(true);
      }
    });
  }

默認數據狀態時未保存,能夠選擇不保存直接跳轉也能夠保存以後再跳轉。

clipboard.png
此場景多用於複雜表單頁或者一些填寫資料步驟的過程當中,甚至瀏覽器後退和前進的操做也會觸發這個守衛,惟一不足的地方時這個守衛綁定的是單一頁面,沒法統一對多個頁面進行攔截。

下一篇介紹路由事件的運用。

相關文章
相關標籤/搜索