您的位置:首页 > Web前端 > AngularJS

angular2 学习笔记 ( Router 路由 )

2016-08-26 16:50 645 查看
更新 : 2017-12-19

今天遇到一个 关于 router empty path + relative 的 bug
https://github.com/angular/angular/issues/18059 https://github.com/angular/angular/issues/13011 https://github.com/angular/angular/issues/17957
也可能这个不是 bug 是 ng 的设计思路.

总只能结果就是如果你想在 component 内使用 relative routerLink 比如 "../" or "./" or "child-path"

那么请你确保你这个 component 所在的 router 一定要有 parent path. 如果所有 parent path 都是 empty string 那么你就 gg.com 了.

imports: [RouterModule.forRoot([
{ path: '', pathMatch: 'full', redirectTo: '/products' },
{
path: '',
children: [
{
path: '',
component: HomeComponent,
children: [{
path: '',
component: CategoryComponent, // <a routerLink="products" >category go</a> 《--result is products/(products) ....
children: [
{
path: 'products',
component: ProductComponent
}
]
}]
}
]
}
])],


下面这个就 ok

imports: [RouterModule.forRoot([
{ path: '', pathMatch: 'full', redirectTo: 'dada/products' },
{
path: '',
children: [
{
path: 'dada', // 要有 parent path
component: HomeComponent,
children: [{
path: '',
component: CategoryComponent, // <a routerLink="products" >category go</a> OK !
children: [
{
path: 'products',
component: ProductComponent
}
]
}]
}
]
}
])],


更新 : 2017-11-04

lazy load vs common chunk

by default ng 会把共用的模块放入 common chunk 里头, 确保即使在 lazy load 的情况下, 模块都不会被加载 2 次 (不重复)

但是这样的设置并不一定就最好的,因为 lazy load 的目的本来就是最小化的加载丫.

这只是一个平衡的问题. ng 视乎只给了 2 个极端的做法, 要嘛使用 common chunk 要嘛完全不要 common chunk

ng build --prod --no-common-chunk

更新 : 2017-10-18

angular 的 router 有一个原则, 如果你触发一个 <a href> 或则调用 router.navigate(...) 但是最终它发现 url 没变动,那么什么不会发生, route event 统统没有运行.

还有另一个是当 url change 时 angular 不会轻易 rebuild component, 如果它的 path 依然是激活的 angular 会保留它哦.

[b]更新 : 2017-08-04[/b]

今天我才发现其实 Preload 不像我说的那样, 我们可以在 preload 方法中把 load 这个方法保持起来.

export class AppCustomPreloader implements PreloadingStrategy {
loaders: Function[] = [];
preload(route: Route, load: Function): Observable<any> {
this.loaders.push(load);
return Observable.of(null);
}
}


然后在任何一个 component 注入 AppCustomePreloader 并调用 load 就可以了.

ng 是很聪明的,如果 load() 运行时发现其模块已经加载了, 那么它并不会报错. 所以鼓励大家去使用它 .



更新 : 2017-04-05

this.router.navigate(['../../'], { relativeTo: this.activatedRoute, queryParamsHandling: 'preserve' });


<a routerLink="../../" queryParamsHandling="preserve">


queryParamsHandling and preserveFragment 可以在移动 router 时保留当前的 queryParams and fragment 很方便哦。

queryParamsHandling 不只是可以 preserve, 还可以 merge 哦

更新 : 2017-03-27

matcher

如果我想自己写 url 匹配, 我们可以通过 matcher

export function matcher(segments: UrlSegment[], group: UrlSegmentGroup, route: Route) {
//判断
return {
consumed: segments.slice(1), //如果你的 route 还有 child 的话,这里要注意,只放入你所匹配到的范围,后面的交给 child 去判断.
posParams: { Id: segments[1] } // 传入 params, url matrix 等等
}
}

@NgModule({
imports: [RouterModule.forChild([
{
matcher : matcher, //帮发放放进来, 这里不要使用匿名方法或则箭头函数哦, aot 不过
component : FirstComponent,
children : []
}
])],
exports: [RouterModule],
})
export class DebugRoutingModule { }


更新 2017-03-05

preloading module

lazy load 的好处是 first load 很快, 因为很多 module 都没有 load 嘛, 但是后续的操作就会变成卡卡的, 因为后来要 load 嘛.

2.1 开始 ng 支持 preloading 的概念. 就是通过 lazyload 让你 firstload 很快之后, 立马去预先加载其它的 module.

比如你的 first page 是一个登入界面, 用户就可以很快看到页面,让后乘着客户在输入密码时,你偷偷去加载后续会用到的模块。这样客户接下来的操作就不会卡卡的了.

这听上去不错哦.

要注意的是, ng 并不太智能, 它不会等到 browser idle 的时候才去加载. 它会马上去加载.

如果你的首页有很多图片或者视屏, ng 不会等待这些图片视屏加载完了才去加载其它模块, 它会马上去加载. 这可能会造成一些麻烦 (因项目而定, 自己做平衡哦)

@Injectable()
export class MyPreloadingStrategy implements PreloadingStrategy {
constructor(private route : ActivatedRoute, private router : Router) {
    //可以注入 router route 任何 service 来帮助我们判断也不要 preload
}
preload(route: Route, load: () => Observable<any>): Observable<any> {
     // ng 会把每一个 lazyload 的 module 丢进来这个函数, 问问你是否要 preload, 如果要, 你就返回 load() 不要 preload 的话就返回 Observable.of(null);
return (true) ?  load() :  Observable.of(null);
}
}

@NgModule({
imports: [RouterModule.forRoot([
{
path: 'home',
loadChildren: "app/+home/home.module#HomeModule"
},
{
path: "about",
loadChildren: "app/+about/about.module#AboutModule"
},
{
path: "contact",
loadChildren: "app/+contact/contact.module#ContactModule"
},
{
path: "",
redirectTo: "home",
pathMatch: "full"
}
], { preloadingStrategy: MyPreloadingStrategy })], // { preloadingStrategy: PreloadAllModules } <--ng 自带的

exports: [RouterModule], providers: [MyPreloadingStrategy] }) export class AppRoutingModule { }


只要在 forRoot 里添加 preloadingStrategy 就可以了. 上面我用了一个自定义的处理, 如果你想简单的表示全部都预加载的话,可以使用 ng 提供的 PreloadAllModules

更新 2017-01-29

提醒 :

路由是有顺序的, 在用 import 特性模块时, 位置要留意.

例如, 如果你 app-routing 最后是处理 404



但是在 app-module 却把 routing 限于特性模块 IdentityModule, 那么 IdentityModule 的 routing 就进不去了。因为已经被匹配掉了.



2016-08-26

参考 :
https://angular.cn/docs/ts/latest/guide/router.html#!#can-activate-guard https://angular.cn/docs/ts/latest/api/ -@angular/router 部分

ng 路由的概念和游览器类似, 和 ui-router 也类似, 下面会把具体功能逐一解释

1. html5 和 hash #

ng 默认模式是 html5, 在开发阶段我们喜欢使用 hash 模式, 这样可以不用部署服务器.

要从 html5 转换成 hash 只要做一个小设定 :

(update:用 angular cli 开发的话,不需要 hash 模式了.)



2.child

和 ui-view 一样 ng 也支持嵌套

就是一个路由的组件模板内又有另一个路由组件

const appRoutes: Routes = [
{
path: "",
redirectTo: "home",
pathMatch: "full"
},
{
path: "home",
component: TopViewComponent, //view 内也有 <router-outlet>
children: [
{
path: ""  //如果没有设置一个空路由的话, "/home" 会报错, 一定要 "/home/detail" 才行.
},
{
path: "detail",
component: FirstChildViewComponent
}
]
}
];


3. 获取 params ( params 是 Matrix Url 和 :Id , 要拿 search 的话用 queryParams )

class TestComponent implements OnInit, OnDestroy {
//home/xx
private sub : Subscription;
constructor(private route: ActivatedRoute) { }
ngOnInit()
{
//监听变化
this.sub = this.route.params.subscribe(params => {
console.log(params); //{ id : "xx" }
});
//如果只是要 get 一次 value, 用快照
console.log(this.route.snapshot.params); //{ id : "xx" }
}
ngOnDestroy()
{
this.sub.unsubscribe(); //记得要取消订阅, 防止内存泄露 (更新 : 其实 ActivatedRoute 可以不需要 unsubscribe,这一个 ng 会智能处理,不过养成取消订阅的习惯也是很好的)
}
}


4. 导航

导航有个重要概念, 类似于游览器, 当我们表示导航时 ../path, /path, path 它是配合当下的区域而做出相对反应的. 记得是 path+区域 哦.

<a [routerLink]="['data',{ key : 'value' }]" [queryParams]="{ name : 'keatkeat' }" fragment="someWhere" >go child</a>


export class TopViewComponent {
constructor(private router: Router, private route: ActivatedRoute) {
console.clear();
}
click(): void {
this.router.navigate(
["data", { key: "value" }], //data 是 child path, {key : "value"} 是 Matrix Url (矩阵 URL) 长这样 /data;key=value
{
relativeTo: this.route, //表示从当前route开始, 这个只有在 path not start with / 的情况下需要放.
//一般的 queryParams, 这里只能 override 整个对象, 如果你只是想添加一个的话,你必须自己实现保留之前的全部.
queryParams: {
'name': "keatkeat" // ng 会对值调用 toString + encode 才放入 url 中, 解析时会 decode, 然后我们自己把 str convert to 我们要的值
},
//#坐标
fragment: "someWhere",
           replaceUrl : true //有时候我们希望 replace history 而不是 push history
}
);
     //redirect by url
let redirectUrl = this.route.snapshot.queryParams["redirectUrl"];
if (redirectUrl != undefined) {
this.router.navigateByUrl(redirectUrl);
}
else {
this.router.navigate(["/admin"]);
}
}
}


大概长这样, 最终个结果 : /home/data;key=value?name=keatkeat#someWhere

通过指令 routerLink , 或则使用代码都可以 (写法有一点点的不同, 看上面对比一下吧)

导航使用的是数组, ["path1","path2",{ matrix : "xx" },"path3"], 你也不一定要一个 path 一个格子, ['/debug/a/b','c'] 也是 work 的

和游览器类似

"/path" 表示从 root 开始

"../path" 表示从当前route往上(parent)

"path" 表示从当前往下(child)

这里有个关键概念, 在不同的 component 获取到的 this.route 是不同的, 组件和 route 是配合使用的

比如上面 click() 方法,如果你放在另一个组件,结果就不同了,this.route 会随着组件和改变.

没有 route name 的概念 (之前好像是有,不知道是不是改了..没找到../.\), 就是用 path 来操作.

matrix url 和 params 获取的手法是一样的. 他的好处就是不需要把所有子孙层页面的参数都放到 params 中,放到 matrix url 才对嘛.

提醒 : { path : "product?name&age" } 注册 route 的时候不要定义 queryParam. ?name&age 删掉 (ui-router need, ng not)

5. 拦截进出

通常进入时需要认证身份,出去时需要提醒保存资料.

ng 为我们提供了拦截点

{
path: ":id",
component: TestComponent,
data: {
title : "test"
},
canActivate: [BlockIn],  //进入
canDeactivate : [BlockOut] //出去
}


BlockIn, BlockOut 分别是2个服务

@Injectable()
export class BlockIn implements CanActivate {
constructor(
private currentRoute: ActivatedRoute,
private router: Router,
) { }
canActivate(nextRoute: ActivatedRouteSnapshot, nextState: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
//this.currentRoute vs nextRoute some logiz
let nextUrl = nextState.url;
let currentUrl = this.router.url;
return new Promise<Boolean>((resolve, reject) => {
setTimeout(() => {
resolve(true);
},5000);
});
}
}


实现了 CanActivate 接口方法, 里头我们可以获取到即将进入的路由, 这样就足够让我们做验证了, 途中如果要跳转页是可以得哦..

@Injectable()
export class BlockOut implements CanDeactivate<TestComponent> {

canDeactivate(
component: TestComponent,
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> | Promise<boolean> | boolean {
console.log("leave");
return true;
}
}


CanDeactivate 还多了一个 Component, 让我们在切出的时候可以调用 component 内容来做检查或者保存资料等等, 很方便呢.

还有一个叫 CanActivateChild, 依据子层来决定能否访问... 它和 CanActivate 的区别是

canActivate(nextRoute: ActivatedRouteSnapshot) vs canActivateChild(childRoute: ActivatedRouteSnapshot)

有点像 dom 事件的 event.target vs event.currenttarget 的概念.



6. resolve

@Injectable()
export class DataResolve implements Resolve<String> {
constructor(private router: Router) { }
resolve(route: ActivatedRouteSnapshot): Observable<any> | Promise<any> | any {
console.log("masuk");
if ("xx" === "xx") {
return "xx";
}
else
{
this.router.navigate(['/someWhere']); //随时可以跳转哦
}
}
}


{
path: "home",
component: TopViewComponent,
resolve: {
resolveData: DataResolve
},
}


注册在 route 里

providers: [appRoutingProviders, Title, BlockIn, BlockOut, DataResolve],


还要记得注册服务哦

ngOnInit()
{
console.log("here");
console.log(this.route.snapshot.data);
}


在 onInit 里面就可以使用啦.

提醒 : 和 ui-router 不同的时, ng 的 resolve 和 data 是不会渗透进子路由的,但是我们在子路由里调用 this.route.parent.... 来获取我们想要的资料.

7. set web browser title

这个和 router 没有直接关系,只是我们通常会在还 route 时改动 browser title 所以记入在这里吧

ng 提供了一个 service 来处理这个title

import { BrowserModule, Title } from '@angular/platform-browser';

providers: [appRoutingProviders, Title, BlockIn, BlockOut, DataResolve]

constructor(private route: ActivatedRoute, private titleService: Title) {
this.titleService.setTitle("data");
}


注册 & 注入 service 就可以用了

8.auxiliary routes / multi view

参考 :

-http://blog.angular-university.io/angular2-router/
-https://yakovfain.com/2016/08/16/angular-2-working-with-auxiliary-routes/
-http://stackoverflow.com/questions/38572370/how-to-specify-auxiliary-component-for-child-route-in-angular2-router3
-http://vsavkin.tumblr.com/post/146722301646/angular-router-empty-paths-componentless-routes

ng 的 multi view 和 ui-router 是不同概念
ng 的 multi view 是指 多个<router-outlet name="secondOutlet" > 在组件中
每一个 router-outlet 都有属于自己的路由管理, 如果你只用一个, 那么它就是 <router-outlet name="primary"> 而 url 就是 browser url
如果你使用多个那么 ng 会把多个 url 结合成一个 放到 browser url 中
它看起来是这样的 /home/(detail//popup:popup)(chat:chat) 对应 /path1/(childPath1//childSecondOutletName:childSecondOutletPath)(mainSecondOutletName:mainSecondOutletPath)
看上去很诡异吧... 它还可以无限嵌套哦 ... 嘻嘻
那它表示的路由结构是这样的 : (全部被激活)

const appRoutes: Routes = [
{
path: "home",
children: [
{
path: "detail",
},
{
path: "popup",
outlet : "popup"
}
]
},
{
path: "chat",
outlet : "chat"
}
];


结构提醒 :

根层是 /home(chat:chat) 而不是 /(home//chat:chat)

子层是 /home/(detail//popup:popup) 而不是 /home/detail(popup:popup)

要留意哦.

// create /team/33/(user/11//aux:chat)
router.createUrlTree(['/team', 33, {outlets: {primary: 'user/11', right: 'chat'}}]);
// remove the right secondary node
router.createUrlTree(['/team', 33, {outlets: {primary: 'user/11', right: null}}]);


生成的方式.

9.异步加载特性模块

首先特性模块和主模块的 routing 设置不同

一个用 .forRoot, 一个用 .forChild 方法

export const routing: ModuleWithProviders = RouterModule.forChild(routes);


要异步加载特性模块的话,非常简单.

在主路由填入 loadChildren 属性,值是模块的路径#类的名字

const appRoutes: Routes = [
{
path: "",
redirectTo: "/home",
pathMatch: "full"
},
{
path: "home",
component: HomeComponent
},
{
path: "product",
loadChildren: "app/product/product.module#ProductModule" //ProductModule 是类的名字, 如果是用 export default 的话,这里可以不需要表明
}
];


在特性模块的路由, 把 path 设置成空

const routes: Routes = [
{
path: "",
component: ProductComponent
}
];


这样就可以啦.

小记:

1.Router : 用于 redirect 操作

2.ActivateRoute : 用于获取 data, params 等等

3.Route : 就是我们每次注册时写的资料咯, 里面有 data, path 哦
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: