路由的做用就是(导航):会加载与请求路由相关联的组件,并获取特定路由的相关数据,这容许咱们经过控制不一样的路由,获取不一样的数据,从而渲染不一样的页面;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
注意,path不能以斜杠(/)开头。 路由器会为解析和构建最终的URL,这样当咱们在应用的多个视图之间导航时,能够任意使用相对路径和绝对路径。
这里用到了RouterModule对象为咱们提供的两个静态方法:forRoot()和forChild() 来配置路由信息;异步
forRoot()
方法提供了路由须要的路由服务提供商和指令,并基于当前浏览器 URL 初始化导航;用于在模块中定义主要的路由信息,经过调用该方法使得咱们的主模块能够访问路由模块中定义的全部指令;
ide
a
标签中的routerLink 指令绑定一个字符串,字符串是path路径中配置的字符串,它将告诉路由器,当用户点击这个连接时,应该导航到哪里;
字体
固然routerLink还能够绑定一个数组,就是咱们的带参路由,下面会具体介绍的:动画
<a [routerLink]="['/test', id]">test</a>
还能够在上面这样配置添加一个routerLinkActive指令:
<a routerLink="test" routerLinkActive="active">test</a>
.active{ color:red }
当此路由被点击时,字体会变成红色;这也是routerLinkActive的做用,使咱们知道哪一个路由处于激活状态;
固然还能够添加上这个[routerLinkActiveOptions]="{exact: true}" 只有当路由彻底同样时,才会将active类加载上去:
<a routerLink="dashboard" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">Dashboard</a>
chrome控制台这样显示:
可见routerLink仍是会自动将路由连接到href上的;class="active“也做用上去啦;
当切换路由时:
class="active” 移到我点击的路由上,只是应该是调用了:ngAfterContentInit(),ngOnChanges(),ngOnDestroy()
注意:
第一个路径片断能够以 / ,./ 或 ../ 开头: 若是以 / 开头,路由将从根路由开始查找 若是以 ./ 开头或没有使用 / ,则路由将从当前激活路由的子路由开始查找 若是以 ../ 开头,路由往上一级查找
eg:<a [routerLink]="['../test', id]">test</a>
固然这里咱们也能够经过在component里控制写:
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>就能够啦;
第二种写法:
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 },
DashboardComponent]; { path: '**', component:}
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
表示重定向路由:须要一个pathMatch属性,告诉路由器是如何用URL去匹配路由的路径的,没有这个属性就会报错;
意思就是当路由URL等于’’时,会去加载DashboardComponent组件;因此你运行你的服务端口号:localhost:4200首先加载的就会是这个组件;
{ path: '**', component:DashboardComponent}
**
路径是一个通配符,表示除了以上几种path,任何的路径都会加载DashboardComponent组件,这个记得写在路由配置最后
固然这种方式咱们还能这么写:
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中引入:
import { AppRoutingModule} from './app-routing.module';
imports:中导入这个就能够啦
AppRoutingModule
这种用法和上面这种用法是同样的
还有一点:如何解决第二次刷新出现404问题:
[RouterModule.forRoot(routes,{useHash:true})]
配置后面这一点,经过添加一个#,防止刷新第二次出现404;
http://localhost:4201/#
RouterModule.forChild(routes)写在子模块里面,并且这边用的是forChild(),不是forRoot(),使用forRoot()也是不会错的,可是通常状况下:
根模块中使用forRoot(),子模块中使用forChild()
forChild()
只能用在特性模块中,这样的一点好处就是咱们没必要在主模块就配置好全部的路由信息,在须要的时候将他们导入主模块;
参数化路由
{ path: 'detail/:id', component: HeroDetailComponent },
配置参数化路由格式: :id 是一个占位符,当导航到HeroDetailCompnent组件上时,会被填入一个特定的id;
这里咱们是这样绑定的:
<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服务,一站式获取路由信息;
import { ActivatedRoute} from '@angular/router'; constructor( private route: ActivatedRoute, ) {}
接下来咱们这样试试:
public params; this.route.params.subscribe( params => { this.params = params; console.log(this.params); } );
这样获取来的是一个对象:
直接取id就能获取到了;
既然是一站式获取,确定不止这几个功能 后面会具体介绍它:
路由配置是也能够经过子路由来配置children:
{ path: 'heroes', component: HeroesComponent, children: [ { path: 'heroTest', component: HeroTestComponent }, ] }
是这样配置的;此时HeroTestComponent组件的路由实际上是:’heroes/heroTest’;
懒加载loadChildren:
{ path:'loadtest', loadChildren:'./loadtest/loadtest.module#LoadtestModule' }
路由是这样配置的:
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) 导出模块类的名称
4.还有一点也是也是重要的:
loadtestModule代码是这样的:里面要引入本身的路由;
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路由里面记得这样配置这样一句才不会出错:
import { LoadtestComponent } from './loadtest.component'; export const route = [ { path:'', component: LoadtestComponent }, ]
path:’’,才能保证代码不出错;
懒加载的文件要注意:
app.module.ts中:
declarations: [ AppComponent, DashboardComponent, HeroDetailComponent, HeroesComponent, TestComponent, ],
这里面的文件,采用懒在家的模块是引用不到得,由于lazy加载文件有本身的ngModule ,若是要使用的组件是同一个,最好创建一个shareModule模块;
采用commonModule 将共享文件放进去,以后的Module里使用再加载进imports中;
Router服务:
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指令同样,因此他们的做用也是同样的:
this.router.navigate(['test', id]);
或者:
this.router.navigate(['test']);
调用该方法后页面会自动跳转到对应的路由地址;
this.router.navigate(['test'], { relativeTo: this.route});
咱们能够设置一个参照路径,参照路径this.route从ActivatedRoute里面取;
配置这个可让本身知道相对于什么位置导航,this.route就是相对于当前的路由进行导航,
假如当前url:localhost:4200/hero ,那么导航后的结果就是:localhost:4200/hero/test
2.咱们注意到还有一个:navigateByUrl()
这个叫作绝对路由;
this.router.navigateByUrl('home');
能够帮助你快速的切换到某个路由下面,若是你当时的路由是这样的:
localhost:4200/hero/test 点击这个路由后就是:localhost:4200/home 咱们通常用这个路由来回到首页; 和navigate()的区别还有点是:这个不是根据参数来肯定路由地址的
3.config 会将页面全部的路由配置信息都显示:
看看路由树:
4.url 输出当前 的路由path
eg:http://localhost:4200/detail/11
url: /detail/11
5.每次导航前都会调用events方法;
RouterModule.forRoot(routes, {enableTracing: true })
经过在控制台配置enableTracing: true能够在控制台看到相关改变;
注意:enableTracing: true 只能在forRoot()里面添加;
具体的事件有:
chrome控制台:
注意:这些事件是以Observable
的形式提供的
ActivateRoute API :
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
第一步:import { Router, ActivatedRoute, ParamMap } from '@angular/router'; 第二步:import 'rxjs/add/operator/switchMap';导入switchMap操做符是由于咱们稍后将会处理路由参数的可观察对象Observable ;会在之后的章节中介绍操做符的; 第三步: constructor( private heroService: HeroService, private route: ActivatedRoute, private router: Router, ) {}
假定事先写好了HeroService:
this.route.paramMap .switchMap((params: ParamMap) => this.heroService.getHero(+params.get('id'))) .subscribe(hero => this.hero = hero ); } 咱们这样操做,前面已经介绍过用parmas获取参数;
因此这样写也能够,用的是paramMap就引入paramMap,params就引入Params
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还能够这样:
<a *ngFor="let hero of heroes" [routerLink]="['/detail', hero.id]" class="col-1-4"></a>
this.params = this.route.snapshot.paramMap.get('id');
console.log(this.params);
因此上面的代码改为这样更好:
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动画必备的:
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
记得在imports中导入:
2.在app.component.ts同级下建立一个animation.ts文件,用来存放咱们的动画效果;
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;
import {slideInDownAnimation} from '../animation';
2.组件中配置
templateUrl: 'hero-detail.component.html', animations: [slideInDownAnimation]
3.html模板中这样引入:
<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路由加载的组件不会被清除,一直显示在下面,状态一直被激活;
这里咱们就能知道第二路由的用处:即便在应用中的不一样页面之间切换,这个组件也应该始终保持打开状态,多重出口能够在同一时间根据不一样的路由来显示不一样的内容;
可是何时清除咱们的第二路由呢?若是我页面不须要呢?
注意:
<a (click)="go()" >Heroes</a> go() { this.router.navigateByUrl('heroes'); }
当点击Heroes时,Contact路由加载的内容就不会被显示:
缘由是这样的:
它使用Router.navigateNyUrl()
方法进行强制导航,因此路由清除啦;
还能够这样清除:
this.router.navigate([{ outlets: { popup: null }}]);
outlets属性的值是另外一个对象,该对象用一些出口名称做为属性名。 惟一的命名出口是'popup'。但这里,'popup'的值是null。null不是一个路由,但倒是一个合法的值。 把popup这个RouterOutlet设置为null会清除该出口,而且从当前URL中移除第二路由popup
3.路由守卫
按照上面所说:任何用户都能在任什么时候候导航到任何地方,这样就有问题,可能此用户并无权限切换到此路由,可能用户未登录不能切换,或者作一些友好提示以后再切换;
因此路由守卫就来了:
守卫返回一个值,以控制路由器的行为:
1.若是它返回true
,导航过程会继续
2.若是它返回false
,导航过程会终止,且用户会留在原地。
也就是你导航的路由是能够取消的,路由守卫还有一个好处就是回退功能时,能够防止用户无限回退,走出app;
路由守卫怎么作:
用CanActivate来处理导航到某路由的状况。
用CanActivateChild来处理导航到某子路由的状况。
用CanDeactivate来处理从当前路由离开的状况.
用Resolve在路由激活以前获取路由数据。
用CanLoad来处理异步导航到某特性模块的状况。
返回的值是一个Observable<boolean>
或Promise<boolean>
,路由器会等待这个可观察对象被解析为true
或false
。
在分层路由的每一个级别上,咱们均可以设置多个守卫。 路由器会先按照从最深的子路由由下往上检查的顺序来检查CanDeactivate()
和CanActivateChild()
守卫。 而后它会按照从上到下的顺序检查CanActivate()
守卫。 若是特性模块是异步加载的,在加载它以前还会检查CanLoad()
守卫。 若是任何一个守卫返回false
,其它还没有完成的守卫会被取消,这样整个导航就被取消了。
看看路由守卫怎么实现:
1.new 一个新项目activeComponent;
2.编写守卫服务:
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.路由中这样导入咱们的守卫:
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就是受保护的;
固然这只是模拟;还有更多用法,之后来列举;