iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 7
1

[S06E06] Router

https://www.youtube.com/watch?v=t7MEvi9RdNI&list=PL9LUW6O9WZqgUMHwDsKQf3prtqVvjGZ6S&index=7

今天是由Leo老師主講,「路由」可參考[Angular 深入淺出三十天] Day 20 ~ Day 26
https://ithelp.ithome.com.tw/users/20090728/ironman/1600

以前就覺得[Angular 深入淺出三十天]寫得超棒,例子很棒,超好懂
想不到看到這一集路由就由Leo來講解!!

如果Leo老師文章中的內容,我就會附那篇的連結 (除了避免抄襲,也剛好可以偷懶一下)
除非是範例我覺得可以再加註解的(讓自己跟新手們更好懂)

首先,先開好片頭導讀的文章,再跟著影片一起看,以後才有能力自己查文件

  1. 開啟 https://angular.io
  2. FUNDAMENTALS / NgModules / NgModules Introduction
    https://angular.io/guide/ngmodules

今天內容有:(方便search)

  • Feature Modules的種類
  • 簡單看一下啟動順序

路由樹狀圖

用XMind畫的 for mac
https://apps.apple.com/tw/app/xmind-zen-mind-mapping/id1327661892?mt=12

root iT邦幫忙 /
                Layout /
                        技術問答
                        技術文章
                        iT徵才
                        iT活動
                        Tag
                        鐵人賽
                登入頁 /
  1. app.component.html
<ul>
    <li><a routerLink="login">登入頁</a></li> routerLink為directive
    <li><a [routerLink]="'login'">登入頁</a></li> 屬性寫法,可指定陣列"['home','xxx']"
    
    <li><a routerLink="">Layout</a></li>
    <li><a routerLink="questions">技術問答</a></li>
    <!-- <li><a routerLink="article?tab=tech">技術文章</a></li> -->
    <li><a routerLink="article" [queryParams]="{ tab: 'tech'}">技術文章</a></li>
    <li><a routerLink="article/tag">iT 徵才</a></li>
    <li><a routerLink="article?tab=event">iT 活動</a></li>
    
    <li><a routerLink="tags" routerLinkActive="active">Tags</a></li> active作為css類別
    會幫<a>動態加上.active
    a.active { background-color: grey}
    
    當輸入 http://localhost:4200/layout/tag
    則/layout跟/layout/tag都會符合路由規則
    要加上這個 [routerLinkActiveOptions]="{exact:true}" 才會只有/layout/tag 符合
    
</ul>
  1. article.component.ts 示範 ActivatedRoute 取 query string 裡的變數
constructor(
    private route: ActivatedRoute // 目前路徑資訊
    private router: Router // 在ts裡路由
    ){}
ngOnInit(){
    // 參數取法1:可以接參數 [queryParams]="{ tab: 'tech'}"
    this.route.queryParams.subscribe((params) => {
        params 會是物件 { tab: 'tech'}
    }
    
    // 參數取法2:
    this.route.snapshot.queryParams; // 一樣取到 物件 { tab: 'tech'}
    this.route.snapshot.queryParams['tab']; // 取到'tech'
    
    // 補充: 取變數 { path: 'article/:id }
    this.route.snapshot.paramMap.get('id'); // paramMap是所有參數的地圖
    
    // private router: Router // 在ts裡路由
    this.router.navigate(['login']); /login
    this.router.navigate(['article','tag']); /article/tag
    
    // 使用navigateByUrl
    this.router.navigateByUrl('login');
    
    // 使用相對路徑redirect
    this.router.navigate(['..','login'],{
        relativeTo: this.route // 當前路由
    })
}
  1. app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
...
const routes: Routes = [
// Layout
{ 
    path: '', 
    component: LayoutComponent
    children: [
        {
            path: 'question',
            component: QuestionComponent
        },
        {
            path: 'articles',
            component: ArticlesComponent
        },
        {
            path: 'tags',
            component: TagsComponent
        },
    ]
},
// 登入頁
{ path: 'login', component: LoginComponent},
{ 
    path: '**', // 萬用路由
    redirectTo: 'login', // 重導向,若用到reidrectTo,則一定要有pathMatch
    pathMatch: 'full' // 預設為prefix,確保只有當其餘路徑與空字串相符時,才會redirect
}

]; // Routes 是一個路由組態的陣列
// VS Code對Routes右鍵,Go to Definition
// 可跳到 node_modules/@angular/router/src/config.d.ts
// 可看 Routes 的 interface,來快速了解有哪些東西可以用
// export interface Route {
//     path?: string;
//     pathMatch?: string;
//     matcher?: UrlMatcher;
//     component?: Type<any>;
//     redirectTo?: string;
//     outlet?: string;
//     canActivate?: any[];
//     canActivateChild?: any[];
//     canDeactivate?: any[];
//     canLoad?: any[];
//     data?: Data;
//     resolve?: ResolveData;
//     children?: Routes;
//     loadChildren?: LoadChildren;
//     runGuardsAndResolvers?: RunGuardsAndResolvers;
// }

@NgModule({
  imports: [RouterModule.forRoot(routes,{
      enableTracing: ture, // 開啟 router log內部事件 顯示在console裡
      useHash: true // 支援IE6,網址會有#,改用 HashLocationStrategy
      // 使用情境:server無法支援spa架構時
      
      // 預設 useHash: false 使用官方建議的 PathLocationStrategy
      // 要設定url rewrite(要回到dist/index.html),否則按F5會跑到404
      
      // PathLocationStrategy 的好處
      // 在路由狀態改變的時候,會加在history.pushState,不會真的向伺服器送出請求
      
      // 載入所有模組(跟lazy loading應用情境相反)
      // 使用情境: 專案較小,或不想在使用時卡卡的
      preloadingStrategy: PreloadAllModules 
  })],
  exports: [RouterModule]
})
export class AppRoutingModule { }

lazy loading 影片50分處

偷懶一下,請參考,看文章很好懂喔
[Angular 深入淺出三十天] Day 22 - 路由(五)
https://ithelp.ithome.com.tw/articles/10208267

{
    path: 'login',
    loadChildren: './login/login.module#LoginModule'
}

Guard 路徑保護

Guard 也是 Service 的一種,本節講3個東西:

  • CanActivate 進入路由前,去呼叫一個function,回傳true/false來判斷有無權限使用該路由
  • CanDeactivate 離開該路由時,去呼叫一個function。可記錄、分析、防止資料流失
  • Resolver 預先載入資料。Query String或matrix URL notation只能傳簡單的資料型別

CanActivate

ng g guard guards/auth

  1. auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { UserStoreService } from '../serivces/user-store.service';
@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  constructor(
      private userStore: UserStoreService, // 假設有一個user的service
      private router: Router
  ){} 
  
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot // 當前路由資訊
    ): Observable<boolean> | Promise<boolean> | boolean // 回傳true|false
  {
    // 如果是Login狀態,就回傳ture
    if(this.userStore.isLoggedIn()) {return true};
    // 若不是Login狀態
    this.router.navigate(['login]); // redirect回login
    return false;
  }
}

  1. app.module.ts
import { AuthGuard } from './guards/auth.guard';
@NgModule({
    ...
    // AuthGuard是Service,要加在providers
    providers: [AuthGuard], // ng g guard不會自動加,要自已加
    ...
})
export class AppModule{}
  1. app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AuthGuard } from './guards/auth.guard';
const routes: Routes = [
    { path: '', redirectTo: '/login', pathMatch: 'full'},
    { path: 'login', component: LoginComponent },
    { 
        path: 'article/tag', 
        component: ArticleTagComponent,
        canActivate: [AuthGuard] // 會去呼叫canActivate(),回傳true就能進去article/tag
    },
];

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

CanDeactivate

跟CanActivate()不同的是,實作一個AuthGuard就能通用
而CanDeactivate要每個Component寫一個XXXXDeactiveGuard
(可能有泛型的方式傳入Component的interface到DeactiveGuard,但我不會,有的朋友可提供文章連結喔)
1.some-deactivate.guard.ts

import { Injectable } from '@angular/core';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { SomeComponent } from './some/some.component';

@Injectable({
  providedIn: 'root'
})
export class SomeDeactivateGuard implements CanDeactivate<SomeComponent> {
  canDeactivate(
    component: SomeComponent,
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    // 當要離開Component時,會自動呼叫canDeactivate()
    return true;
  }
}
  1. app-routing.module.ts
    省略一下程式,SomeDeactivateGuard是Service
    自然也要在app.module.ts將SomeDeactivateGuard加入providers裡
const routes: Routes = [
    { 
        path: 'some', 
        component: SomeComponent,
        canDeactivate: [SomeDeactivateGuard] // 會去呼叫canActivate(),回傳true就能進去article/tag
    },
];

Resolver 啟用一個路由前,預先載入資料

Query String或matrix URL notation只能傳簡單的資料型別

  1. app-routing.module.ts
const routes: Routes = [
    { 
        path: 'some/:itemID', 
        component: SomeComponent,
        resolve: { item : ItemLoadResolverService} // 實作Resolver
                    key      Resolver實作
    },
];
  1. some.component.ts
...
import { ActivatedRoute } from '@angular/router';
import { Item } from '../model/item';
@Component({...})
export class SomeComponent implements OnInit {
    public item: Item;
    constructor(private route: ActivatedRoute){}
    ngOnInit(){
        // 訂閱ActivatedRoute上data元素異動
        // 排除對DataService的相依
        this.route.data.subscribe((data: {item: Item})={
                                           key  resolve所回傳的東西
            this.item = data.item;
        });
    }
}
  1. item-load-resolver.service.ts
import { Injectable } from '@angular/core';
import { DataService } from './data.service';
import { Resolve, ActivateRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Item } from '../model/item';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class ItemLoadResolverService implements Resolve<Item>{
    constructor(private dataService: DataService){}
    
    resolve(
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot):
            Item | Observable<Item> | Promise<Item>{
        
        // path: 'some/:itemID' 從URL取得 :itemID
        const itemID = route.paramMap.get('itemID');
        // 取得值後回傳Observable<Item>
        // 排除SomeComponent對DataService的相依
        return this.dataService.getItem(itemID);
    }
}

上一篇
Day06_NgModule & DI
下一篇
Day08_Form Part I - Template-Driven Form
系列文
Angular新手村學習筆記(2019)33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0

我要留言

立即登入留言