Angular 4.x 路由快速入门

建了个群有兴趣的朋友能够加一下 QQ 群:Angular 修仙之路(1)群 - 153742079 (已满),请加 Angular 修仙之路(2)群 - 648681235。

路由是 Angular 应用程序的核心,它加载与所请求路由相关联的组件,以及获取特定路由的相关数据。这容许咱们经过控制不一样的路由,获取不一样的数据,从而渲染不一样的页面。html

接下来咱们将按照如下目录的内容,介绍 Angular 的路由。具体目录以下:node

目录

  • Installing the routertypescript

    • Base href
  • Using the routershell

    • RouterModule.forRoot
    • RouterModule.forChild
  • Configuring a route
  • Displaying routes
  • Futher configurationnpm

    • Dynamic routes
    • Child routes
    • Component-less routes
    • loadChildren
  • Router directivesbootstrap

    • routerLink
    • routerLinkActive
  • Router API

Installing the router

首先第一件事,咱们须要安装 Angular Router。你能够经过运行如下任一操做来执行此操做:数组

yarn add @angular/router
# OR
npm i --save @angular/router

以上命令执行后,将会自动下载 @angular/router 模块到 node_modules 文件夹中。app

Base href

咱们须要作的最后一件事,是将 <base> 标签添加到咱们的 index.html 文件中。路由须要根据这个来肯定应用程序的根目录。例如,当咱们转到 http://example.com/page1 时,若是咱们没有定义应用程序的基础路径,路由将没法知道咱们的应用的托管地址是 http://example.com 仍是 http://example.com/page1less

这件事操做起来很简单,只需打开项目中的 index.html 文件,添加相应的 <base> 标签,具体以下:dom

<!doctype html>
<html>
  <head>
    <base href="/">
    <title>Application</title>
  </head>
  <body>
    <app-root></app-root>
  </body>
</html>

以上配置信息告诉 Angular 路由,应用程序的根目录是 /

Using the router

要使用路由,咱们须要在 AppModule 模块中,导入 RouterModule 。具体以下:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';

import { AppComponent } from './app.component';

@NgModule({
  imports: [
    BrowserModule,
    RouterModule
  ],
  bootstrap: [
    AppComponent
  ],
  declarations: [
    AppComponent
  ]
})
export class AppModule {}

此时咱们的路由还不能正常工做,由于咱们还未配置应用程序路由的相关信息。RouterModule 对象为咱们提供了两个静态的方法:forRoot()forChild() 来配置路由信息。

RouterModule.forRoot()

RouterModule.forRoot() 方法用于在主模块中定义主要的路由信息,经过调用该方法使得咱们的主模块能够访问路由模块中定义的全部指令。接下来咱们来看一下如何使用 forRoot()

// ...
import { Routes, RouterModule } from '@angular/router';

export const ROUTES: Routes = [];

@NgModule({
  imports: [
    BrowserModule,
    RouterModule.forRoot(ROUTES)
  ],
  // ...
})
export class AppModule {}

咱们经过使用 const 定义路由的配置信息,而后把它做为参数调用 RouterModule.forRoot() 方法,而不是直接使用 RouterModule.forRoot([...]) 这种方式,这样作的好处是方便咱们在须要的时候导出 ROUTES 到其它模块中。

RouterModule.forChild()

RouterModule.forChild() 与 Router.forRoot() 方法相似,但它只能应用在特性模块中。

友情提示:根模块中使用 forRoot(),子模块中使用 forChild()

这个功能很是强大,由于咱们没必要在一个地方(咱们的主模块)定义全部路由信息。反之,咱们能够在特性模块中定义模块特有的路由信息,并在必要的时候将它们导入咱们主模块。RouterModule.forChild() 的使用方法以下:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Routes, RouterModule } from '@angular/router';

export const ROUTES: Routes = [];

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forChild(ROUTES)
  ],
  // ...
})
export class ChildModule {}

经过以上示例,咱们知道在主模块和特性模块中,路由配置对象的类型是同样的,区别只是主模块和特性模块中需调用不一样的方法,来配置模块路由。接下来咱们来介绍一下如何配置 ROUTES 对象。

Configuring a route

咱们定义的全部路由都是做为 ROUTES 数组中的对象。首先,为咱们的主页定义一个路由:

import { Routes, RouterModule } from '@angular/router';

import { HomeComponent } from './home/home.component';

export const ROUTES: Routes = [
  { path: '', component: HomeComponent }
];

@NgModule({
  imports: [
    BrowserModule,
    RouterModule.forRoot(ROUTES)
  ],
  // ...
})
export class AppModule {}

示例中咱们经过 path 属性定义路由的匹配路径,而 component 属性用于定义路由匹配时须要加载的组件。

友情提示:咱们使用 path: '' 来匹配空的路径,例如: https://yourdomain.com

Displaying routes

配置完路由信息后,下一步是使用一个名为 router-outlet 的指令告诉 Angular 在哪里加载组件。当 Angular 路由匹配到响应路径,并成功找到须要加载的组件时,它将动态建立对应的组件,并将其做为兄弟元素,插入到 router-outlet 元素以后。

在咱们 AppComponent 组件中,咱们能够在任意位置插入 router-outlet 指令:

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

@Component({
  selector: 'app-root',
  template: `
    <div class="app">
      <h3>Our app</h3>
      <router-outlet></router-outlet>
    </div>
  `
})
export class AppComponent {}

咱们如今已经创建了应用程序的主路由,咱们能够进一步了解路由的其它配置选项。

Further configuration

到目前为止咱们已经介绍的内容只是一个开始 ,接下来咱们来看看其它一些选项和功能。

Dynamic routes

若是路由始终是静态的,那没有多大的用处。例如 path: '' 是加载咱们 HomeComponent 组件的静态路由。咱们将介绍动态路由,基于动态路由咱们能够根据不一样的路由参数,渲染不一样的页面。

例如,若是咱们想要在我的资料页面根据不一样的用户名显示不一样的用户信息,咱们可使用如下方式定义路由:

import { HomeComponent } from './home/home.component';
import { ProfileComponent } from './profile/profile.component';

export const ROUTES: Routes = [
  { path: '', component: HomeComponent },
  { path: '/profile/:username', component: ProfileComponent }
];

这里的关键点是 : ,它告诉 Angular 路由,:username 是路由参数,而不是 URL 中实际的部分。

友情提示:若是没有使用 : ,它将做为静态路由,仅匹配 /profile/username 路径

如今咱们已经创建一个动态路由,此时最重要的事情就是如何获取路由参数。要访问当前路由的相关信息,咱们须要先从 @angular/router 模块中导入 ActivatedRoute ,而后在组件类的构造函数中注入该对象,最后经过订阅该对象的 params 属性,来获取路由参数,具体示例以下:

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

@Component({
  selector: 'profile-page',
  template: `
    <div class="profile">
      <h3>{{ username }}</h3>
    </div>
  `
})
export class SettingsComponent implements OnInit {
  username: string;
  constructor(private route: ActivatedRoute) {}
  ngOnInit() {
    this.route.params.subscribe((params) => this.username = params.username);
  }
}

介绍完动态路由,咱们来探讨一下如何建立 child routes

Child routes

实际上每一个路由都支持子路由,假设在咱们 /settings 设置页面下有 /settings/profile/settings/password 两个页面,分别表示我的资料页和修改密码页。

咱们可能但愿咱们的 / settings 页面拥有本身的组件,而后在设置页面组件中显示 / settings/profile/ settings/password 页面。咱们能够这样作:

import { SettingsComponent } from './settings/settings.component';
import { ProfileSettingsComponent } from './settings/profile/profile.component';
import { PasswordSettingsComponent } from './settings/password/password.component';

export const ROUTES: Routes = [
  { 
    path: 'settings', 
    component: SettingsComponent,
    children: [
      { path: 'profile', component: ProfileSettingsComponent },
      { path: 'password', component: PasswordSettingsComponent }
    ]
  }
];

@NgModule({
  imports: [
    BrowserModule,
    RouterModule.forRoot(ROUTES)
  ],
})
export class AppModule {}

在这里,咱们在 setttings 路由中定义了两个子路由,它们将继承父路由的路径,所以修改密码页面的路由匹配地址是 /settings/password ,依此类推。

接下来,咱们须要作的最后一件事是在咱们的 SettingsComponent 组件中添加 router-outlet 指令,由于咱们要在设置页面中呈现子路由。若是咱们没有在 SettingsComponent 组件中添加 router-outlet 指令,尽管 /settings/password 匹配修改密码页面的路由地址,但修改密码页面将没法正常显示。具体代码以下:

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

@Component({
  selector: 'settings-page',
  template: `
    <div class="settings">
      <settings-header></settings-header>
      <settings-sidebar></settings-sidebar>
      <router-outlet></router-outlet>
    </div>
  `
})
export class SettingsComponent {}

Component-less routes

另外一个颇有用的路由功能是 component-less 路由。使用 component-less 路由容许咱们将路由组合在一块儿,并让它们共享路由配置信息和 outlet。

例如,咱们能够定义 setttings 路由而不须要使用 SettingsComponent 组件:

import { ProfileSettingsComponent } from './settings/profile/profile.component';
import { PasswordSettingsComponent } from './settings/password/password.component';

export const ROUTES: Routes = [
  {
    path: 'settings',
    children: [
      { path: 'profile', component: ProfileSettingsComponent },
      { path: 'password', component: PasswordSettingsComponent }
    ]
  }
];

@NgModule({
  imports: [
    BrowserModule,
    RouterModule.forRoot(ROUTES)
  ],
})
export class AppModule {}

此时, /settings/profile/settings/password 路由定义的内容,将显示在 AppComponent 组件的 router-outlet 元素中。

loadChildren

咱们也能够告诉路由从另外一个模块中获取子路由。这将咱们谈论的两个想法联系在一块儿 - 咱们能够指定另外一个模块中定义的子路由,以及经过将这些子路由设置到特定的路径下,来充分利用 component-less 路由的功能。

让咱们建立一个 SettingsModule 模块,用来保存全部 setttings 相关的路由信息:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Routes, RouterModule } from '@angular/router';

export const ROUTES: Routes = [
  {
    path: '',
    component: SettingsComponent,
    children: [
      { path: 'profile', component: ProfileSettingsComponent },
      { path: 'password', component: PasswordSettingsComponent }
    ]
  }
];

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forChild(ROUTES)
  ],
})
export class SettingsModule {}

须要注意的是,在 SettingsModule 模块中咱们使用 forChild() 方法,由于 SettingsModule 不是咱们应用的主模块。

另外一个主要的区别是咱们将 SettingsModule 模块的主路径设置为空路径 ('')。由于若是咱们路径设置为 /settings ,它将匹配 /settings/settings ,很明显这不是咱们想要的结果。经过指定一个空的路径,它就会匹配 /settings 路径,这就是咱们想要的结果。

那么 /settings 路由信息,须要在哪里配置?答案是在 AppModule 中。这时咱们就须要用到 loadChildren 属性,具体以下:

export const ROUTES: Routes = [
  {
    path: 'settings',
    loadChildren: './settings/settings.module#SettingsModule'
  }
];

@NgModule({
  imports: [
    BrowserModule,
    RouterModule.forRoot(ROUTES)
  ],
  // ...
})
export class AppModule {}

须要注意的是,咱们没有将 SettingsModule 导入到咱们的 AppModule 中,而是经过 loadChildren 属性,告诉 Angular 路由依据 loadChildren 属性配置的路径去加载 SettingsModule 模块。这就是模块懒加载功能的具体应用,当用户访问 /settings/** 路径的时候,才会加载对应的 SettingsModule 模块,这减小了应用启动时加载资源的大小。

另外咱们传递一个字符串做为 loadChildren 的属性值,该字符串由三部分组成:

  • 须要导入模块的相对路径
  • # 分隔符
  • 导出模块类的名称

了解完路由的一些高级选项和功能,接下来咱们来介绍路由指令。

Router Directives

除了 router-outlet 指令,路由模块中还提供了一些其它指令。让咱们来看看它们如何与咱们以前介绍的内容结合使用。

routerLink

为了让咱们连接到已设置的路由,咱们须要使用 routerLink 指令,具体示例以下:

<nav>
  <a routerLink="/">Home</a>
  <a routerLink="/settings/password">Change password</a>
  <a routerLink="/settings/profile">Profile Settings</a>
</nav>

当咱们点击以上的任意连接时,页面不会被从新加载。反之,咱们的路径将在 URL 地址栏中显示,随后进行后续视图更新,以匹配 routerLink 中设置的值。

友情提示:咱们也能够将 routerLink 的属性值,改为数组形式,以便咱们传递特定的路由信息

若是咱们想要连接到动态的路由地址,且该地址有一个 username 的路由变量,则咱们能够按照如下方式配置 routerLink 对应的属性值:

<a [routerLink]="['/profile', username]">
  Go to {{ username }}'s profile.
</a>

routerLinkActive

在实际开发中,咱们须要让用户知道哪一个路由处于激活状态,一般状况下咱们经过向激活的连接添加一个 class 来实现该功能。为了解决上述问题,Angular 路由模块为咱们提供了 routerLinkActive 指令,该指令的使用示例以下:

<nav>
  <a routerLink="/settings" routerLinkActive="active">Home</a>
  <a routerLink="/settings/password" routerLinkActive="active">Change password</a>
  <a routerLink="/settings/profile" routerLinkActive="active">Profile Settings</a>
</nav>

经过使用 routerLinkActive 指令,当 a 元素对应的路由处于激活状态时,active 类将会自动添加到 a 元素上。

最后,咱们来简单介绍一下 Router API。

Router API

咱们能够经过路由还提供的 API 实现与 routerLink 相同的功能。要使用 Router API,咱们须要在组件类中注入 Router 对象,具体以下:

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

@Component({
  selector: 'app-root',
  template: `
    <div class="app">
      <h3>Our app</h3>
      <router-outlet></router-outlet>
    </div>
  `
})
export class AppComponent {
  constructor(private router: Router) {}
}

组件类中注入的 router 对象中有一个 navigate() 方法,该方法支持的参数类型与 routerLink 指令同样,当调用该方法后,页面将会自动跳转到对应的路由地址。具体使用示例以下:

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-root',
  template: `
    <div class="app">
      <h3>Our app</h3>
      <router-outlet></router-outlet>
    </div>
  `
})
export class AppComponent implements OnInit {
  constructor(private router: Router) {}
  ngOnInit() {
    setTimeout(() => {
      this.router.navigate(['/settings']);
    }, 5000);
  }
}

若以上代码成功运行,用户界面将在 5 秒后被重定向到 /settings 页面。这个方法很是有用,例如当检测到用户还没有登陆时,自动重定向到登陆页面。

另外一个使用示例是演示页面跳转时如何传递数据,具体以下:

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-root',
  template: `
    <div class="app">
      <h3>Users</h3>
      <div *ngFor="let user of users">
        <user-component 
          [user]="user"
          (select)="handleSelect($event)">
        </user-component>
      </div>
      <router-outlet></router-outlet>
    </div>
  `
})
export class AppComponent implements OnInit {
  users: Username[] = [
    { name: 'toddmotto', id: 0 },
    { name: 'travisbarker', id: 1 },
    { name: 'tomdelonge', id: 2 }
  ];
  
  constructor(private router: Router) {}
  
  handleSelect(event) {
    this.router.navigate(['/profile', event.name]);
  }
}

Angular 路由的功能很是强大,既可使用指令方式也可使用命令式 API,但愿本文能够帮助你尽快入门,若要进一步了解路由详细信息,请访问 - Angular Router 官文文档

我有话说

除了使用 navigate() 方法外还有没有其它方法能够实现页面导航?

Angular Router API 为咱们提供了 navigate()navigateByUrl() 方法来实现页面导航。那为何会有两个不一样的方法呢?

使用 router.navigateByUrl() 方法与直接改变地址栏上的 URL 地址同样,咱们使用了一个新的 URL 地址。然而 router.navigate() 方法基于一系列输入参数,产生一个新的 URL 地址。为了更好的区分它们之间的差别,咱们来看个例子,假设当前的 URL 地址是:

/inbox/11/message/22(popup:compose)

当咱们调用 router.navigateByUrl('/inbox/33/message/44') 方法后,此时的 URL 地址将变成 /inbox/33/message/44 。但若是咱们是调用 router.navigate('/inbox/33/message/44') 方法,当前的 URL 地址将变成 /inbox/33/message/44(popup:compose)

参考资源