Angular里的RouterOutlet指令学习笔记
官网:https://angular.io/api/router/RouterOutlet#description
Acts as a placeholder that Angular dynamically fills based on the current router state.
RouterOutlet作为place holder,Angular会基于当前路由状态动态地填充内容进来。
使用的selector正是router-outlet.
Each outlet can have a unique name, determined by the optional name attribute. The name cannot be set or changed dynamically. If not set, default value is "primary".
默认的名称是primary.
<router-outlet></router-outlet><router-outlet name='left'></router-outlet><router-outlet name='right'></router-outlet>
A router outlet emits an activate event when a new component is instantiated, and a deactivate event when a component is destroyed.
<router-outlet (activate)='onActivate($event)' (deactivate)='onDeactivate($event)'></router-outlet>
路由配置的实体
Each definition translates to a Route object which has two things: a path, the URL path segment for this route; and a component, the component associated with this route.
The router draws upon its registry of definitions when the browser URL changes or when application code tells the router to navigate along a route path.
当浏览器的url发生变化,或者应用程序调用router的路由方法时,router就根据这些注册的定义,进行新Component的绘制。
When the browser's location URL changes to match the path segment /XXX, then the router activates an instance of the YComponent and displays its view.
例如,当浏览器地址栏的url变成/XXX时,router激活XXX对应的Component Y的一个实例,然后显示其视图。
In order to use the Router, you must first register the RouterModule from the @angular/router package.
要使用router,必须先从@angular/router包里注册RouterModule:
Define an array of routes, appRoutes, and pass them to the RouterModule.forRoot() method.
定义一个包含路由信息的数组,传入RouterModule.forRoot方法里。
The RouterModule.forRoot() method returns a module that contains the configured Router service provider, plus other providers that the routing library requires.
RouterModule.forRoot方法返回一个新的module,包含了配置好的Router service provider,加上其他路由库实现需要的provider.
Once the application is bootstrapped, the Router performs the initial navigation based on the current browser URL.
当Angular应用完成初始化之后,Router基于当前浏览器的默认url,进行初始跳转动作。
RouterModule provides the Router service, as well as router directives, such as RouterOutlet and routerLink.
RouterModule提供Router服务,Router directive,比如RouterOutlet和RouterLink.
The root application module imports RouterModule so that the application has a Router and the root application components can access the router directives.
Root应用module导入RouterModule,这样应用可以使用Router服务,并且应用Component可以访问router指令。
Any feature modules must also import RouterModule so that their components can place router directives into their templates.
任何feature module都必须导入RouterModule,只有这样,feature module包含的Component才能在其template里使用router指令。
If the RouterModule didn’t have forRoot() then each feature module would instantiate a new Router instance, which would break the application as there can only be one Router. By using the forRoot() method, the root application module imports RouterModule.forRoot(...) and gets a Router, and all feature modules import RouterModule.forChild(...) which does not instantiate another Router.
Router实例的单例原则。
How forRoot() works
forRoot() takes a service configuration object and returns a ModuleWithProviders, which is a simple object with the following properties:
ngModule: in this example, the GreetingModule class
providers: the configured providers
例子:
const appRoutes: Routes = [ { path: 'crisis-center', component: CrisisListComponent }, { path: 'heroes', component: HeroListComponent }, ]; @NgModule({ imports: [ BrowserModule, FormsModule, RouterModule.forRoot( appRoutes, { enableTracing: true } // <-- debugging purposes only ) ], declarations: [ AppComponent, HeroListComponent, CrisisListComponent, ], bootstrap: [ AppComponent ] }) export class AppModule { }
Registering the RouterModule.forRoot() in the AppModule imports array makes the Router service available everywhere in the application.
在AppModule imports数组里注册RouterModule.forRoot的返回结果,确保Router服务在应用的任意位置都能被使用。
The root AppComponent is the application shell. It has a title, a navigation bar with two links, and a router outlet where the router renders components.
The router outlet serves as a placeholder where the routed components are rendered.
router outlet就是一个占位符,用来存放被路由的Component.
Add a wildcard route to intercept invalid URLs and handle them gracefully. A wildcard route has a path consisting of two asterisks. It matches every URL. Thus, the router selects this wildcard route if it can't match a route earlier in the configuration. A wildcard route can navigate to a custom "404 Not Found" component or redirect to an existing route.
wildcard route就是一个优雅的路由错误处理机制。
{ path: '**', component: PageNotFoundComponent }
如何设置默认路由?
使用redirectTo属性:
const appRoutes: Routes = [ { path: 'crisis-center', component: CrisisListComponent }, { path: 'heroes', component: HeroListComponent }, { path: '', redirectTo: '/heroes', pathMatch: 'full' }, { path: '**', component: PageNotFoundComponent } ];
使用Angular CLI创建启用了routing功能的Component:
ng generate module my-module --routing
This tells the CLI to include the @angular/router npm package and create a file named app-routing.module.ts. You can then use routing in any NgModule that you add to the project or app.
const appRoutes: Routes = [ { path: 'crisis-center', component: CrisisListComponent }, { path: 'heroes', component: HeroListComponent }, { path: '', redirectTo: '/heroes', pathMatch: 'full' }, { path: '**', component: PageNotFoundComponent } ]; @NgModule({ imports: [ RouterModule.forRoot( appRoutes, { enableTracing: true } // <-- debugging purposes only ) ], exports: [ RouterModule ] }) export class AppRoutingModule {}
路由时的参数传递
<a [routerLink]="['/hero', hero.id]">
路由url:localhost:4200/hero/15.
The router extracts the route parameter (id:15) from the URL and supplies it to the HeroDetailComponent via the ActivatedRoute service.
Router将路由参数id:15从url里提取出来,通过ActivatedRoute服务传递到路由的目的Component中去。
如何使用ActivatedRoute
constructor( private route: ActivatedRoute, private router: Router, private service: HeroService ) {}
从ActivatedRoute中提取出参数id:
ngOnInit() { this.hero$ = this.route.paramMap.pipe( switchMap((params: ParamMap) => this.service.getHero(params.get('id'))) ); }
When the map changes, paramMap gets the id parameter from the changed parameters.
当参数map发生变化时,上面代码的paramMap从变化的参数里获得id参数。
The switchMap operator does two things. It flattens the Observable that HeroService returns and cancels previous pending requests. If the user re-navigates to this route with a new id while the HeroService is still retrieving the old id, switchMap discards that old request and returns the hero for the new id.
switchMap操作符做的两件事情:将Observable\的返回类型,平整化为Hero,同时cancel之前pending的请求。如果用户重新跳转到这条路由路径,而HeroService仍然在读取前一个id,则old的请求被discard.
为什么要用Observable包裹Param?
In this example, you retrieve the route parameter map from an Observable. That implies that the route parameter map can change during the lifetime of this component.
暗示了存储路由参数的map有可能在该Component生命周期内发生变化。
By default, the router re-uses a component instance when it re-navigates to the same component type without visiting a different component first. The route parameters could change each time.
默认情况下,当我们反复跳转到一个同样的UI时,router重新该UI Component实例。
You wouldn't want the router to remove the current HeroDetailComponent instance from the DOM only to re-create it for the next id as this would re-render the view. For better UX, the router re-uses the same component instance and updates the parameter.
Router重用Component实例,只是替换parameter值。
Since ngOnInit() is only called once per component instantiation, you can detect when the route parameters change from within the same instance using the observable paramMap property.
ngOnInit在Component整个生命周期里只会触发一次,所以我们可以用Observable包裹过的paramMap属性,来检测参数值的变化。
This application won't re-use the HeroDetailComponent. The user always returns to the hero list to select another hero to view. There's no way to navigate from one hero detail to another hero detail without visiting the list component in between. Therefore, the router creates a new HeroDetailComponent instance every time.
如果是list-detail风格的应用,我们无法从一个明细页面跳转到另一个明细页面,中间必须通过list页面的中转。因此,router每次被迫创建新的明细页面Component实例。
When you know for certain that a HeroDetailComponent instance will never be re-used, you can use snapshot.
route.snapshot provides the initial value of the route parameter map. You can access the parameters directly without subscribing or adding observable operators as in the following:
从route.snapshot能获取route参数的初始值。
ngOnInit() { const id = this.route.snapshot.paramMap.get('id'); this.hero$ = this.service.getHero(id); }
snapshot only gets the initial value of the parameter map with this technique. Use the observable paramMap approach if there's a possibility that the router could re-use the component. This tutorial sample app uses with the observable paramMap.
如果router会重用一个Component,这意味着paramMap在Component生命周期会发生变化,此时要用Observable包裹后的paramMap来检测这种变化。
为什么我们需要Route Guard
Perhaps the user is not authorized to navigate to the target component.
Maybe the user must login (authenticate) first.
Maybe you should fetch some data before you display the target component.
You might want to save pending changes before leaving a component.
You might ask the user if it's OK to discard pending changes rather than save them.
通过路由守卫的返回值确定路由是否继续。
If it returns true, the navigation process continues.
If it returns false, the navigation process stops and the user stays put.
If it returns a UrlTree, the current navigation cancels and a new navigation is initiated to the UrlTree returned.
CanActivate: requiring authentication
Applications often restrict access to a feature area based on who the user is. You could permit access only to authenticated users or to users with a specific role. You might block or limit access until the user's account is activated. The CanActivate guard is the tool to manage these navigation business rules.
用于实现权限检查。
看一个例子:
实现一个AuthGuard:
import { Injectable } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; @Injectable({ providedIn: 'root', }) export class AuthGuard implements CanActivate { canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { console.log('AuthGuard#canActivate called'); return true; } }
在app module里导入这个AuthGuard,维护到Routes数组元素的canActivate属性里:
import { AuthGuard } from '../auth/auth.guard'; const adminRoutes: Routes = [ { path: 'admin', component: AdminComponent, canActivate: [AuthGuard], children: [ { path: '', children: [ { path: 'crises', component: ManageCrisesComponent }, { path: 'heroes', component: ManageHeroesComponent }, { path: '', component: AdminDashboardComponent } ], } ] } ]; @NgModule({ imports: [ RouterModule.forChild(adminRoutes) ], exports: [ RouterModule ] }) export class AdminRoutingModule {}
一个模拟登录的service:
import { Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; import { tap, delay } from 'rxjs/operators'; @Injectable({ providedIn: 'root', }) export class AuthService { isLoggedIn = false; // store the URL so we can redirect after logging in redirectUrl: string; login(): Observable<boolean> { return of(true).pipe( delay(1000), tap(val => this.isLoggedIn = true) ); } logout(): void { this.isLoggedIn = false; } }
The ActivatedRouteSnapshot contains the future route that will be activated and the RouterStateSnapshot contains the future RouterState of the application, should you pass through the guard check.
If the user is not logged in, you store the attempted URL the user came from using the RouterStateSnapshot.url and tell the router to redirect to a login page—a page you haven't created yet. Returning a UrlTree tells the Router to cancel the current navigation and schedule a new one to redirect the user.