美文网首页
Angular路由深入讲解

Angular路由深入讲解

作者: 我是上帝可爱多 | 来源:发表于2017-08-15 23:10 被阅读515次

也许这是我最后一次写关于Angular的文章,但也不一定,如果有机会我可能会继续吧,但今天的主题很明确就是路由。

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <h1>{{title}}</h1>
    <nav>
      <a routerLink="/dashboard" routerLinkActive="active">Dashboard</a>
      <a routerLink="/heroes" routerLinkActive="active">Heroes</a>
      <a routerLink="/cities" routerLinkActive="active">Cities</a>
    </nav>
    <router-outlet></router-outlet>
  `,
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  title = 'Tour of Heroes';
}

这是主页面模版,通过routerLink实现定位,routerLinkActive会给当前显示的打上标记。

nav a.active {
  color: #039be5;
}

<router-outlet></router-outlet>实际上则是对当前路由页面的填充。

那么问题来了,我们怎么知道每一个routerLink所指向的页面,看这个:

<!-- app-routing.module.ts -->
import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { DashboardComponent }   from './dashboard.component';
import { HeroesComponent }      from './heroes.component';
import { HeroDetailComponent }  from './hero-detail.component';

const routes: Routes = [
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  { path: 'dashboard',  component: DashboardComponent },
  { path: 'detail/:id', component: HeroDetailComponent },
  { path: 'heroes',     component: HeroesComponent }
];

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

说以下三点:

  • 注意命名的规范,路由模块都以routing这种形式,.module结尾。
  • 路由里面的依赖组件必须都引进来。
  • 结尾处一定要 将这个路由注册[ RouterModule.forRoot(routes) ]。

redirectTo是进来之后重定向到dashboard这个组件显示的页面。重定向路由需要一个pathMatch属性,来告诉路由器如何用URL去匹配路由的路径,否则路由器就会报错。 在本应用中,路由器应该只有在完整的URL等于''时才选择HeroListComponent组件,因此我们要把pathMatch设置为'full'。

我们觉得有一点很奇怪{ path: 'detail/:id', component: HeroDetailComponent },这里出处在哪里,模版中并没有呀。。
此时我们定位到了heroes的组件:

<!-- heroes.component.js -->
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

import { Hero } from './hero';
import { HeroService } from './hero.service';

@Component({
  selector: 'my-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: [ './heroes.component.css' ]
})
export class HeroesComponent implements OnInit {
  heroes: Hero[];
  selectedHero: Hero;

  constructor(
    private router: Router,
    private heroService: HeroService) { }

  getHeroes(): void {
    this.heroService.getHeroes().then(heroes => this.heroes = heroes);
  }

  ngOnInit(): void {
    this.getHeroes();
  }

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }

  gotoDetail(): void {
    this.router.navigate(['/detail', this.selectedHero.id]);
  }
}

我们看这个

this.router.navigate(['/detail', this.selectedHero.id]);

这实际上是在走这个路由{ path: 'detail/:this.selectedHero.id', component: HeroDetailComponent }
我们来看模版的代码:

<h2>My Heroes</h2>
<ul class="heroes">
  <li *ngFor="let hero of heroes"
    [class.selected]="hero === selectedHero"
    (click)="onSelect(hero)">
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>
</ul>
<div *ngIf="selectedHero">
  <h2>
    {{selectedHero.name | uppercase}} is my hero
  </h2>
  <button (click)="gotoDetail()">View Details</button>
</div>

我们这里有一个选定的hero,点击之后就会走那个detail路由。问题来了,这样的话如何把需要的数据带到那个页面去呢?

import { Component, OnInit }        from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Location }                 from '@angular/common';

import { Hero }         from './hero';
import { HeroService }  from './hero.service';
@Component({
  selector: 'hero-detail',
  templateUrl: './hero-detail.component.html',
  styleUrls: [ './hero-detail.component.css' ]
})
export class HeroDetailComponent implements OnInit {
  hero: Hero;

  constructor(
    private heroService: HeroService,
    private route: ActivatedRoute,
    private location: Location
  ) {}

  ngOnInit(): void {
    this.route.paramMap
      .switchMap((params: ParamMap) => this.heroService.getHero(+params.get('id')))
      .subscribe(hero => this.hero = hero);
  }

  // 返回上一页
  goBack(): void {
    this.location.back();
  }
}

我们在ngOninit的时候,通过paramMap获取到来这个路由携带的参数,进而在服务中去做操作,把hero取出来。params.get('id')就是从hero页面传过来的selectedHero的id。

paramMap
An Observable
that contains a map of the required and optional parameters specific to the route. The map supports retrieving single and multiple values from the same parameter.

注意ActivatedRoute很关键,它可以帮我们获取路由的信息。

好了就剩最后一步就完成了路由的所有配置,那到底是什么呢?

import { AppRoutingModule }     from './app-routing.module';
@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule
  ]

以上截取部分app.module的代码。

我们有个页面是英雄列表,理出每条英雄明细,现在需要每个跳转到详细页面。

<a *ngFor="let hero of heroes"  [routerLink]="['/detail', hero.id]"  class="col-1-4">

我们在routerlink里面也可以传递参数,这样也会走activeroute的路线。

我们再来看一个复杂一点的:

<!--app.component.ts -->
import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <h1 class="title">Angular Router</h1>
    <nav>
      <a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
      <a routerLink="/heroes" routerLinkActive="active">Heroes</a>
      <a routerLink="/admin" routerLinkActive="active">Admin</a>
      <a routerLink="/login" routerLinkActive="active">Login</a>
      <a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
    </nav>
    <router-outlet></router-outlet>
    <router-outlet name="popup"></router-outlet>
  `
})
export class AppComponent {
}

我们这里有2个点不太明白,我也是一样,后面我们再解释。

<!-- app-routing.module.ts -->
import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ComposeMessageComponent }  from './compose-message.component';
import { PageNotFoundComponent }    from './not-found.component';
import { CanDeactivateGuard }       from './can-deactivate-guard.service';
import { AuthGuard }                from './auth-guard.service';
import { SelectivePreloadingStrategy } from './selective-preloading-strategy';

const appRoutes: Routes = [
  {
    path: 'compose',
    component: ComposeMessageComponent,
    outlet: 'popup'
  },
  {
    path: 'admin',
    loadChildren: 'app/admin/admin.module#AdminModule',
    canLoad: [AuthGuard]
  },
  {
    path: 'crisis-center',
    loadChildren: 'app/crisis-center/crisis-center.module#CrisisCenterModule',
    data: { preload: true }
  },
  { path: '',   redirectTo: '/heroes', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];

@NgModule({
  imports: [
    RouterModule.forRoot(
      appRoutes,
      {
        enableTracing: true, // <-- debugging purposes only
        preloadingStrategy: SelectivePreloadingStrategy,

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

讲解一下几点:

  • { path: '**', component: PageNotFoundComponent } 找不到匹配,notfound组件
  • loadChildren: 'app/crisis-center/crisis-center.module#CrisisCenterModule',这个路由还有子路由,admin也是。

是不是有点傻傻分不清了,没关系咱们先看admin目录。

接下来看

<!-- admin-routing.module.ts -->
import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { AdminComponent }           from './admin.component';
import { AdminDashboardComponent }  from './admin-dashboard.component';
import { ManageCrisesComponent }    from './manage-crises.component';
import { ManageHeroesComponent }    from './manage-heroes.component';

import { AuthGuard }     from '../auth-guard.service';

const adminRoutes: Routes = [
  {
    path: '',
    component: AdminComponent,
    canActivate: [AuthGuard],
    children: [
      {
        path: '',
        canActivateChild: [AuthGuard],
        children: [
          { path: 'crises', component: ManageCrisesComponent },
          { path: 'heroes', component: ManageHeroesComponent },
          { path: '', component: AdminDashboardComponent }
        ]
      }
    ]
  }
];

@NgModule({
  imports: [
    RouterModule.forChild(adminRoutes)
  ],
  exports: [
    RouterModule
  ]
})
export class AdminRoutingModule {}

试想我们在主页面点击admin,他会怎么走,会走到admin子路由下面,此时对应的是path:'',canActivateChild: [AuthGuard]是什么鬼。

import { Injectable }       from '@angular/core';
import {
  CanActivate, Router,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  CanActivateChild,
  NavigationExtras,
  CanLoad, Route
}                           from '@angular/router';
import { AuthService }      from './auth.service';

@Injectable()
export class AuthGuard implements CanActivate, CanActivateChild, CanLoad {
  constructor(private authService: AuthService, private router: Router) {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    let url: string = state.url;

    return this.checkLogin(url);
  }

  canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    return this.canActivate(route, state);
  }

  canLoad(route: Route): boolean {
    let url = `/${route.path}`;

    return this.checkLogin(url);
  }

  checkLogin(url: string): boolean {
    if (this.authService.isLoggedIn) { return true; }

    // Store the attempted URL for redirecting
    this.authService.redirectUrl = url;

    // Create a dummy session id
    let sessionId = 123456789;

    // Set our navigation extras object
    // that contains our global query params and fragment
    let navigationExtras: NavigationExtras = {
      queryParams: { 'session_id': sessionId },
      fragment: 'anchor'
    };

    // Navigate to the login page with extras
    this.router.navigate(['/login'], navigationExtras);
    return false;
  }
}

CanActivate, CanActivateChild, CanLoad 这三个是说是否能进入子路由。

我们来看这个过程:

canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    return this.canActivate(route, state);
  }
 //这是第一步,能否激活子路由。

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    let url: string = state.url;
    return this.checkLogin(url);
  }

checkLogin(url: string): boolean {
    if (this.authService.isLoggedIn) { return true; }

    // Store the attempted URL for redirecting  初次进来isLoggedIn是false, 会走这里 
    this.authService.redirectUrl = url;

    // Create a dummy session id
    let sessionId = 123456789;

    // Set our navigation extras object
    // that contains our global query params and fragment
    let navigationExtras: NavigationExtras = {
      queryParams: { 'session_id': sessionId },
      fragment: 'anchor'
    };

    // Navigate to the login page with extras
    this.router.navigate(['/login'], navigationExtras);
    return false;
  }

初次进来,会进canload方法,获取到当前的url为'/admin'被拦截,通过this.router.navigate(['/login'], navigationExtras)定位到login的组件页面。
注意canload只会在页面首次请求的那一次才有。

执行顺序:canload(only once) -> canActivate -> canActivateChild

Auth.service代码:

import { Injectable } from '@angular/core';

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/delay';

@Injectable()
export class AuthService {
  isLoggedIn = false;   // 提供的登录状态为false

  // store the URL so we can redirect after logging in
  redirectUrl: string;

  login(): Observable<boolean> {
    return Observable.of(true).delay(1000).do(val => this.isLoggedIn = true);
  }

  logout(): void {
    this.isLoggedIn = false;
  }
}

根据以上我们首次点击admin,此时还处于未登录状态,我们会被带到login界面。

<!-- login.component.ts -->
import { Component }        from '@angular/core';
import { Router, NavigationExtras } from '@angular/router';
import { AuthService }      from './auth.service';

@Component({
  template: `
    <h2>LOGIN</h2>
    <p>{{message}}</p>
    <p>
      <button (click)="login()"  *ngIf="!authService.isLoggedIn">Login</button>
      <button (click)="logout()" *ngIf="authService.isLoggedIn">Logout</button>
    </p>`
})
export class LoginComponent {
  message: string;

  constructor(public authService: AuthService, public router: Router) {
    this.setMessage();
  }

  setMessage() {
    this.message = 'Logged ' + (this.authService.isLoggedIn ? 'in' : 'out');
  }

  login() {
    this.message = 'Trying to log in ...';

    this.authService.login().subscribe(() => {
      this.setMessage();
      if (this.authService.isLoggedIn) {
        // Get the redirect URL from our auth service
        // If no redirect has been set, use the default
        let redirect = this.authService.redirectUrl ? this.authService.redirectUrl : '/admin';

        // Set our navigation extras object
        // that passes on our global query params and fragment
        let navigationExtras: NavigationExtras = {
          queryParamsHandling: 'preserve',
          preserveFragment: true
        };

        // Redirect the user
        this.router.navigate([redirect], navigationExtras);
      }
    });
  }

  logout() {
    this.authService.logout();
    this.setMessage();
  }
}

初次进入login未登录,message='logged out',点击login会执行authService的login方法,改变登录状态。

if (this.authService.isLoggedIn) {
        // Get the redirect URL from our auth service
        // If no redirect has been set, use the default
        let redirect = this.authService.redirectUrl ? this.authService.redirectUrl : '/admin';

        // Set our navigation extras object
        // that passes on our global query params and fragment 保留之前的设置
        let navigationExtras: NavigationExtras = {
          queryParamsHandling: 'preserve',
          preserveFragment: true
        };

        // Redirect the user
        this.router.navigate([redirect], navigationExtras);
      }

之前我们在auth.service中设置了redirecturl为admin,所以会navigate到admin界面,并携带navigationExtras。

const adminRoutes: Routes = [
  {
    path: '',
    component: AdminComponent,
    canActivate: [AuthGuard],
    children: [
      {
        path: '',
        canActivateChild: [AuthGuard],
        children: [
          { path: 'crises', component: ManageCrisesComponent },
          { path: 'heroes', component: ManageHeroesComponent },
          { path: '', component: AdminDashboardComponent }
        ]
      }
    ]
  }
];

好,现在成功进入admin,默认会加载AdminDashboardComponent。

import { Component, OnInit }    from '@angular/core';
import { ActivatedRoute }       from '@angular/router';
import { Observable }           from 'rxjs/Observable';

import { SelectivePreloadingStrategy } from '../selective-preloading-strategy';

import 'rxjs/add/operator/map';

@Component({
  template:  `
    <p>Dashboard</p>

    <p>Session ID: {{ sessionId | async }}</p>
    <a id="anchor"></a>
    <p>Token: {{ token | async }}</p>

    Preloaded Modules
    <ul>
      <li *ngFor="let module of modules">{{ module }}</li>
    </ul>
  `
})
export class AdminDashboardComponent implements OnInit {
  sessionId: Observable<string>;
  token: Observable<string>;
  modules: string[];

  constructor(
    private route: ActivatedRoute,
    private preloadStrategy: SelectivePreloadingStrategy
  ) {
    this.modules = preloadStrategy.preloadedModules;
  }

  ngOnInit() {
    // Capture the session ID if available
    this.sessionId = this.route
      .queryParamMap
      .map(params => params.get('session_id') || 'None');

    // Capture the fragment if available
    this.token = this.route
      .fragment
      .map(fragment => fragment || 'None');
  }
}

ngoninit的数据就是从auth.service里面原封不动过来的。。。

最后说一下:

loadChildren: 'app/admin/admin.module#AdminModule'

这个是加载的子模块名。感觉这一期讲不完,下一期再继续分享吧。。。

相关文章

  • Angular路由深入讲解

    也许这是我最后一次写关于Angular的文章,但也不一定,如果有机会我可能会继续吧,但今天的主题很明确就是路由。 ...

  • 模拟面试问题总结

    AngularJS路由实现原理 Angular路由 允许我们通过不同的URL访问不同的内容,并且Angular路由...

  • 模拟面试问题总结

    AngularJS路由实现原理 Angular路由 允许我们通过不同的URL访问不同的内容,并且Angular路由...

  • Angular Route导航

    Angular Route导航 路由基础知识 路由相关对象介绍 新建路由项目 使用angular-cli新建项目。...

  • angular路由跳转

    angular路由 使用路由 routerLink="/"路由出口

  • 前端路由原理和React Router

    前端路由原理 前端三大框架 Angular、React、Vue ,它们的路由解决方案 angular/router...

  • Angular的路由延迟加载

    Angular的路由延迟加载 angular的路由延迟加载大致分为两类CanActivate和CanLoad。这两...

  • angular中ngRoute和uiRoute的区别

    最近一直在学习angular路由这一部分,angular的路由有两种,在angular最先出来的ngRoute的时...

  • 基于 Angular 2+ 的新版 Tab页与路由策略

    一、难点: Angular 辅助路由 + 路由策略 会报错。Cannot reattach ActivatedRo...

  • angular路由

    1.背景介绍 angular路由 angular路由可以实现多视图的单页Web应用。当请求一个url时,根据...

网友评论

      本文标题:Angular路由深入讲解

      本文链接:https://www.haomeiwen.com/subject/xpsvrxtx.html