Angular4學習筆記(三)- 路由

路由簡介

路由是 Angular 應用程序的核心,它加載與所請求路由相關聯的組件,以及獲取特定路由的相關數據。這容許咱們經過控制不一樣的路由,獲取不一樣的數據,從而渲染不一樣的頁面。css

相關的類

Routes

Routes實際上是一個Route類的數組。html

Route的參數以下圖所示,通常狀況下,pathcomponent是必選的兩個參數。
好比:path:/a,component:A則說明,當地址爲/a時,應該展現組件A的內容。node

其他類的簡介見下圖:jquery

應用

新建項目

輸入命令ng new router --routing新建一個名叫router的項目,其中--routing命令參數表明在項目基礎上添加一個路由配置文件app-routingcodule.tstypescript

能夠看到路由配置文件已經生成,其初始內容是:npm

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [];

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

新增組件

在項目根路徑下運行命令ng g component homeng g component books來新增homebooks兩個組件,此時可能會出現too many symbolic links encountered的錯誤:數組

解決辦法:app

  1. 刪除根目錄下的node_modules目錄
  2. 更新cli命令行工具npm install -g @angular/cli
  3. 從新安裝:npm install

配置路由

在路由配置文件中爲routes賦值:dom

const routes: Routes = [{path: '', component: HomeComponent}, {path: 'books', component: BooksComponent}];

其中HomeComponentBooksComponent分別對應咱們新增的兩個組件。
另外須要注意path參數中不須要輸入/iphone

定義模板

打開app.component.html,輸入一下代碼:

<a [routerLink]="['/']">主頁</a>
<a [routerLink]="['/books']">書籍</a>

<router-outlet></router-outlet>

啓動項目就能夠看到效果了:

使用Router

要使用Router對象,首先須要在控制器文件中定義它,通常是在構造函數constructor中傳遞相關參數:

constructor(private router: Router) {
  }

這樣就能夠在控制器中使用router來跳轉路由了,好比寫個方法toBookDetails

toBookDetails() {
    router.navigate(['/books']);
}

如何調用呢,在模板文件中經過定義一個按鈕並用(click)來綁定便可:

<input type="button" value="書籍" (click)="toBookDetails()">

效果以下:

通配符

以上咱們都假設輸入的路徑都是存在的,可是假如用戶不當心輸入了一個不存在的路徑,咱們有義務給予一些善意的提醒,如何實現呢?這就要使用通配符了,其用法以下:
首先建立一個新增模塊err404模塊,用於輸入不存在的路徑時展現,命令以下:
ng g component err404;
而後在路由配置文件中添加一條路由信息:

const routes: Routes = [
  {path: '', component: HomeComponent},
  {path: 'books', component: BooksComponent},
  {path: '**', component: Err404Component}
];

程序會根據用戶定義的路由順序依次匹配,當全部明確指定的路由均不知足匹配條件時,纔會進入Err404Component組件,因爲是依次匹配,因此將其放入最後。
最後,輸入http://localhost:4200/test,結果以下:

參數傳遞

參數傳遞的幾種方式:

  1. 普通方式傳遞數據:/product?id=1&name=iphone => ActivatedRoute.queryParams[id];
  2. rest方式傳遞數據:{path:/product/:id} => /product/1 => ActivatedRoute.params[id];
  3. 路由配置傳遞數據:{path:/product,component:ProductComponent,data:[{madeInChina:true}]} => ActivatedRoute.data[0][madeInChina];

注入ActivatedRoute

與注入Router對象同樣,將其置於組件控制器的構造函數中,此處以BooksComponent爲例併爲其增長一個bookname的屬性,讓其在初始化時直接顯示屬性bookname的值:

import {Component, OnInit} from '@angular/core';
import {ActivatedRoute, Params} from '@angular/router';

@Component({
  selector: 'app-books',
  templateUrl: './books.component.html',
  styleUrls: ['./books.component.css']
})
export class BooksComponent implements OnInit {
  private bookname: string;

  constructor(private activatedRout: ActivatedRoute) {
  }

  ngOnInit() {
    // 普通方式傳參的快照獲取方式
    this.bookname = this.activatedRout.snapshot.queryParams['bookname'];
  }

}

其中snapshot表示快照方式,還可使用訂閱subscribe方式,下面再說。

普通方式傳參

在模板文件app.component.html中爲/books所在的<a>標籤中添加屬性[queryParams]:

<a [routerLink]="['/books']" [queryParams]="{bookname:'《活着》'}">書籍</a>

此時點擊顯示的路由信息則爲:

爲了更明確起見,在books組件的模板文件中添加綁定信息:

最終呈現效果:

rest方式傳參

首先須要在路由配置文件中預約義參數的名稱:

而後在<a>中直接傳遞值:

<a [routerLink]="['/books','《活着》']">書籍</a>

此時點擊連接時所呈現的路徑變爲:

最後,經過params方法來獲取參數的值:

// rest方式傳參的快照獲取方式
    this.bookname = this.activatedRout.snapshot.params['bookname'];

這樣就能夠呈現同上面同樣的效果的內容了。那麼,如何經過方法來傳遞rest形式的參數呢?其實同<a>的格式同樣:

toBookDetails() {
    this.router.navigate(['/books', '《簡愛》']);
  }

此時我傳入的參數是《簡愛》,在連接主頁和連接書籍或者在連接主頁和按鈕書籍之間切換點擊時,內容的顯示是沒有問題的,可是在連接書籍和按鈕書籍之間切換點擊時,底下展現的內容不發生變化,這時就要用到參數訂閱的方式來實時呈現了:

// rest方式傳參的訂閱獲取方式
this.activatedRout.params.subscribe((params: Params) => this.bookname = params['bookname']);

路由配置傳參

這種傳參是靜態的,假設此時須要添加一個參數isChineseVersion來表示是不是中文版,其配置形式以下:

此時在book組件控制器中新增一個boolean類型參數isChineseVersion,並在初始化方法ngOnInit中爲其賦值:
this.isChineseVersion = this.activatedRout.snapshot.data[0]['isChineseVersion'];,最後在模板文件中綁定便可:

<p>
  is Chinese Version: {{isChineseVersion}}
</p>

重定向路由

在用戶訪問一個特定地址時,將其重定向到另外一個指定的地址。
此處先將全部指向HomeComponent的根路徑所有改成/home,包括路由配置文件和<a>
此時雖然指定了當路徑爲/home時才指向HomeComponent組件,但若是我但願訪問根路徑時直接展現HomeComponent的內容是怎麼辦,重定向路由能夠幫助咱們實現,語法以下:

const routes: Routes = [
  {path: '', redirectTo: '/home', pathMatch: 'full'},
  {path: 'home', component: HomeComponent},
  {path: 'books/:bookname', component: BooksComponent, data: [{isChineseVersion: true}]},
  {path: '**', component: Err404Component}
];

須要注意的是,pathMatch屬性的值,當爲full時,表示只有當路徑爲根路徑時才指向HomeComponent組件,它還有另外一個值:prefix,意思是匹配前綴,試了以後發現沒有用,好比
{path: 'hh', redirectTo: '/home', pathMatch: 'prefix'},當訪問的路徑爲http://localhost:4200/h或者http://localhost:4200/hhh時都不會呈現主頁內容而是直接跳到Err404的內容,此處存疑

子路由

子路由定義的格式以下:

上圖中子路由的意思是,當訪問/home/時,展現HomeComponentXxxComponent的內容,當訪問/home/yyy時,展現HomeComponentYyyComponent的內容。

明確需求

在書籍模塊的模板下方,增長兩個連接,《簡愛》的中文簡介和英文簡介,點擊時分別顯示對應語言的簡介,默認顯示中文簡介。

實現

  1. 生成組件introduction_cn=>ng g component introduction_cn
    introduction_en=>ng g component introduction_en
  2. 分別在對應中英文模板中添加顯示的內容,代碼略;
  3. 在路由配置文件中定義子路由:
{
    path: 'books/:bookname', component: BooksComponent, children: [
    {path: '', component: IntroductionCnComponent},
    {path: 'en', component: IntroductionEnComponent}
  ]
  }
  1. 在書籍模板中定義連接和定位顯示位置:
<p>
  books works!
</p>
<p>
  query book is name of {{bookname}}
</p>
<a [routerLink]="['./']">中文簡介</a>
<a [routerLink]="['./en']">英文簡介</a>

<router-outlet></router-outlet>

效果

多個路由

以上的例子中都是一個路由來操控頁面的顯示內容,可是實際上頁面老是有多個模塊,如何來分配各自區域的顯示控制呢?這時就要用到多路由了,好比在上面的例子中,在主界面上只定義了一個<router-outlet></router-outlet>,當咱們定義多個路由時就須要加以區分,實現方式很簡單,在<router-outlet>上加一個name屬性並賦值便可,好比:<router-outlet name = "a"></router-outlet>,此時a就是此路由定位器的標識,當不定義name屬性時,其名稱默認爲primary

下面經過實際例子作個簡單演示

  • 需求
    在原有基礎上添加兩個連接開始諮詢結束諮詢,當點擊開始諮詢時頁面右側顯示文本域,同時左側顯示HomeComponent組件內容,當點擊結束諮詢時文本域消失,頁面左側仍然顯示原有內容。
  • 爲原有組件添加樣式,使其寬度變爲70%,而且靠頁面左邊,此例中涉及的組件有HomeComponent/BooksComponent,以主頁組件爲例演示:

使用div元素將原有內容包裹,並定義class值爲home

<div class="home">
  <p>
    home works!
  </p>
</div>

定義樣式:

.home {
  background-color: yellowgreen;
  height: 750px;
  width: 70%;
  float: left;
  box-sizing: border-box;
}

同理定義BooksComponent模板及樣式文件。

  • 新增諮詢組件
    ng g component advise
  • 定義模板
<textarea placeholder="請輸入內容" class="advise"></textarea>
  • 定義樣式
.advise {
  background-color: green;
  height: 750px;
  width: 30%;
  float: left;
  box-sizing: border-box;
}
  • 新增路由advise

在路由配置文件中新增路由並制定定位器:

  • 新增開始諮詢連接並指定定位器
    指定定位器仍然經過routerLink,格式:<a [routerLink]="[{outlets:{primary:'home', advise:'advise'}}]">開始諮詢</a>
    表示點擊連接時,沒有名字(前面說了,它的默認名字叫primary)的定位器處顯示路徑爲/home即組件是HomeComponent的內容,而名叫advise定位器處顯示路徑爲/advise即組件是AdviseComponent的內容。
    同理,定義結束諮詢的:<a [routerLink]="[{outlets:{advise:null}}]">書籍</a>

  • 添加名叫advise的定位器

  • 效果

路由守衛

路由守衛通常涉及三個類:CanActivate,CanDeactivate,Resolve.
CanActivate:決定是否有權限進入某路由;
CanDeactivate:決定是否能夠離開某路由;
Resolve:初始化某個組件中的被Resolve綁定的類型參數;

CanActivate用法

新建ts文件:PermissionGuard.ts

import {ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot} from '@angular/router';
import {Observable} from 'rxjs/Observable';
import * as _ from 'lodash';
export class PermissionGuard implements CanActivate {
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable<boolean> | Promise<boolean> {
    const rand = _.random(0, 5);
    console.log(rand);
    return rand % 2 === 0;
  }
}

在路由配置文件中增長canActivate參數:

const routes: Routes = [
  {path: '', redirectTo: '/home', pathMatch: 'full'},
  {path: 'home', component: HomeComponent},
  {path: 'advise', component: AdviseComponent, outlet: 'advise'},
  /*{path: 'books/:bookname', component: BooksComponent, data: [{isChineseVersion: true}]},*/
  {
    path: 'books/:bookname',
    component: BooksComponent,
    children: [
      {path: '', component: IntroductionCnComponent},
      {path: 'en', component: IntroductionEnComponent}
    ],
    canActivate: [PermissionGuard]
  },
  {path: '**', component: Err404Component}
];

在模塊配置文件app.moudle.ts中的providers參數中增長實現了CanActivate接口的值:

providers: [PermissionGuard]

目的是實例化PermissionGuard

此時,再次點擊連接書籍時會先判斷生成的隨機數除2時的餘數,餘0則可進入,不然被阻止。

CanDeactivate用法

需求:
當諮詢窗口已被打開,而且已經輸入了內容,此時忽然點擊結束,會被阻止,相似博客編輯到一半忽然訪問其餘網站時會給出提示同樣。

新建ts文件LeaveGuard.ts

import {CanDeactivate} from '@angular/router';
import {AdviseComponent} from '../advise/advise.component';
import {Observable} from 'rxjs/Observable';
export class LeaveGuard implements CanDeactivate<AdviseComponent> {
  /**
   * 下面這段代碼是自帶實現,暫時注掉不用它。
   * canDeactivate(component: AdviseComponent,
   * currentRoute: ActivatedRouteSnapshot,
   * currentState: RouterStateSnapshot,
   * nextState?: RouterStateSnapshot)
   * : boolean | Observable<boolean> | Promise<boolean> {
   *    throw new Error("Method not implemented.");
   * }
   * @param advise
   * @returns {boolean}
   */
  canDeactivate(advise: AdviseComponent): boolean | Observable<boolean> | Promise<boolean> {
    return advise.isEmpty();
  }
}

能夠看到CanDeactivate不一樣於CanActivate,使用時須要輸入泛型類,用以綁定數據。代碼中使用了isEmpty(),它是諮詢組件AdviseComponent中的一個方法,表示當輸入的諮詢內容爲空時返回真,代碼以下:

import {Component, OnInit} from '@angular/core';
import * as $ from 'jquery';
@Component({
  selector: 'app-advise',
  templateUrl: './advise.component.html',
  styleUrls: ['./advise.component.css']
})
export class AdviseComponent implements OnInit {

  constructor() {
  }

  ngOnInit() {
  }

  isEmpty(): boolean {
    const adviseVal = $('textarea').val();
    console.log(adviseVal);
    return adviseVal === '';
  }

}

在路由配置文件中爲組件AdviseComponent添加canDeactivate屬性並賦值:

{
    path: 'advise',
    component: AdviseComponent,
    outlet: 'advise',
    canDeactivate: [LeaveGuard]
  }

在模塊文件app.module.ts參數providers新增LeaveGuard用以實例化:

providers: [PermissionGuard, LeaveGuard]

Resolve

爲方便演示,先將路由配置文件中關於BookComponentcanActivate參數注掉,防止干擾。

books.componnet.ts中新建類Book,將屬性bookname替換名爲book類型爲Book的成員變量,添加對對此成員變量的訂閱方法:

import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';

@Component({
  selector: 'app-books',
  templateUrl: './books.component.html',
  styleUrls: ['./books.component.css']
})
export class BooksComponent implements OnInit {
  public book: Book;
  private isChineseVersion: boolean;

  constructor(private activatedRout: ActivatedRoute) {
  }

  ngOnInit() {
    // 普通方式傳參的快照獲取方式
    // this.bookname = this.activatedRout.snapshot.queryParams['bookname'];

    // rest方式傳參的快照獲取方式
    // this.bookname = this.activatedRout.snapshot.params['bookname'];
    // rest方式傳參的訂閱獲取方式
    // this.activatedRout.params.subscribe((params: Params) => this.book.bookname = params['bookname']);
    this.activatedRout.data.subscribe((data: { book: Book }) => this.book = data.book);
    // 路由配置方式傳參的獲取方式
    // this.isChineseVersion = this.activatedRout.snapshot.data[0]['isChineseVersion'];
  }

}

export class Book {
  public bookname: string;
  public author?: string;
  public private?: number;

  constructor(bookname: string) {
    this.bookname = bookname;
  }
}

修改books.component.htmlquery book is name of {{bookname}} => query book is name of {{book?.bookname}}

新建BookResolve.ts:

import {ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot} from '@angular/router';
import {Book} from '../books/books.component';
import {Observable} from 'rxjs/Observable';
import {Injectable} from '@angular/core';

@Injectable()
export class BookResolve implements Resolve<Book> {
  constructor(private router: Router) {
  }

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Book | Observable<Book> | Promise<Book> {
    const bookname = route.params['bookname'];
    if (bookname === '《活着》' || bookname === '《簡愛》') {
      console.log('當前輸入書名爲:' + bookname);
      return new Book('《簡愛》');
    } else {
      this.router.navigate(['/home']);
      return null;
    }
  }
}

在路由控制文件中爲組件BooksComponent添加resolve參數並賦值:resolve: {book: BookResolve}

在模塊文件app.module.ts參數providers新增BookResolve用以實例化:

providers: [PermissionGuard, LeaveGuard, BookResolve]

總結

源碼

http://pan.baidu.com/s/1bpNdgFt

使用前先運行ci.bat~

相關文章
相關標籤/搜索