这是SitePoint Angular 2+教程的第4部分,有关如何使用Angular CLI创建CRUD应用程序。 在本文中,我们将介绍Angular Router,并了解当浏览器URL更改时它如何更新我们的应用程序,反之亦然。 我们还将学习如何使用路由器更新应用程序,以解析来自后端API的数据。

在第一部分中,我们学习了如何启动和运行Todo应用程序并将其部署到GitHub页面。 这样做很好,但是不幸的是,整个应用程序都挤在一个组件中。

在第二部分中,我们研究了模块化程度更高的组件体系结构,并学习了如何将单个组件分解为较小的组件的结构化树,这些树更易于理解,重用和维护。

在第三部分中,我们更新了应用程序以使用RxJS和Angular的HTTP服务与REST API后端进行通信。

  1. 第0部分— Ultimate Angular CLI参考指南
  2. 第1部分-启动并运行我们的Todo应用程序的第一个版本
  3. 第2部分-创建单独的组件以显示待办事项列表和一个待办事项
  4. 第3部分-更新Todo服务以与REST API通信
  5. 第4部分-使用Angular路由器解析数据
  6. 第5部分-添加身份验证以保护私有内容
  7. 第6部分-如何将Angular项目更新到最新版本。

不用担心 您无需遵循本教程的第一部分,第二部分或第三部分,就可以使第四部分有意义。 您可以简单地获取一份我们的仓库的副本,从第三部分中检出代码,然后以此为起点。 下面将对此进行详细说明。

启动并运行

确保已安装最新版本的Angular CLI。 如果没有安装,则可以使用以下命令进行安装:

npm install -g @angular/cli@latest

如果您需要删除以前版本的Angular CLI,则可以执行以下操作:

npm uninstall -g @angular/cli angular-cli
npm cache clean
npm install -g @angular/cli@latest

之后,您将需要第三部分的代码副本。 可以在GitHub上找到 。 本系列中的每篇文章在存储库中都有一个相应的标记,因此您可以在应用程序的不同状态之间来回切换。

我们在第三部分结尾并且在本文开始的代码被标记为第三部分 。 本文结尾处的代码被标记为part-4 。

您可以将标签视为特定提交ID的别名。 您可以使用git checkout在它们之间切换。 您可以在此处内容 。

因此,要启动并运行(安装了最新版本的Angular CLI),我们可以这样做:

git clone git@github.com:sitepoint-editors/angular-todo-app.git
cd angular-todo-app
git checkout part-3
npm install
ng serve

然后访问http:// localhost:4200 / 。 如果一切顺利,您应该会看到正在运行的Todo应用程序。

快速回顾

这是第3部分结尾处的应用程序体系结构:

在本文中,我们将:

  • 了解为什么应用程序可能需要路由
  • 了解什么是JavaScript路由器
  • 了解什么是Angular Router,如何工作以及可以为您做什么
  • 设置Angular Router并为我们的应用程序配置路由
  • 创建一个解析器以从我们的REST API获取待办事项
  • 使用新的解析器更新我们的应用程序以获取待办事项。

到本文结尾,您将了解:

  • 什么时候以及为什么您的应用程序可能需要路由
  • 服务器上的路由和浏览器中的路由之间的区别
  • 什么是Angular Router以及它可以为您的应用程序做什么
  • 如何设置角度路由器
  • 如何为您的应用程序配置路由
  • 如何告诉Angular Router在DOM中放置组件的位置
  • 如何正常处理未知URL
  • 什么是解析器及其用途
  • 如何使用解析器使用Angular Router解析数据。

所以,让我们开始吧!

为什么要布线?

在当前状态下,我们的Web应用程序未考虑浏览器URL。

我们通过一个URL(例如http://localhost:4200访问我们的应用程序,而我们的应用程序不知道其他任何URL(例如http://localhost:4200/todos

大多数Web应用程序需要支持不同的URL才能将用户导航到应用程序中的不同页面。 那就是路由器进来的地方。

在传统网站中,路由是由服务器上的路由器处理的:

  1. 用户单击浏览器中的链接,导致URL更改
  2. 浏览器向服务器发送HTTP请求
  3. 服务器从HTTP请求中读取URL并生成适当的HTTP响应
  4. 服务器将HTTP响应发送到浏览器。

在现代JavaScript Web应用程序中,路由通常由浏览器中的JavaScript路由器处理。

什么是JavaScript路由器?

本质上,JavaScript路由器执行以下两项操作:

  1. 当浏览器URL更改时更新Web应用程序状态
  2. 当Web应用程序状态更改时,更新浏览器URL。

JavaScript路由器使我们能够开发单页应用程序(SPA)。

SPA是一种Web应用程序,可提供类似于桌面应用程序的用户体验。 在SPA中,与后端的所有通信都在后台进行。

当用户从一个页面导航到另一页面时,即使URL更改了,页面也将动态更新而无需重新加载。

有许多不同的JavaScript路由器实现。

其中一些是为特定的JavaScript框架(例如Angular , Ember , React , Vue.js和Aurelia等)专门编写的。其他实现是出于通用目的而构建的,并不与特定的框架绑定。

什么是角路由器?

Angular Router是由Angular Core团队编写和维护的官方Angular路由库。

这是一个JavaScript路由器实现,旨在与Angular一起使用,并打包为@angular/router

首先,Angular Router负责JavaScript路由器的职责:

  • 当用户导航到某个URL时,它将激活所有必需的Angular组件以组成一个页面
  • 它使用户可以从一页导航到另一页而无需重新加载页面
  • 它会更新浏览器的历史记录,以便用户在页面之间来回导航时可以使用后退前进按钮。

此外,Angular Router还使我们能够:

  • 将一个URL重定向到另一个URL
  • 在显示页面之前解析数据
  • 激活或停用页面时运行脚本
  • 我们应用程序的延迟加载部分。

在本文中,我们将学习如何设置和配置Angular Router,如何重定向URL以及如何使用Angular Router通过后端API解析待办事项。

在下一篇文章中,我们将向我们的应用程序添加身份验证,并使用路由器确保只有在用户登录后才能访问某些页面。

角路由器如何工作

在深入研究代码之前,了解Angular Router的工作方式及其引入的术语非常重要。

当用户导航到页面时,Angular Router依次执行以下步骤:

  1. 它读取用户想要导航到的浏览器URL
  2. 它应用URL重定向(如果已定义)
  3. 它找出哪个路由器状态对应于URL
  4. 它运行在路由器状态中定义的防护
  5. 它解析路由器状态所需的数据
  6. 它激活Angular组件以显示页面
  7. 它管理导航并在请求新页面时重复上述步骤。

为了完成其任务,Angular Router引入了以下术语和概念:

  • 路由器服务 :我们应用程序中的全局Angular路由器服务
  • 路由器配置 :定义我们的应用程序可以位于的所有可能的路由器状态
  • 路由器状态 :路由器在某个时间点的状态,表示为激活的路由快照的树
  • 激活的路由快照 :提供对路由器状态节点的URL,参数和数据的访问
  • guard :在加载,激活或停用路由时运行的脚本
  • 解析器 :在激活请求的页面之前获取数据的脚本
  • 路由器出口 :Angular Router可以在其中放置激活组件的DOM中的位置。

不用担心该术语听起来很压倒性。 当我们在本系列中逐步解决这些术语时,您会习惯这些术语,并且您在Angular Router上获得了更多的经验。

使用Angular路由器的Angular应用程序只有一个路由器服务实例:这是一个单例。 无论何时何地在应用程序中注入Router服务,您都可以访问相同的Angular Router服务实例。

要更深入地了解Angular路由过程,请确保检查出Angular Router导航的7步路由过程 。

启用路由

要在Angular应用程序中启用路由,我们需要做三件事:

  1. 创建一个路由配置,为我们的应用程序定义可能的状态
  2. 将路由配置导入我们的应用程序
  3. 添加一个路由器出口,以告诉Angular Router将激活的组件放置在DOM中的何处。

因此,让我们从创建路由配置开始。

创建路由配置

要创建路由配置,我们需要我们希望应用程序支持的URL列表。

当前,我们的应用程序非常简单,只有一个页面显示待办事项列表:

  • / :显示待办事项清单

它将待办事项列表显示为我们应用程序的主页。

但是,当用户在浏览器中为/书签以查看待办事项列表时,如果我们更改了主页的内容(我们将在本系列的第5部分中进行此操作),则他们的书签将不再显示待办事项列表。

因此,让我们给待办事项列出自己的URL并将我们的主页重定向到该URL:

  • / :重定向到/todos
  • /todos :显示待办事项列表。

这为我们提供了两个好处:

  • 当用户将待办事项页面添加为书签时,即使我们更改了主页内容,他们的浏览器也会将/todos而不是/书签,这将继续按预期工作
  • 现在,我们可以通过将其重定向到我们喜欢的任何URL来轻松地更改主页,如果您需要定期更改主页内容,这将非常方便。

官方Angular样式指南建议将Angular模块的路由配置存储在文件名中,该文件名以-routing.module.ts结尾,该文件将导出一个单独的Angular模块,其名称以RoutingModule结尾。

我们当前的模块称为AppModule ,因此我们创建一个文件src/app/app-routing.module.ts并将路由配置导出为一个名为AppRoutingModule的Angular模块:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app.component';const routes: Routes = [{path: '',redirectTo: 'todos',pathMatch: 'full'},{path: 'todos',component: AppComponent}
];@NgModule({imports: [RouterModule.forRoot(routes)],exports: [RouterModule],providers: []
})
export class AppRoutingModule {
}

首先,我们从@angular/router导入RouterModuleRoutes

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

接下来,我们定义类型为Routes的可变routes ,并为其分配路由器配置:

const routes: Routes = [{path: '',redirectTo: 'todos',pathMatch: 'full'},{path: 'todos',component: AppComponent}
];

Routes类型是可选的,可让具有TypeScript支持的IDE或TypeScript编译器在开发过程中方便地验证您的路由配置。

路由器配置代表我们的应用程序可以处于的所有可能的路由器状态。

它是路由树,定义为JavaScript数组,其中每个路由可以具有以下属性:

  • path :字符串,匹配URL的路径
  • pathMatch :字符串,如何匹配URL
  • component :类引用,激活此路由时要激活的组件
  • redirectTo :字符串,激活此路由后重定向到的URL
  • data :分配给路由的静态数据
  • resolve解决动态数据并在解析后与数据合并
  • 儿童 :儿童路线。

我们的应用程序很简单,仅包含两个同级路由,但是较大的应用程序可能具有带有子路由的路由器配置,例如:

const routes: Routes = [{path: '',redirectTo: 'todos',pathMatch: 'full'},{path: 'todos',children: [{path: '',component: 'TodosPageComponent'},{path: ':id',component: 'TodoPageComponent'}]}
];

在这里, todos有两个子路由,而:id是一个路由参数,使路由器能够识别以下URL:

  • / :主页,重定向到/todos
  • /todos :激活TodosPageComponent并显示TodosPageComponent列表
  • /todos/1 :激活TodoPageComponent并将:id参数的值设置为1
  • /todos/2 :激活TodoPageComponent并将:id参数的值设置为2

注意定义重定向时我们如何指定pathMatch: 'full'

Angular Router具有两种匹配策略:

  • prefix :默认值,当URL path的值开头时匹配
  • full :URL 等于 path的值时匹配。

我们可以创建以下路线:

// no pathMatch specified, so Angular Router applies
// the default `prefix` pathMatch
{path: '',redirectTo: 'todos'
}

在这种情况下,Angular Router应用默认的prefix路径匹配策略,并且每个URL都重定向到todos因为每个URL都以path指定的空字符串'' 开头

我们只希望将主页重定向到todos ,因此我们添加pathMatch: 'full'以确保仅匹配等于空字符串''的URL:

{path: '',redirectTo: 'todos',pathMatch: 'full'
}

要了解有关不同的路由配置选项的更多信息,请查看有关路由和导航的正式Angular文档。

最后,我们创建并导出一个Angular模块AppRoutingModule

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

有两种创建路由模块的方法:

  1. RouterModule.forRoot(routes) :创建一个路由模块,其中包括路由器指令,路由配置路由器服务
  2. RouterModule.forChild(routes) :创建一个路由模块,其中包含路由器指令,路由配置, 但不包括路由器服务。

当您的应用程序具有多个路由模块时,需要RouterModule.forChild()方法。

请记住,路由器服务负责应用程序状态和浏览器URL之间的同步。 实例化与同一浏览器URL交互的多个路由器服务会导致问题,因此,无论我们在应用程序中导入了多少个路由模块,我们的应用程序中只有一个路由器服务实例非常重要。

当我们导入使用RouterModule.forRoot()创建的路由模块时,Angular将实例化路由器服务。 当我们导入使用RouterModule.forChild()创建的路由模块时,Angular将不会实例化路由器服务。

因此,对于其他路由模块,我们只能使用一次RouterModule.forRoot() ,并多次使用RouterModule.forChild()

因为我们的应用程序只有一个路由模块,所以我们使用RouterModule.forRoot()

imports: [RouterModule.forRoot(routes)]

另外,我们还在exports属性中指定RouterModule

exports: [RouterModule]

这确保了我们没有明确导入RouterModule再次AppModuleAppModule进口AppRoutingModule

现在我们有了AppRoutingModule ,我们需要将其导入到AppModule以启用它。

导入路由配置

要将路由配置导入到我们的应用程序中,我们必须将AppRoutingModule导入到主AppModule

让我们打开src/app/app.module.ts并将AppRoutingModule添加到AppModule@NgModule元数据中的imports数组中:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';import { AppComponent } from './app.component';
import { TodoListComponent } from './todo-list/todo-list.component';
import { TodoListFooterComponent } from './todo-list-footer/todo-list-footer.component';
import { TodoListHeaderComponent } from './todo-list-header/todo-list-header.component';
import { TodoDataService } from './todo-data.service';
import { TodoListItemComponent } from './todo-list-item/todo-list-item.component';
import { ApiService } from './api.service';
import { AppRoutingModule } from './app-routing.module';@NgModule({declarations: [AppComponent,TodoListComponent,TodoListFooterComponent,TodoListHeaderComponent,TodoListItemComponent],imports: [AppRoutingModule,BrowserModule,FormsModule,HttpModule],providers: [TodoDataService, ApiService],bootstrap: [AppComponent]
})
export class AppModule {
}

由于AppRoutingModuleRoutingModule在其上市exports财产,角将导入RoutingModule自动当我们导入AppRoutingModule ,所以我们没有明确导入RouterModule再次(虽然这样做不会造成任何伤害)。

在尝试在浏览器中进行更改之前,我们需要完成第三步也是最后一步。

添加路由器插座

尽管我们的应用程序现在具有路由配置,但是我们仍然需要告诉Angular Router它将实例化的组件放置在DOM中的位置。

当我们的应用程序被引导时,Angular实例化AppComponent因为AppComponentbootstrap属性中列出了AppModule

@NgModule({// ...bootstrap: [AppComponent]
})
export class AppModule {
}

为了告诉Angular Router可以在哪里放置组件,我们必须在AppComponent的HTML模板中添加<router-outlet></router-outlet>元素。

<router-outlet></router-outlet>元素告诉Angular Router可以在哪里实例化DOM中的组件。

如果您熟悉AngularJS 1.x路由器和UI-Router ,可以考虑<router-outlet></router-outlet>替代ng-viewui-view的Angular。

如果没有<router-outlet></router-outlet>元素,Angular Router将不知道组件的放置位置,并且仅AppComponent自己的HTML。

AppComponent当前显示AppComponent列表。

但是,我们现在不希望AppComponent显示AppComponent列表,而是希望AppComponent包含<router-outlet></router-outlet>并告诉Angular Router实例化AppComponent内部的另一个组件以显示AppComponent列表。

为此,让我们使用Angular CLI生成一个新组件TodosComponent

$ ng generate component Todos

让我们还将所有HTML从src/app/app.component.html移到src/app/todos/todos.component.html

<!-- src/app/todos/todos.component.html -->
<section class="todoapp"><app-todo-list-header(add)="onAddTodo($event)"></app-todo-list-header><app-todo-list[todos]="todos"(toggleComplete)="onToggleTodoComplete($event)"(remove)="onRemoveTodo($event)"></app-todo-list><app-todo-list-footer[todos]="todos"></app-todo-list-footer>
</section>

让我们还将所有逻辑从src/app/app.component.ts移到src/app/todos/todos.component.ts

/* src/app/todos/todos.component.ts */
import { Component, OnInit } from '@angular/core';
import { TodoDataService } from '../todo-data.service';
import { Todo } from '../todo';@Component({selector: 'app-todos',templateUrl: './todos.component.html',styleUrls: ['./todos.component.css'],providers: [TodoDataService]
})
export class TodosComponent implements OnInit {todos: Todo[] = [];constructor(private todoDataService: TodoDataService) {}public ngOnInit() {this.todoDataService.getAllTodos().subscribe((todos) => {this.todos = todos;});}onAddTodo(todo) {this.todoDataService.addTodo(todo).subscribe((newTodo) => {this.todos = this.todos.concat(newTodo);});}onToggleTodoComplete(todo) {this.todoDataService.toggleTodoComplete(todo).subscribe((updatedTodo) => {todo = updatedTodo;});}onRemoveTodo(todo) {this.todoDataService.deleteTodoById(todo.id).subscribe((_) => {this.todos = this.todos.filter((t) => t.id !== todo.id);});}
}

现在,我们可以更换AppComponent的模板中src/app/app.component.html有:

<router-outlet></router-outlet>

我们也可以删除所有过时代码AppComponent的类src/app/app.component.ts

import { Component } from '@angular/core';@Component({selector: 'app-root',templateUrl: './app.component.html',styleUrls: ['./app.component.css'],
})
export class AppComponent {}

最后,我们更新src/app/app-routing.module.ts todos路由,以实例化TodosComponent而不是AppComponent

const routes: Routes = [{path: '',redirectTo: 'todos',pathMatch: 'full'},{path: 'todos',component: TodosComponent}
];

现在,当我们的应用程序启动时,Angular实例化AppComponent并找到一个<router-outlet></router-outlet> ,Angular Router可以在其中实例化和激活组件。

让我们尝试在浏览器中进行更改。

通过运行以下命令启动开发服务器和后端API:

$ ng serve
$ npm run json-server

然后将浏览器导航到http://localhost:4200

Angular Router会读取路由器配置,并自动将浏览器重定向到http://localhost:4200/todos

如果检查页面上的元素,则会看到TodosComponent不在<router-outlet></router-outlet>内部呈现,而是在其旁边呈现:

<app-root><!-- Angular Router finds router outlet --><router-outlet></router-outlet><!-- and places the component right next to it, NOT inside it --><app-todos></app-todos>
</app-root>

我们的应用程序现已启用路由。 太棒了!

添加通配符路由

当您将浏览器导航到http://localhost:4200/unmatched-url并打开浏览器的开发人员工具时,您会注意到Angular Router将以下错误记录到控制台:

Error: Cannot match any routes. URL Segment: 'unmatched-url'

为了优雅地处理不匹配的URL,我们需要做两件事:

  1. 创建PageNotFoundComponent (如果需要,可以命名不同)以显示一条友好消息,即找不到所请求的页面
  2. 当没有路由与请求的URL匹配时,告诉Angular Router显示PageNotFoundComponent

让我们从使用Angular CLI生成PageNotFoundComponent开始:

$ ng generate component PageNotFound

然后在src/app/page-not-found/page-not-found.component.html编辑其模板:

<p>We are sorry, the requested page could not be found.</p>

接下来,我们使用**作为路径添加通配符路由:

const routes: Routes = [{path: '',redirectTo: 'todos',pathMatch: 'full'},{path: 'todos',component: AppComponent},{path: '**',component: PageNotFoundComponent}
];

**匹配任何URL,包括子路径。

现在,如果将浏览器导航到http://localhost:4200/unmatched-url PageNotFoundComponent http://localhost:4200/unmatched-urlPageNotFoundComponent显示PageNotFoundComponent

请注意,通配符路由必须是我们路由配置中的最后一条路由,才能按预期工作。

当Angular Router将请求URL与路由器配置匹配时,一旦找到第一个匹配项,它将立即停止处理。

因此,如果我们要将路线的顺序更改为此:

const routes: Routes = [{path: '',redirectTo: 'todos',pathMatch: 'full'},{path: '**',component: PageNotFoundComponent},{path: 'todos',component: AppComponent}
];

然后todos将永远不会达到和PageNotFoundComponent将显示,因为通配符路线将首先匹配。

我们已经做了很多工作,所以让我们快速回顾一下到目前为止已经完成的工作:

  • 我们设置了角路由器
  • 我们为应用程序创建了路由配置
  • 我们重构AppComponentTodosComponent
  • 我们在AppComponent的模板中添加了<router-outlet></router-outlet>
  • 我们添加了通配符路由,以正常处理不匹配的网址。

接下来,我们将创建解析器以使用Angular Router从我们的后端API中获取现有的待办事项。

使用Angular路由器解析数据

在本系列的第3部分中,我们已经学习了如何使用Angular HTTP服务从后端API提取数据。

当前,当我们将浏览器导航到todos URL时,会发生以下情况:

  1. Angular Router匹配todos URL
  2. Angular Router激活TodosComponent
  3. Angular Router将TodosComponent放置在TodosComponent中的<router-outlet></router-outlet>旁边
  4. TodosComponent在浏览器中显示,带有一个空的todos数组
  5. ngOnInit是从TodosComponentngOnInit处理程序中的API获取的
  6. 浏览器中的TodosComponent会使用从API获取的TodosComponent进行更新。

如果在步骤5中加载待办事项需要三秒钟,则在步骤6中显示实际待办事项之前,将向用户显示一个空的待办事项列表三秒钟。

如果TodosComponent的模板中包含以下HTML:

<div *ngIf="!todos.length">You currently do not have any todos yet.
</div>

那么用户会在显示实际待办事项之前看到此消息三秒钟,这可能会完全误导用户并导致用户在输入实际数据之前先离开。

我们可以在TodosComponent中添加一个加载器,以在加载数据时显示一个微调TodosComponent ,但是有时我们可能无法控制实际的组件,例如,当我们使用第三方组件时。

要解决此不良行为,我们需要进行以下操作:

  1. Angular Router匹配todos URL
  2. Angular Router从API获取待办事项
  3. Angular Router激活TodosComponent
  4. Angular Router将TodosComponent放置在TodosComponent中的<router-outlet></router-outlet>旁边
  5. TodosComponent显示在浏览器中,并带有从API提取的TodosComponent

在这里,直到来自我们的API后端的数据可用时, TodosComponent显示TodosComponent

这正是解析器可以为我们做的。

为了让Angular Router在激活TodosComponent之前解析TodosComponent ,我们必须做两件事:

  1. 创建一个从API获取TodosResolver
  2. 在激活todos路由中的TodosComponent时,告诉Angular Router使用TodosResolver来获取TodosComponent

通过将解析器连接到todos路由,我们要求Angular Router在激活TodosComponent之前先解析数据。

因此,让我们创建一个解析器以获取我们的待办事项。

创建TodosResolver

Angular CLI没有用于生成解析器的命令,因此让我们手动创建一个新文件src/todos.resolver.ts并添加以下代码:

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { Todo } from './todo';
import { TodoDataService } from './todo-data.service';@Injectable()
export class TodosResolver implements Resolve<Observable<Todo[]>> {constructor(private todoDataService: TodoDataService) {}public resolve(route: ActivatedRouteSnapshot,state: RouterStateSnapshot): Observable<Todo[]> {return this.todoDataService.getAllTodos();}
}

我们将解析器定义为实现Resolve接口的类。

Resolve接口是可选的,但是让我们的TypeScript IDE或编译器通过要求我们实现resolve()方法来确保我们正确地实现了该类。

当Angular Router需要使用解析器解析数据时,它将调用解析器的resolve()方法并期望resolve()方法返回一个值,一个promise或一个observable。

如果resolve()方法返回一个Promise或可观察到的Angular Router将在激活路由的组件之前等待该Promise或可观察到的完成。

当调用resolve()方法时,Angular Router可以方便地传递已激活的路由快照和路由器状态快照,以使我们可以访问数据(例如,路由参数或查询参数),我们可能需要解析数据。

TodosResolver的代码非常简洁,因为我们已经有一个TodoDataService来处理与API后端的所有通信。

我们注入TodoDataService在构造和使用其getAllTodos()方法中获取所有待办事项resolve()方法。

resolve方法返回类型为Todo[]的可观察对象,因此Angular Router将在激活路由组件之前等待可观察对象完成。

现在我们有了解析器,让我们配置Angular Router来使用它。

通过路由器解析待办事项

为了使Angular Router使用解析器,我们必须将其附加到路由配置中的路由上。

让我们打开src/app-routing.module.ts并将我们的TodosResolver添加到todos路由中:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { TodosComponent } from './todos/todos.component';
import { TodosResolver } from './todos.resolver';const routes: Routes = [{path: '',redirectTo: 'todos',pathMatch: 'full'},{path: 'todos',component: TodosComponent,resolve: {todos: TodosResolver}},{path: '**',component: PageNotFoundComponent}
];@NgModule({imports: [RouterModule.forRoot(routes)],exports: [RouterModule],providers: [TodosResolver]
})
export class AppRoutingModule {
}

我们导入TodosResolver

import { TodosResolver } from './todos.resolver';

todos它添加为todos路线的解析器:

{path: 'todos',component: TodosComponent,resolve: {todos: TodosResolver}
}

这告诉Angular Router使用TodosResolver解析数据,并将解析器的返回值指定为路由数据中的todos

可以从ActivatedRouteActivatedRouteSnapshot访问路线的数据,我们将在下一部分中看到它们。

您可以使用路线的data属性将静态数据直接添加到路线的数据中:

{path: 'todos',component: TodosComponent,data: {title: 'Example of static route data'}
}

您还可以使用在路由的resolve属性中指定的解析器添加动态数据:

resolve: {path: 'todos',component: TodosComponent,resolve: {todos: TodosResolver}
}

您也可以同时做两个:

resolve: {path: 'todos',component: TodosComponent,data: {title: 'Example of static route data'}resolve: {todos: TodosResolver}
}

一旦解析来自resolve属性的解析器,它们的值就会与来自data属性的静态数据合并,并且所有数据都将用作路径的数据。

Angular Router使用Angular依赖注入来访问解析器,因此我们必须确保将TodosResolver注册到Angular的依赖注入系统中,方法是将其添加到AppRoutingModule@NgModule元数据中的providers属性中:

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

当您将浏览器导航到http://localhost:4200 ,Angular Router现在:

  1. 将URL从/重定向到/todos
  2. 看到todos路由在resolve属性中定义了TodosResolver
  3. TodosResolver运行resolve()方法,等待结果并将结果分配给路线数据中的todos
  4. 激活TodosComponent

如果打开开发人员工具的“网络”标签,您会看到待办事项现在已从API提取了两次。 一旦被角路由器,一旦由ngOnInit在处理TodosComponent

因此,Angular Router已经从API提取了TodosComponent ,但是TodosComponent仍然使用其自己的内部逻辑来加载待办事项。

在下一节中,我们将更新TodosComponent以使用Angular Router解析的数据。

使用已解析的数据

让我们打开app/src/todos/todos.component.ts

ngOnInit()处理程序当前直接从API获取ngOnInit()

public ngOnInit() {this.todoDataService.getAllTodos().subscribe((todos) => {this.todos = todos;});
}

现在,Angular Router使用TodosResolver来获取TodosResolver ,我们想从路由数据而非API中获取TodosComponent中的TodosComponent

要访问路线数据,我们必须从@angular/router导入ActivatedRoute

import { ActivatedRoute } from '@angular/router';

并使用Angular依赖注入来获取已激活路由的句柄:

constructor(private todoDataService: TodoDataService,private route: ActivatedRoute
) {
}

最后,我们更新ngOnInit()处理函数,以从路线数据而非API获取ngOnInit()

public ngOnInit() {this.route.data.map((data) => data['todos']).subscribe((todos) => {this.todos = todos;});
}

ActivatedRoute将路由数据公开为可观察的,因此我们的代码几乎不变。

我们用this.route.data.map((data) => data['todos'])替换this.todoDataService.getAllTodos() ,其余所有代码保持不变。

如果将浏览器导航到localhost:4200并打开“网络”标签,您将不再看到两个HTTP请求从API提取待办事项。

任务完成! 我们已成功将Angular Router集成到我们的应用程序中!

在总结之前,让我们运行单元测试:

ng serve

一个单元测试失败:

Executed 11 of 11 (1 FAILED)
TodosComponent should create FAILED'app-todo-list-header' is not a known element

在测试TodosComponent ,测试平台不知道TodoListHeaderComponent ,因此Angular抱怨它不知道app-todo-list-header元素。

要解决此错误,我们打开app/src/todos/todos.component.spec.ts并将NO_ERRORS_SCHEMA添加到TestBed选项中:

beforeEach(async(() => {TestBed.configureTestingModule({declarations: [TodosComponent],schemas: [NO_ERRORS_SCHEMA]}).compileComponents();
}));

现在,业力显示另一个错误:

Executed 11 of 11 (1 FAILED)
TodosComponent should create FAILEDNo provider for ApiService!

让我们将必要的提供程序添加到测试平台选项中:

beforeEach(async(() => {TestBed.configureTestingModule({declarations: [TodosComponent],schemas: [NO_ERRORS_SCHEMA],providers: [TodoDataService,{provide: ApiService,useClass: ApiMockService}],}).compileComponents();
}));

这再次引发另一个错误:

Executed 11 of 11 (1 FAILED)
TodosComponent should create FAILEDNo provider for ActivatedRoute!!

让我们在测试床选项中添加一个更多的ActivatedRoute提供程序:

beforeEach(async(() => {TestBed.configureTestingModule({declarations: [TodosComponent],schemas: [NO_ERRORS_SCHEMA],providers: [TodoDataService,{provide: ApiService,useClass: ApiMockService},{provide: ActivatedRoute,useValue: {data: Observable.of({todos: []})}}],}).compileComponents();
}));

我们为ActivatedRoute的提供者分配了一个模拟对象,该对象包含一个可观察的data属性,以显示todos的测试值。

现在,单元测试成功通过:

Executed 11 of 11 SUCCESS

极好! 要将我们的应用程序部署到生产环境中,我们现在可以运行:

ng build --aot --environment prod

我们将生成的dist目录上载到我们的托管服务器。 那有多甜?

我们已经在本文中介绍了很多内容,所以让我们回顾一下我们学到的东西。

摘要

在第一篇文章中 ,我们学习了如何:

  • 使用Angular CLI初始化我们的Todo应用程序
  • 创建一个Todo类来代表单个Todo
  • 创建TodoDataService服务以创建,更新和删除待办事项
  • 使用AppComponent组件显示用户界面
  • 将我们的应用程序部署到GitHub页面

在第二篇文章中 ,我们将AppComponent重构为将其大部分工作委托给:

  • TodoListComponent以显示TodoListComponent列表
  • TodoListItemComponent以显示单个待办事项
  • 一个TodoListHeaderComponent来创建一个新的待办事项
  • TodoListFooterComponent来显示还剩下多少个TodoListFooterComponent

在第三篇文章中 ,我们学习了如何:

  • 创建一个模拟REST API后端
  • 将API URL存储为环境变量
  • 创建一个ApiService与REST API通信
  • 更新TodoDataService以使用新的ApiService
  • 更新AppComponent以处理异步API调用
  • 创建一个ApiMockService以避免在运行单元测试时进行真正的HTTP调用。

在第四篇文章中,我们了解到:

  • 为什么应用程序可能需要路由
  • 什么是JavaScript路由器
  • 什么是Angular Router,它如何工作以及它能为您做什么
  • 如何为我们的应用程序设置Angular Router和配置路由
  • 如何告诉Angular Router在DOM中放置组件的位置
  • 如何正常处理未知URL
  • 如何使用解析器让Angular Router解析数据。

这篇文章中的所有代码都可以在GitHub上找到 。

在第五部分中,我们将实现身份验证,以防止未经授权访问我们的应用程序。

因此,请继续关注更多信息,并且与往常一样,随时在评论中留下您的想法和问题!

推荐课程

Angular和TypeScript在线课程
托德·格托
专家指导的针对个人和团队的在线AngularJS,Angular和TypeScript培训课程。 结帐时使用优惠券代码'SITEPOINT'可获得25%的折扣

From: https://www.sitepoint.com/component-routing-angular-router/

Angular Router的组件路由介绍相关推荐

  1. angular复用路由组件_Angular Router的组件路由简介

    angular复用路由组件 这是SitePoint Angular 2+教程的第4部分,有关如何使用Angular CLI创建CRUD应用程序. 在本文中,我们将介绍Angular Router,并了 ...

  2. Solr router 路由介绍

    目录 1.compositeId路由 1.1.compositeId路由原理 1.2.compositeId路由查询 2.implicit路由 2.1.implicit路由原理 2.2.implici ...

  3. Angular中实现动态路由跳转并传递参数

    场景 Angular介绍.安装Angular Cli.创建Angular项目入门教程: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/detail ...

  4. 【硬核解说】一口气讲明白Angular的5种路由守卫RouteGuard是嘛玩意儿

    Angular的常用路由守卫有5种,按照执行顺序: ① CanLoad:进入到当前路由的时候触发(若用户没有权限访问,相应的模块并不会被加载.这里是指对应组件的代码). ↓ ② CanAcitivat ...

  5. Angular动态创建组件之Portals

    本文主要介绍使用Angular api 和 CDK Portals两种方式实现动态创建组件,另外还会讲一些跟它相关的内容.如:Angular多级依赖注入.ViewContainerRef,Portal ...

  6. Angular 2+ 监听路由变化动态设置页面标题

    原网址:https://segmentfault.com/a/1190000009971757 路由配置代码:const routes: Routes = [{ path: 'calendar', c ...

  7. 使用Angular Router导航基础

    名称 简介 Routes 路由配置,保存着那个URL对应着哪个组件,以及在哪个RouterOulet中展示组件. RouterOutlet 在HTML中标记路由内容呈现位置的占位符指令. Router ...

  8. angular学习6之路由可选参数和必选参数

    有路由配置如下 const routes:Routes = [{path:'/product/:title',component:ProductDetailComponent }] 在要导航到Prod ...

  9. React 路由基础--React路由介绍

    1.React路由介绍 现代的前端应用大多都是 SPA(单页应用程序)single page application,也就是只有一个 HTML 页面的应用程序.因为它的用户体 验更好.对服务器的压力更 ...

最新文章

  1. SQL语句中 left join 后用 on 还是 where,区别大了!
  2. 一大波物联网僵尸正在袭来,都有啥安全保护方法?
  3. php中并发读写文件冲突的解决方案(文件锁应用示例)
  4. HDU 4628 Pieces(DP + 状态压缩)
  5. Tkinter中常用的函数
  6. Sqlserver中 登录用户只能看到自己拥有权限的库
  7. 2021牛客多校4 - Rebuild Tree(树形dp)
  8. linux退出编辑器命令,LINUX中,Vi编辑器的几种模式及保存、退出等命令
  9. 【渝粤题库】国家开放大学2021春2402外国文学题目
  10. anaconda中怎么sh_【好工具】 深度学习炼丹,你怎么能少了这款工具!JupyterLab 远程访问指南...
  11. 辨异 —— 概率与统计
  12. tcp/ip协议栈总结
  13. 从零开始学PCR技术(二):Taq DNA酶
  14. 聚石塔RDS数据备份与迁移
  15. Spigot 算法之一 计算调和级数的和
  16. 视频教程-网络安全与渗透测试工程师-渗透测试
  17. signal(SIGCHLD, SIG_IGN)
  18. 在deepin运行imageapp程序
  19. 计算机二级两个控件之间求偶,求,全国计算机等级考试二级java历年试题及答案合集,还有上机考试真题?...
  20. DCHQ + EMC REX-Ray在多个云和虚拟化平台上交付容器数据库服务

热门文章

  1. Nginx优化与防盗链
  2. 中国半导体工业测试设备市场深度研究分析报告
  3. 【C语言】数字直角三角形,数字矩阵,蛇形数组
  4. 堆米微信H5页面怎么制作?易企秀微信H5页面制作,微信简历制作,
  5. CF311D Interval Cubing 数学、线段树
  6. Python爬取《明日之子》(腾讯视频)评论和弹幕,看看大家都在吐槽什么
  7. AAAI 2023 | CF-ViT:由粗到细的两阶段动态推理架构
  8. 数据库常用操作,会持续更新
  9. html表格填充随页面大小自动缩放,一个简单的html表格自适应解决方案
  10. 时间复杂度:1秒内能执行多少指令