路由的做用就是(導航):會加載與請求路由相關聯的組件,並獲取特定路由的相關數據,這容許咱們經過控制不一樣的路由,獲取不一樣的數據,從而渲染不一樣的頁面;html
幾種常見的路由配置:chrome
Angular路由器是一個可選的外部Angular NgModule ,叫RouterModule; 路由器裏面包含多種服務(RouterModule),多種指令(RouterOutlet,RouterLink,RouterLinkActive),和一套配置(Routes); import { RouterModule} from '@angular/router'; RouterModule.forRoot([ { path: 'test', component: TestComponent }
]) <a routerLink="test">Test</a> <router-outlet></router-outlet>
詳細解析:api
路由定義包括下面部分:數組
Path:路由器會用它來匹配瀏覽器地址欄中的地址,如’test’;瀏覽器
Component:導航到此路由時,須要加載的組件;app
1
|
注意,path不能以斜槓(/)開頭。 路由器會爲解析和構建最終的URL,這樣當咱們在應用的多個視圖之間導航時,能夠任意使用相對路徑和絕對路徑。
|
這裏用到了RouterModule對象爲咱們提供的兩個靜態方法:forRoot()和forChild() 來配置路由信息;異步
forRoot()
方法提供了路由須要的路由服務提供商和指令,並基於當前瀏覽器 URL 初始化導航;用於在模塊中定義主要的路由信息,經過調用該方法使得咱們的主模塊能夠訪問路由模塊中定義的全部指令;
ide
a
標籤中的routerLink 指令綁定一個字符串,字符串是path路徑中配置的字符串,它將告訴路由器,當用戶點擊這個連接時,應該導航到哪裏;
字體
固然routerLink還能夠綁定一個數組,就是咱們的帶參路由,下面會具體介紹的:動畫
1
|
<
a
[routerLink]="['/test', id]">test</
a
>
|
還能夠在上面這樣配置添加一個routerLinkActive指令:
1
|
<
a
routerLink="test" routerLinkActive="active">test</
a
>
|
1
2
3
|
.active{
color:red
}
|
當此路由被點擊時,字體會變成紅色;這也是routerLinkActive的做用,使咱們知道哪一個路由處於激活狀態;
固然還能夠添加上這個[routerLinkActiveOptions]="{exact: true}" 只有當路由徹底同樣時,纔會將active類加載上去:
1
|
<
a
routerLink="dashboard" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">Dashboard</
a
>
|
chrome控制檯這樣顯示:
可見routerLink仍是會自動將路由連接到href上的;class="active「也做用上去啦;
當切換路由時:
class="active」 移到我點擊的路由上,只是應該是調用了:ngAfterContentInit(),ngOnChanges(),ngOnDestroy()
注意:
1
2
3
4
|
第一個路徑片斷能夠以 / ,./ 或 ../ 開頭:
若是以 / 開頭,路由將從根路由開始查找
若是以 ./ 開頭或沒有使用 / ,則路由將從當前激活路由的子路由開始查找
若是以 ../ 開頭,路由往上一級查找<
br
>eg:<
a
[routerLink]="['../test', id]">test</
a
>
|
固然這裏咱們也能夠經過在component裏控制寫:
1
2
3
4
5
6
|
import {Router} from '@angular/router';
<
a
(click)="go()">Heroes</
a
>
constructor(private router: Router) {}
go() {
this.router.navigate(['heroes']);
}
|
這種效果也是同樣的;這裏就須要注入router服務:
router方法用於切換路由頗有用,下面會具體來介紹router服務的;
路由出口:RouterOutlet是由RouterModule提供的指令之一。當咱們在應用中導航時,路由器就把激活的組件顯示在<router-outlet>
裏面。不寫<router-outlet></router-outlet>會致使組件內容不加載進來,從而不顯示內容;
可是一個組件能夠共用一個routeroutlet,因此app.component.ts裏面配置了<router-outlet></router-outlet>就能夠啦;
第二種寫法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
RouterModule.forRoot([...]) 將[] 及中間的內容當成配置文件提取出去;
RouterModule.forRoot(routes),
routes是咱們須要導入的配置文件參數名:
import { routes} from './app-routing.module';
app-routing.module:中咱們能夠這樣寫:
import { DashboardComponent } from './dashboard/dashboard.component';
import { HeroesComponent } from './hero/heroes.component';
import { HeroDetailComponent } from './detail/hero-detail.component';
export const routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'detail/:id', component: HeroDetailComponent },
{ path: 'heroes', component: HeroesComponent }, { path: '**', component:DashboardComponent}];
|
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
表示重定向路由:須要一個pathMatch屬性,告訴路由器是如何用URL去匹配路由的路徑的,沒有這個屬性就會報錯;
意思就是當路由URL等於’’時,會去加載DashboardComponent組件;因此你運行你的服務端口號:localhost:4200首先加載的就會是這個組件;
1
|
{ path: '**', component:DashboardComponent}
|
**
路徑是一個通配符,表示除了以上幾種path,任何的路徑都會加載DashboardComponent組件,這個記得寫在路由配置最後
固然這種方式咱們還能這麼寫:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import { NgModule } from '@angular/core';
import { Routes,RouterModule} from '@angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';
import { HeroesComponent } from './hero/heroes.component';
const routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'heroes', component: HeroesComponent }
];
@NgModule({
imports: [ RouterModule.forChild(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule {}
|
無declarations
!聲明是關聯模塊的重點。
咱們將AppRoutingModule拋出去,當作一個路由模塊,
app.module.ts中引入:
1
|
import { AppRoutingModule} from './app-routing.module';
|
imports:中導入這個就能夠啦
AppRoutingModule
這種用法和上面這種用法是同樣的
還有一點:如何解決第二次刷新出現404問題:
1
|
[RouterModule.forRoot(routes,{useHash:true})]
|
配置後面這一點,經過添加一個#,防止刷新第二次出現404;
http://localhost:4201/#
RouterModule.forChild(routes)寫在子模塊裏面,並且這邊用的是forChild(),不是forRoot(),使用forRoot()也是不會錯的,可是通常狀況下:
1
|
根模塊中使用forRoot(),子模塊中使用forChild()
|
forChild()
只能用在特性模塊中,這樣的一點好處就是咱們沒必要在主模塊就配置好全部的路由信息,在須要的時候將他們導入主模塊;
參數化路由
1
|
{ path: 'detail/:id', component: HeroDetailComponent },
|
配置參數化路由格式: :id 是一個佔位符,當導航到HeroDetailCompnent組件上時,會被填入一個特定的id;
這裏咱們是這樣綁定的:
1
|
<
a
*ngFor="let hero of heroes" [routerLink]="['/detail', hero.id]" class="col-1-4"></
a
>
|
eg:
http://localhost:4201/detail/11
這時的id等於11;
傳參類型的id做用能夠根據傳入的id不一樣讓HeroDetailComponent顯示不一樣的內容;
可是怎麼能讓其顯示不一樣的內容呢? 也就和咱們這個id有關係,如何獲取這個id 用在咱們的組件裏面呢?
經過注入ActivatedRoute服務,一站式獲取路由信息;
1
2
3
4
|
import { ActivatedRoute} from '@angular/router';
constructor(
private route: ActivatedRoute,
) {}
|
接下來咱們這樣試試:
1
2
3
4
5
6
7
|
public params;
this.route.params.subscribe(
params => {
this.params = params;
console.log(this.params);
}
);
|
這樣獲取來的是一個對象:
直接取id就能獲取到了;
既然是一站式獲取,確定不止這幾個功能 後面會具體介紹它:
1
|
路由配置是也能夠經過子路由來配置children:
|
1
2
3
4
5
6
7
|
{
path: 'heroes',
component: HeroesComponent,
children: [
{ path: 'heroTest', component: HeroTestComponent },
]
}
|
是這樣配置的;此時HeroTestComponent組件的路由實際上是:’heroes/heroTest’;
懶加載loadChildren:
1
2
3
4
|
{
path:'loadtest',
loadChildren:'./loadtest/loadtest.module#LoadtestModule'
}
|
路由是這樣配置的:
1.這裏注意幾點:
1
|
import { LoadtestComponent } from './loadtest/loadtest.component';
|
組件不須要在app.module.ts引入
2. loadtest.module.ts 也不須要在app.module.ts中引入;而是經過loadchildren屬性,在須要的時候告訴Angular路由依據loadchildren屬性配置的路徑去加載LoadtestModule模塊,這就是模塊懶加載功能;當用戶須要的時候纔回去加載,大大減小了應用啓動時的資源加載大小;
3.loadChildren後面的字符串由三部分組成:
1
2
3
|
(1) 須要導入模塊路勁的相對路徑
(2) #分隔符
(3) 導出模塊類的名稱
|
4.還有一點也是也是重要的:
loadtestModule代碼是這樣的:裏面要引入本身的路由;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import { NgModule } from '@angular/core';
import {CommonModule} from '@angular/common';
import { LoadtestComponent } from './loadtest.component';
import {RouterModule} from '@angular/Router';
import {route} from './loadtest-routing.module';
@NgModule({
imports:[
CommonModule,
RouterModule.forChild(route),
],
declarations:[
LoadtestComponent
]
})
export class LoadtestModule{}
|
在route路由裏面記得這樣配置這樣一句纔不會出錯:
1
2
3
4
5
6
7
|
import { LoadtestComponent } from './loadtest.component';
export const route = [
{
path:'',
component: LoadtestComponent
},
]
|
path:’’,才能保證代碼不出錯;
懶加載的文件要注意:
app.module.ts中:
1
2
3
4
5
6
7
|
declarations: [
AppComponent,
DashboardComponent,
HeroDetailComponent,
HeroesComponent,
TestComponent,
],
|
這裏面的文件,採用懶在家的模塊是引用不到得,由於lazy加載文件有本身的ngModule ,若是要使用的組件是同一個,最好創建一個shareModule模塊;
採用commonModule 將共享文件放進去,以後的Module裏使用再加載進imports中;
Router服務:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
1. class Router{
2. errorHandler:ErrorHandler
3. navigated: boolean
4. urlHandlingStrategy:UrlHandlingStrategy
5. routeReuseStrategy:RouteReuseStrategy
6. config:Routes
7. initialNavigation():void
8. setUpLocationChangeListener():void
9. get routerState():RouterState
10. get url(): string
11. get events():Observable<
Event
>
12. resetConfig(config:Routes):void
13. ngOnDestroy():void
14. dispose():void
15. createUrlTree(commands: any[], navigationExtras:NavigationExtras):UrlTree
16. navigateByUrl(url: string|UrlTree, extras:NavigationExtras):Promise<
boolean
>
17. navigate(commands: any[], extras:NavigationExtras):Promise<
boolean
>
18. serializeUrl(url:UrlTree): string
19. parseUrl(url: string):UrlTree
20. isActive(url: string|UrlTree, exact: boolean): boolean
21. }
|
這是Router API爲咱們提供的方法和屬性;
看看幾個經常使用的:
1.navigate() 該方法支持的參數類型和routerLink指令同樣,因此他們的做用也是同樣的:
1
|
this.router.navigate(['test', id]);
|
或者:
1
|
this.router.navigate(['test']);
|
調用該方法後頁面會自動跳轉到對應的路由地址;
1
|
this.router.navigate(['test'], { relativeTo: this.route});
|
咱們能夠設置一個參照路徑,參照路徑this.route從ActivatedRoute裏面取;
配置這個可讓本身知道相對於什麼位置導航,this.route就是相對於當前的路由進行導航,
1
|
假如當前url:localhost:4200/hero ,那麼導航後的結果就是:localhost:4200/hero/test
|
2.咱們注意到還有一個:navigateByUrl()
這個叫作絕對路由;
1
|
this.router.navigateByUrl('home');
|
能夠幫助你快速的切換到某個路由下面,若是你當時的路由是這樣的:
1
2
3
|
localhost:4200/hero/test 點擊這個路由後就是:localhost:4200/home 咱們通常用這個路由來回到首頁;
和navigate()的區別還有點是:這個不是根據參數來肯定路由地址的
|
3.config 會將頁面全部的路由配置信息都顯示:
看看路由樹:
4.url 輸出當前 的路由path
eg:http://localhost:4200/detail/11
url: /detail/11
5.每次導航前都會調用events方法;
1
|
RouterModule.forRoot(routes, {enableTracing: true })
|
經過在控制檯配置enableTracing: true能夠在控制檯看到相關改變;
注意:enableTracing: true 只能在forRoot()裏面添加;
具體的事件有:
chrome控制檯:
注意:這些事件是以Observable
的形式提供的
ActivateRoute API :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
interface ActivatedRoute {
snapshot: ActivatedRouteSnapshot
url: Observable<
UrlSegment
[]>
params: Observable<
Params
>
queryParams: Observable<
Params
>
fragment: Observable<
string
>
data: Observable<
Data
>
outlet: string
component: Type<
any
>|string|null
get routeConfig(): Route|null
get root(): ActivatedRoute
get parent(): ActivatedRoute|null
get firstChild(): ActivatedRoute|null
get children(): ActivatedRoute[]
get pathFromRoot(): ActivatedRoute[]
get paramMap(): Observable<
ParamMap
>
get queryParamMap(): Observable<
ParamMap
>
toString(): string
}
|
1.parmaMap
1
2
3
4
5
6
7
8
9
10
|
第一步:import { Router, ActivatedRoute, ParamMap } from '@angular/router';
第二步:import 'rxjs/add/operator/switchMap';導入switchMap操做符是由於咱們稍後將會處理路由參數的可觀察對象Observable ;會在之後的章節中介紹操做符的;
第三步:
constructor(
private heroService: HeroService,
private route: ActivatedRoute,
private router: Router,
) {}
<
code
> <
br
></
code
>
|
假定事先寫好了HeroService:
1
2
3
4
5
6
|
this.route.paramMap
.switchMap((params: ParamMap) => this.heroService.getHero(+params.get('id')))
.subscribe(hero => this.hero = hero );
}
咱們這樣操做,前面已經介紹過用parmas獲取參數;
|
因此這樣寫也能夠,用的是paramMap就引入paramMap,params就引入Params
1
2
3
4
5
6
7
|
this.route.params
.switchMap((params: Params) => this.heroService.getHero(+params['id']))
.subscribe(hero =>
this.hero = hero;
}
);
|
因爲參數是做爲Observable
提供的,因此咱們得用switchMap
操做符來根據名字取得id
參數,並告訴HeroService
來獲取帶有那個id
的英雄。
2/snapshot(快照)
route.snapshot
提供了路由參數的初始值。 咱們能夠經過它來直接訪問參數,而不用訂閱或者添加Observable的操做符
因此獲取參數的id還能夠這樣:
1
2
|
<
a
*ngFor="let hero of heroes" [routerLink]="['/detail', hero.id]" class="col-1-4"></
a
>
<
br
>this.params = this.route.snapshot.paramMap.get('id');<
br
><
br
>console.log(this.params);
|
因此上面的代碼改爲這樣更好:
1
2
3
4
|
this.params = this.route.snapshot.paramMap.get('id');
console.log(this.params);
this.heroService.getHero(this.params)
.then(hero => this.hero = hero);
|
兩種方法:params 和snapshot到底何時該用哪一種呢?
總結 ,路由主要是用到了這些方面啦:
給路由添加一些新特性:
一..添加動畫
1. 在app.module.ts中引入啓用Angular動畫必備的:
1
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
記得在imports中導入:
2.在app.component.ts同級下建立一個animation.ts文件,用來存放咱們的動畫效果;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
import { animate, AnimationEntryMetadata, state, style, transition, trigger } from '@angular/core';
export const slideInDownAnimation: AnimationEntryMetadata =
trigger('routeAnimation', [
state('*',
style({
opacity: 1,
transform: 'translateX(0)'
})
),
transition(':enter', [
style({
opacity: 0,
transform: 'translateX(-100%)'
}),
animate('0.2s ease-in')
]),
transition(':leave', [
animate('0.5s ease-out', style({
opacity: 0,
transform: 'translateY(100%)'
}))
])
]);
|
假定我有以上代碼,視圖進場和出場;
1.構建動畫須要的庫; 2.導出了一個名叫slideInDownAnimation的常量,並把它設置爲一個名,用於外部引入此ts文件; 3.叫routeAnimation的動畫觸發器。帶動畫的組件將會引用這個名字。用在外部html頁面引用 4.指定了一個通配符狀態 —— *,它匹配該路由組件存在時的任何動畫狀態。 5. 定義兩個過渡效果,其中一個(:enter)在組件進入應用視圖時讓它從屏幕左側緩動進入(ease-in),另外一個(:leave)在組件離開應用視圖時讓它向下飛出。
3,如何使用動畫;
1.在須要的組件中引入變量名爲:slideInDownAnimation的文件animation.ts;
1
|
import {slideInDownAnimation} from '../animation';
|
2.組件中配置
1
2
|
templateUrl: 'hero-detail.component.html',
animations: [slideInDownAnimation]
|
3.html模板中這樣引入:
1
|
<
div
*ngIf="hero" [@routeAnimation]="'active'">
|
@routeAnimation 動畫觸發器名
點擊以後會自動加載動畫的;
二.多重路由出口
通常狀況下:咱們使用一個路由出口就行啦,什麼狀況下會使用第二路由呢?
1.建立一個新組件ComposemessageComponent
2.路由配置:
{ path:'compose', component:ComposemessageComponent, outlet:'popup' }
3.html頁面這樣配置:
<nav> <a routerLink="dashboard" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">Dashboard</a> <a (click)="go()" >Heroes</a> <a routerLink="test" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">Test</a> <a routerLink="loadtest" routerLinkActive="active">loadTest</a> <a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a> </nav> <router-outlet></router-outlet> <router-outlet name="popup"></router-outlet>
這是個人頁面全部的路由配置;
點擊Contact 不會替換其餘的組件信息,注意看Url:http://localhost:4200/dashboard(popup:compose)
點擊Contact url地址沒有變成http://localhost:4200/contact而是採用圓括號加載
圓括號包裹的部分是第二路由。
第二路由包括一個出口名稱(popup
)、一個冒號分隔符和第二路由的路徑(compose
)
而是顯示在下面,點擊test也是同樣:
Contact路由加載的組件不會被清除,一直顯示在下面,狀態一直被激活;
這裏咱們就能知道第二路由的用處:即便在應用中的不一樣頁面之間切換,這個組件也應該始終保持打開狀態,多重出口能夠在同一時間根據不一樣的路由來顯示不一樣的內容;
可是何時清除咱們的第二路由呢?若是我頁面不須要呢?
注意:
1
2
3
4
|
<
a
(click)="go()" >Heroes</
a
>
go() {
this.router.navigateByUrl('heroes');
}
|
當點擊Heroes時,Contact路由加載的內容就不會被顯示:
緣由是這樣的:
它使用Router.navigateNyUrl()
方法進行強制導航,因此路由清除啦;
還能夠這樣清除:
1
|
this.router.navigate([{ outlets: { popup: null }}]);
|
1
|
outlets屬性的值是另外一個對象,該對象用一些出口名稱做爲屬性名。 惟一的命名出口是'popup'。但這裏,'popup'的值是null。null不是一個路由,但倒是一個合法的值。 把popup這個RouterOutlet設置爲null會清除該出口,而且從當前URL中移除第二路由popup
|
3.路由守衛
按照上面所說:任何用戶都能在任什麼時候候導航到任何地方,這樣就有問題,可能此用戶並無權限切換到此路由,可能用戶未登錄不能切換,或者作一些友好提示以後再切換;
因此路由守衛就來了:
守衛返回一個值,以控制路由器的行爲:
1.若是它返回true
,導航過程會繼續
2.若是它返回false
,導航過程會終止,且用戶會留在原地。
也就是你導航的路由是能夠取消的,路由守衛還有一個好處就是回退功能時,能夠防止用戶無限回退,走出app;
路由守衛怎麼作:
1
2
3
4
5
|
用CanActivate來處理導航到某路由的狀況。
用CanActivateChild來處理導航到某子路由的狀況。
用CanDeactivate來處理從當前路由離開的狀況.
用Resolve在路由激活以前獲取路由數據。
用CanLoad來處理異步導航到某特性模塊的狀況。
|
返回的值是一個Observable<boolean>
或Promise<boolean>
,路由器會等待這個可觀察對象被解析爲true
或false
。
在分層路由的每一個級別上,咱們均可以設置多個守衛。 路由器會先按照從最深的子路由由下往上檢查的順序來檢查CanDeactivate()
和CanActivateChild()
守衛。 而後它會按照從上到下的順序檢查CanActivate()
守衛。 若是特性模塊是異步加載的,在加載它以前還會檢查CanLoad()
守衛。 若是任何一個守衛返回false
,其它還沒有完成的守衛會被取消,這樣整個導航就被取消了。
看看路由守衛怎麼實現:
1.new 一個新項目activeComponent;
2.編寫守衛服務:
1
2
3
4
5
6
7
8
9
|
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
@Injectable()
export class LoadTestService implements CanActivate{
canActivate() {
console.log('AuthGuard#canActivate called');
return true;
}
}
|
3.路由中這樣導入咱們的守衛:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import { ActiveComponent } from './active/active.component';
import {LoadTestService} from './loadtest.service';
export const route = [
{
path:'',
component: LoadtestComponent,
canActivate:[LoadTestService],
children:[
{
path:'a',
component: ActiveComponent
}
]
},
]
|
這樣咱們的ActiveComponent就是受保護的;
固然這只是模擬;還有更多用法,之後來列舉;