1. 程式人生 > >【6】 angular 路由基礎知識_在路由時傳遞資料_重定向路由_子路由_輔助路由_路由守衛

【6】 angular 路由基礎知識_在路由時傳遞資料_重定向路由_子路由_輔助路由_路由守衛

路由基礎知識

  路由相關物件介紹

 

新建一個專案

ng new router --routing

並使用webstrom 開啟 

發現--routing 引數建立後 會多出一個檔案app-routing.module.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 { }

app.module.ts 也會匯入這個模組

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.html 下面也會多一個插座

<router-outlet></router-outlet>

為了展示功能 先建立兩個元件

ng g component home

ng g component product

修改 home.component.html

<p>
  這裡是主頁元件
</p>

product.component.html

<p>
  這是是商品資訊元件
</p>

修改 app.component.html

<!--The content below is only a placeholder and can be replaced.-->
<a [routerLink]="['/']">主頁</a>
<a [routerLink]="['/product']">商品詳情</a>

<router-outlet></router-outlet>

執行檢視效果

加個按鈕  使用控制器跳轉

<!--The content below is only a placeholder and can be replaced.-->
<a [routerLink]="['/']">主頁</a>
<a [routerLink]="['/product']">商品詳情</a>
<button type="button" value="商品詳情" (click)="toProDuctDetails()" >商品詳情</button>
<router-outlet></router-outlet>

修改 app.component.ts

import {Component} from '@angular/core';
import {Router} from '@angular/router';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'router';

  constructor(private router: Router) {
  }

  toProDuctDetails() {
    this.router.navigate(['/product']);
  }
}

效果同routerLink

當用戶使用不存在的路徑

建立一個404元件

ng g component code404

修改 code404.component.html

<p>
  頁面不存在
</p>

修改 app-routing.module.ts

import {NgModule} from '@angular/core';
import {Routes, RouterModule} from '@angular/router';
import {HomeComponent} from './home/home.component';
import {ProductComponent} from './product/product.component';
import {Code404Component} from './code404/code404.component';

const routes: Routes = [
  {path: '', component: HomeComponent},
  {path: 'product', component: ProductComponent},
  {path: '**', component: Code404Component},
];

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

也能出現錯誤介面

在路由時傳遞資料

在查詢引數時傳遞資料

/product?id=1&name=2   >>  ActivatedRoute.queryParams[id]

在路由的路徑中傳遞資料

{path:/product/:id} >>  /product/1  ActivatedRoute.params[id]

在路由配置中傳遞資料

 {path:/product,comonent:ProductCompomemt,data[{isProd:true}]} >> ActivatedRoute.data[0][isProd]

下面我們繼續修改demo 檢視效果

 app.component.html

<!--The content below is only a placeholder and can be replaced.-->
<a [routerLink]="['/']">主頁</a>
<a [routerLink]="['/product']" [queryParams]="{id:1}">商品詳情</a>
<button type="button" value="商品詳情" (click)="toProDuctDetails()" >商品詳情</button>
<router-outlet></router-outlet>

product.component.ts

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

@Component({
  selector: 'app-product',
  templateUrl: './product.component.html',
  styleUrls: ['./product.component.css']
})
export class ProductComponent implements OnInit {
  public productId: number;

  constructor(private routeInfo: ActivatedRoute) {
  }

  ngOnInit() {
    this.productId = this.routeInfo.snapshot.queryParams['id'];
  }

}
<p>
  這是是商品資訊元件
</p>
<p>商品Id是{{productId}}</p>

下面第二種方式

app.component.html

<!--The content below is only a placeholder and can be replaced.-->
<a [routerLink]="['/']">主頁</a>
<a [routerLink]="['/product',1]" >商品詳情</a>
<button type="button" value="商品詳情" (click)="toProDuctDetails()" >商品詳情</button>
<router-outlet></router-outlet>

app-routing.module.ts

import {NgModule} from '@angular/core';
import {Routes, RouterModule} from '@angular/router';
import {HomeComponent} from './home/home.component';
import {ProductComponent} from './product/product.component';
import {Code404Component} from './code404/code404.component';

const routes: Routes = [
  {path: '', component: HomeComponent},
  {path: 'product/:id', component: ProductComponent},
  {path: '**', component: Code404Component},
];

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

product.component.ts

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

@Component({
  selector: 'app-product',
  templateUrl: './product.component.html',
  styleUrls: ['./product.component.css']
})
export class ProductComponent implements OnInit {
  public productId: number;

  constructor(private routeInfo: ActivatedRoute) {
  }

  ngOnInit() {
    this.productId = this.routeInfo.snapshot.params['id'];
  }

}

在寫第三種方式的時候  我們要明確兩個概念  引數快照 和引數訂閱

修改 app.component.ts

import {Component} from '@angular/core';
import {Router} from '@angular/router';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'router';

  constructor(private router: Router) {
  }

  toProDuctDetails() {
    this.router.navigate(['/product', 2]);
  }
}

然後操作 網頁

上面 主頁  商品詳情(p)  商品詳情(button)  我們 稱為1 2 3

點選 1  點選 3  效果正常 

點選 1 點選2  效果正常

點選 1 點選2 點選3 效果如上圖所示

這是因為product.component.ts中的

  ngOnInit() {
    this.productId = this.routeInfo.snapshot.params['id'];
  }

使用了引數快照  

我們改成引數訂閱試一下

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

@Component({
  selector: 'app-product',
  templateUrl: './product.component.html',
  styleUrls: ['./product.component.css']
})
export class ProductComponent implements OnInit {
  public productId: number;

  constructor(private routeInfo: ActivatedRoute) {
  }

  ngOnInit() {
    this.routeInfo.params.subscribe((params: Params) => this.productId = params['id']);
  }

}

 我們會發現 即使 點選 1 點選2 點選3 就可以了 效果就沒問題了

重定向路由

在使用者訪問一個特定的地址時,將其重定向到另一個指定的地址、

www.aaa.com  >> www.aaa.com/products
www.aaa.com/x >> www.aaa.com/y

修改路由配置

import {NgModule} from '@angular/core';
import {Routes, RouterModule} from '@angular/router';
import {HomeComponent} from './home/home.component';
import {ProductComponent} from './product/product.component';
import {Code404Component} from './code404/code404.component';

const routes: Routes = [
  {path: '', redirectTo: '/home', pathMatch: 'full'},
  {path: 'home', component: HomeComponent},
  {path: 'product/:id', component: ProductComponent},
  {path: '**', component: Code404Component},
];

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

子路由

{path:'home',component:HomeComponent}

 -----------------------------------------------------------------

{path:'home',component:HomeComponent,
 children:[

{path:'',component:XxxComponent,},
{path:'/yyy' compenent:YyyComponent}
]
}

 當訪問home 路徑的時候 還是會展現Home元件的模版 但是 

router-outlet會顯示Xxx組價模版

同理 訪問home/yyy的時候 還是會展現Home元件的模版 但是 

router-outlet會顯示Yyy組價模版

現在我們來一步一步來實現子路由

新建兩個元件

ng g component productDesc

ng g component sellerInfo

app-routing.module.ts

import {NgModule} from '@angular/core';
import {Routes, RouterModule} from '@angular/router';
import {HomeComponent} from './home/home.component';
import {ProductComponent} from './product/product.component';
import {Code404Component} from './code404/code404.component';
import {ProductDescComponent} from './product-desc/product-desc.component';
import {SellerInfoComponent} from './seller-info/seller-info.component';

const routes: Routes = [
  {path: '', redirectTo: '/home', pathMatch: 'full'},
  {path: 'home', component: HomeComponent},
  {
    path: 'product/:id', component: ProductComponent,
    children: [
      {path: '', component: ProductDescComponent},
      {path: 'seller/:id', component: SellerInfoComponent},
    ]
  },
  {path: '**', component: Code404Component},
];

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

seller-info.component.ts

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

@Component({
  selector: 'app-seller-info',
  templateUrl: './seller-info.component.html',
  styleUrls: ['./seller-info.component.css']
})
export class SellerInfoComponent implements OnInit {
  public sellerId: number;

  constructor(private routeInfo: ActivatedRoute) {
  }

  ngOnInit() {
    this.sellerId = this.routeInfo.snapshot.params['id'];
  }

}

seller-info.component.html

<p>
  銷售員ID是{{sellerId}}
</p>

product-desc.component.html

<p>
  這是一個牛叉的商品
</p>

product.component.html

<p>
  這是是商品資訊元件
</p>
<p>商品Id是{{productId}}</p>
<a href="#" [routerLink]="['./']">商品描述</a>
<a href="#" [routerLink]="['./seller',99]">銷售員資訊</a>
<router-outlet></router-outlet>

 子路由不僅僅可以巢狀兩層 隨著需求的複雜 可以無限往下巢狀

輔助路由

配置分為三步
<router-outlet></router-outlet>
<router-outlet name="aux"></router-outlet>

{path: 'xxx',component: XxxComponent,outlet: 'aux'}
{path: 'yyy',component: YxxComponent,outlet: 'aux'}


<a[routerLink]="['/home',{outles:{aux: 'xxx'}}]">Xxx</a>
<a[routerLink]="['/product',{outles:{aux: 'yyy'}}]">Yyy</a>

輔助路由案例 整體思路

在app 元件的模板上 再定義一個插座來顯示聊天面板

單獨開發一個聊天的外掛,只顯示在新定義的插座上

通過路由引數控制新插座是否顯示聊天面板

ng g component chat

chat.component.html

<textarea placeholder="請輸入聊天內容" class="chat"></textarea>
.chat{
  background: aquamarine;
  height: 100px;
  width: 30%;
  float: left;
  box-sizing: border-box;
}

home.component.html

.home{
  background: red;
  height: 100px;
  width: 70%;
  float: left;
  box-sizing: border-box;
}

product.component.html

<div class="product">
  <p>
    這是是商品資訊元件
  </p>
  <p>商品Id是{{productId}}</p>
  <a href="#" [routerLink]="['./']">商品描述</a>
  <a href="#" [routerLink]="['./seller',99]">銷售員資訊</a>
  <router-outlet></router-outlet>

</div>

product.component.css

.product{
  background: yellow;
  height: 100px;
  width: 70%;
  float: left;
  box-sizing: border-box;
}

app-routing.module.ts

import {NgModule} from '@angular/core';
import {Routes, RouterModule} from '@angular/router';
import {HomeComponent} from './home/home.component';
import {ProductComponent} from './product/product.component';
import {Code404Component} from './code404/code404.component';
import {ProductDescComponent} from './product-desc/product-desc.component';
import {SellerInfoComponent} from './seller-info/seller-info.component';
import {ChatComponent} from './chat/chat.component';

const routes: Routes = [
  {path: '', redirectTo: '/home', pathMatch: 'full'},
  {path: 'chat', component: ChatComponent, outlet: 'aux'},
  {path: 'home', component: HomeComponent},
  {
    path: 'product/:id', component: ProductComponent,
    children: [
      {path: '', component: ProductDescComponent},
      {path: 'seller/:id', component: SellerInfoComponent},
    ]
  },
  {path: '**', component: Code404Component},
];

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

app.component.html

<!--The content below is only a placeholder and can be replaced.-->
<a [routerLink]="['/']">主頁</a>
<a [routerLink]="['/product',1]">商品詳情</a>
<button type="button" value="商品詳情" (click)="toProDuctDetails()">商品詳情</button>

<a [routerLink]="[{outlets:{ primary: 'home',aux:'chat'}}]">開始聊天</a>
<a [routerLink]="[{outlets:{aux:null}}]">結束聊天</a>

<p>
  <router-outlet></router-outlet>
  <router-outlet name="aux"></router-outlet>
</p>

其中 primary:'home'  是為了 不論每次切換到chat元件時候 主頁都顯示home 

路由守衛

 使用場景

  • 只有當用戶已經登入並擁有某些許可權時才能進入某些路由。
  • 一個由多個表單元件組成的嚮導,例如註冊流程,使用者只有在當前路由的元件中填寫了滿足需求的資訊才可以導航到下一路由。
  • 當用戶未執行儲存操作而試圖離開當前導航時提醒使用者。

可以使用鉤子來完成這些操作

有三種路由守衛

  • CanActivate:處理導航到某路由的情況。
  • CanDeactivate:處理當前路由離開的情況。
  • Resolve:在路由啟用之前獲取路由資料。

下面我們繼續修改案例 只有當用戶已經登入並擁有某些許可權時才能進入產品路由

在app目錄下 新建一個guard 資料夾 並新建一個 login.guard.ts

import {CanActivate} from '@angular/router';

export class LoginGuard implements CanActivate {
  canActivate() {
    const loggedIn: boolean = Math.random() < 0.5;
    if (!loggedIn) {
      console.log('使用者未登入');
    }
    return loggedIn;
  }

}

修改路由app-routing.module.ts

import {NgModule} from '@angular/core';
import {Routes, RouterModule} from '@angular/router';
import {HomeComponent} from './home/home.component';
import {ProductComponent} from './product/product.component';
import {Code404Component} from './code404/code404.component';
import {ProductDescComponent} from './product-desc/product-desc.component';
import {SellerInfoComponent} from './seller-info/seller-info.component';
import {ChatComponent} from './chat/chat.component';
import {LoginGuard} from './guard/login.guard';
import {UnsavedGuard} from './guard/Unsaved.guard';

const routes: Routes = [
  {path: '', redirectTo: '/home', pathMatch: 'full'},
  {path: 'chat', component: ChatComponent, outlet: 'aux'},
  {path: 'home', component: HomeComponent},
  {
    path: 'product/:id', component: ProductComponent,
    children: [
      {path: '', component: ProductDescComponent},
      {path: 'seller/:id', component: SellerInfoComponent},
    ], canActivate: [LoginGuard],
  
  },
  {path: '**', component: Code404Component},
];

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

這樣就只有百分之50的概率進入商品路由了 

我們再新建一個 CanDeactivate:處理當前路由離開的情況。

guard 檔案下 新建 Unsaved.guard.ts

import {CanDeactivate} from '@angular/router';
import {ProductComponent} from '../product/product.component';

export class UnsavedGuard implements CanDeactivate<ProductComponent>{
  canDeactivate(component: ProductComponent) {
    return window.confirm('你還沒有儲存,確定要離開麼?');
  }

}

修改app-routing.module.ts路由配置

import {NgModule} from '@angular/core';
import {Routes, RouterModule} from '@angular/router';
import {HomeComponent} from './home/home.component';
import {ProductComponent} from './product/product.component';
import {Code404Component} from './code404/code404.component';
import {ProductDescComponent} from './product-desc/product-desc.component';
import {SellerInfoComponent} from './seller-info/seller-info.component';
import {ChatComponent} from './chat/chat.component';
import {LoginGuard} from './guard/login.guard';
import {UnsavedGuard} from './guard/Unsaved.guard';

const routes: Routes = [
  {path: '', redirectTo: '/home', pathMatch: 'full'},
  {path: 'chat', component: ChatComponent, outlet: 'aux'},
  {path: 'home', component: HomeComponent},
  {
    path: 'product/:id', component: ProductComponent,
    children: [
      {path: '', component: ProductDescComponent},
      {path: 'seller/:id', component: SellerInfoComponent},
    ], canActivate: [LoginGuard],
    canDeactivate: [UnsavedGuard],
  },
  {path: '**', component: Code404Component},
];

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

當從商品離開到別的元件的時候 就會彈出對話方塊

下面使用Resolve守衛

在guard檔案下 建立product.resolve.ts

程式碼如下

///<reference path="../../../node_modules/@angular/core/src/di/injectable.d.ts"/>
import {ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot} from '@angular/router';
import {Product} from '../product/product.component';
import {Observable} from 'rxjs';
import {Injectable} from '@angular/core';

/**
 * // 這個類需要裝飾器裝飾一下

 */
@Injectable()
export class ProductResolve implements Resolve<Product> {
  constructor(private router: Router) {

  }

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Product> | Promise<Product> | Product {
    let productId: number = route.params['id'];
    if (productId == 1) {
      return new Product(1, 'iPhoneX');
    } else {
      this.router.navigate(['/home']);
      return undefined;
    }
  }

}

product.component.html

<div class="product">
  <p>
    這是是商品資訊元件
  </p>
  <p>商品Id是{{productId}}</p>
  <p>商品名稱是{{productName}}</p>
  <a href="#" [routerLink]="['./']">商品描述</a>
  <a href="#" [routerLink]="['./seller',99]">銷售員資訊</a>
  <router-outlet></router-outlet>

</div>

product.component.ts

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

@Component({
  selector: 'app-product',
  templateUrl: './product.component.html',
  styleUrls: ['./product.component.css']
})
export class ProductComponent implements OnInit {
  public productId: number;
  public productName: string;

  constructor(private routeInfo: ActivatedRoute) {
  }

  ngOnInit() {
    this.routeInfo.params.subscribe((params: Params) => this.productId = params['id']);
    this.routeInfo.data.subscribe((data: { product: Product }) => {
      this.productId = data.product.id;
      this.productName = data.product.name;
    });
  }

}

export class Product {
  constructor(public id: number, public name: string) {
  }
}

app-routing.module.ts

import {NgModule} from '@angular/core';
import {Routes, RouterModule} from '@angular/router';
import {HomeComponent} from './home/home.component';
import {ProductComponent} from './product/product.component';
import {Code404Component} from './code404/code404.component';
import {ProductDescComponent} from './product-desc/product-desc.component';
import {SellerInfoComponent} from './seller-info/seller-info.component';
import {ChatComponent} from './chat/chat.component';
import {LoginGuard} from './guard/login.guard';
import {UnsavedGuard} from './guard/Unsaved.guard';
import {ProductResolve} from './guard/product.resolve';

const routes: Routes = [
  {path: '', redirectTo: '/home', pathMatch: 'full'},
  {path: 'chat', component: ChatComponent, outlet: 'aux'},
  {path: 'home', component: HomeComponent},
  {
    path: 'product/:id', component: ProductComponent,
    children: [
      {path: '', component: ProductDescComponent},
      {path: 'seller/:id', component: SellerInfoComponent},
    ], resolve: {product: ProductResolve}
  },
  {path: '**', component: Code404Component},
];

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

我們之前命名的1 2 3 還記得麼

點選1 的時候 主頁 點選2 的時候 進的是id 1 所以能進去 顯示蘋果7 點選3 的時候 進去的是id 2 所以顯示失敗  進入了主頁

下載專案