Angular4學習筆記(三)- 路由


路由簡介

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

相關的類

Routes

Routes其實是一個Route類的數組。

Route的參數如下圖所示,一般情況下,pathcomponent是必選的兩個參數。
比如:path:/a,component:A則說明,當地址為/a時,應該展示組件A的內容。

其余類的簡介見下圖:

應用

新建項目

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

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

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的錯誤:

解決辦法:

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

配置路由

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

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

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

定義模板

打開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~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM