Angular Router的组件路由介绍
这是SitePoint Angular 2+教程的第4部分,有关如何使用Angular CLI创建CRUD应用程序。 在本文中,我们将介绍Angular Router,并了解当浏览器URL更改时它如何更新我们的应用程序,反之亦然。 我们还将学习如何使用路由器更新应用程序,以解析来自后端API的数据。
在第一部分中,我们学习了如何启动和运行Todo应用程序并将其部署到GitHub页面。 这样做很好,但是不幸的是,整个应用程序都挤在一个组件中。
在第二部分中,我们研究了模块化程度更高的组件体系结构,并学习了如何将单个组件分解为较小的组件的结构化树,这些树更易于理解,重用和维护。
在第三部分中,我们更新了应用程序以使用RxJS和Angular的HTTP服务与REST API后端进行通信。
- 第0部分— Ultimate Angular CLI参考指南
- 第1部分-启动并运行我们的Todo应用程序的第一个版本
- 第2部分-创建单独的组件以显示待办事项列表和一个待办事项
- 第3部分-更新Todo服务以与REST API通信
- 第4部分-使用Angular路由器解析数据
- 第5部分-添加身份验证以保护私有内容
- 第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才能将用户导航到应用程序中的不同页面。 那就是路由器进来的地方。
在传统网站中,路由是由服务器上的路由器处理的:
- 用户单击浏览器中的链接,导致URL更改
- 浏览器向服务器发送HTTP请求
- 服务器从HTTP请求中读取URL并生成适当的HTTP响应
- 服务器将HTTP响应发送到浏览器。
在现代JavaScript Web应用程序中,路由通常由浏览器中的JavaScript路由器处理。
什么是JavaScript路由器?
本质上,JavaScript路由器执行以下两项操作:
- 当浏览器URL更改时更新Web应用程序状态
- 当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依次执行以下步骤:
- 它读取用户想要导航到的浏览器URL
- 它应用URL重定向(如果已定义)
- 它找出哪个路由器状态对应于URL
- 它运行在路由器状态中定义的防护
- 它解析路由器状态所需的数据
- 它激活Angular组件以显示页面
- 它管理导航并在请求新页面时重复上述步骤。
为了完成其任务,Angular Router引入了以下术语和概念:
- 路由器服务 :我们应用程序中的全局Angular路由器服务
- 路由器配置 :定义我们的应用程序可以位于的所有可能的路由器状态
- 路由器状态 :路由器在某个时间点的状态,表示为激活的路由快照的树
- 激活的路由快照 :提供对路由器状态节点的URL,参数和数据的访问
- guard :在加载,激活或停用路由时运行的脚本
- 解析器 :在激活请求的页面之前获取数据的脚本
- 路由器出口 :Angular Router可以在其中放置激活组件的DOM中的位置。
不用担心该术语听起来很压倒性。 当我们在本系列中逐步解决这些术语时,您会习惯这些术语,并且您在Angular Router上获得了更多的经验。
使用Angular路由器的Angular应用程序只有一个路由器服务实例:这是一个单例。 无论何时何地在应用程序中注入Router
服务,您都可以访问相同的Angular Router服务实例。
要更深入地了解Angular路由过程,请确保检查出Angular Router导航的7步路由过程 。
启用路由
要在Angular应用程序中启用路由,我们需要做三件事:
- 创建一个路由配置,为我们的应用程序定义可能的状态
- 将路由配置导入我们的应用程序
- 添加一个路由器出口,以告诉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
导入RouterModule
和Routes
:
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 {
}
有两种创建路由模块的方法:
RouterModule.forRoot(routes)
:创建一个路由模块,其中包括路由器指令,路由配置和路由器服务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
再次AppModule
时AppModule
进口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 {
}
由于AppRoutingModule
有RoutingModule
在其上市exports
财产,角将导入RoutingModule
自动当我们导入AppRoutingModule
,所以我们没有明确导入RouterModule
再次(虽然这样做不会造成任何伤害)。
在尝试在浏览器中进行更改之前,我们需要完成第三步也是最后一步。
添加路由器插座
尽管我们的应用程序现在具有路由配置,但是我们仍然需要告诉Angular Router它将实例化的组件放置在DOM中的位置。
当我们的应用程序被引导时,Angular实例化AppComponent
因为AppComponent
的bootstrap
属性中列出了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-view
和ui-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,我们需要做两件事:
- 创建
PageNotFoundComponent
(如果需要,可以命名不同)以显示一条友好消息,即找不到所请求的页面 - 当没有路由与请求的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-url
, PageNotFoundComponent
显示PageNotFoundComponent
。
请注意,通配符路由必须是我们路由配置中的最后一条路由,才能按预期工作。
当Angular Router将请求URL与路由器配置匹配时,一旦找到第一个匹配项,它将立即停止处理。
因此,如果我们要将路线的顺序更改为此:
const routes: Routes = [{path: '',redirectTo: 'todos',pathMatch: 'full'},{path: '**',component: PageNotFoundComponent},{path: 'todos',component: AppComponent}
];
然后todos
将永远不会达到和PageNotFoundComponent
将显示,因为通配符路线将首先匹配。
我们已经做了很多工作,所以让我们快速回顾一下到目前为止已经完成的工作:
- 我们设置了角路由器
- 我们为应用程序创建了路由配置
- 我们重构
AppComponent
到TodosComponent
- 我们在
AppComponent
的模板中添加了<router-outlet></router-outlet>
- 我们添加了通配符路由,以正常处理不匹配的网址。
接下来,我们将创建解析器以使用Angular Router从我们的后端API中获取现有的待办事项。
使用Angular路由器解析数据
在本系列的第3部分中,我们已经学习了如何使用Angular HTTP服务从后端API提取数据。
当前,当我们将浏览器导航到todos
URL时,会发生以下情况:
- Angular Router匹配
todos
URL - Angular Router激活
TodosComponent
- Angular Router将
TodosComponent
放置在TodosComponent
中的<router-outlet></router-outlet>
旁边 TodosComponent
在浏览器中显示,带有一个空的todos数组ngOnInit
是从TodosComponent
的ngOnInit
处理程序中的API获取的- 浏览器中的
TodosComponent
会使用从API获取的TodosComponent
进行更新。
如果在步骤5中加载待办事项需要三秒钟,则在步骤6中显示实际待办事项之前,将向用户显示一个空的待办事项列表三秒钟。
如果TodosComponent
的模板中包含以下HTML:
<div *ngIf="!todos.length">You currently do not have any todos yet.
</div>
那么用户会在显示实际待办事项之前看到此消息三秒钟,这可能会完全误导用户并导致用户在输入实际数据之前先离开。
我们可以在TodosComponent
中添加一个加载器,以在加载数据时显示一个微调TodosComponent
,但是有时我们可能无法控制实际的组件,例如,当我们使用第三方组件时。
要解决此不良行为,我们需要进行以下操作:
- Angular Router匹配
todos
URL - Angular Router从API获取待办事项
- Angular Router激活
TodosComponent
- Angular Router将
TodosComponent
放置在TodosComponent
中的<router-outlet></router-outlet>
旁边 TodosComponent
显示在浏览器中,并带有从API提取的TodosComponent
。
在这里,直到来自我们的API后端的数据可用时, TodosComponent
显示TodosComponent
。
这正是解析器可以为我们做的。
为了让Angular Router在激活TodosComponent
之前解析TodosComponent
,我们必须做两件事:
- 创建一个从API获取
TodosResolver
- 在激活
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
。
可以从ActivatedRoute
或ActivatedRouteSnapshot
访问路线的数据,我们将在下一部分中看到它们。
您可以使用路线的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现在:
- 将URL从
/
重定向到/todos
- 看到
todos
路由在resolve
属性中定义了TodosResolver
- 从
TodosResolver
运行resolve()
方法,等待结果并将结果分配给路线数据中的todos
- 激活
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上找到 。
在第五部分中,我们将实现身份验证,以防止未经授权访问我们的应用程序。
因此,请继续关注更多信息,并且与往常一样,随时在评论中留下您的想法和问题!
推荐课程
From: https://www.sitepoint.com/component-routing-angular-router/
Angular Router的组件路由介绍相关推荐
- angular复用路由组件_Angular Router的组件路由简介
angular复用路由组件 这是SitePoint Angular 2+教程的第4部分,有关如何使用Angular CLI创建CRUD应用程序. 在本文中,我们将介绍Angular Router,并了 ...
- Solr router 路由介绍
目录 1.compositeId路由 1.1.compositeId路由原理 1.2.compositeId路由查询 2.implicit路由 2.1.implicit路由原理 2.2.implici ...
- Angular中实现动态路由跳转并传递参数
场景 Angular介绍.安装Angular Cli.创建Angular项目入门教程: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/detail ...
- 【硬核解说】一口气讲明白Angular的5种路由守卫RouteGuard是嘛玩意儿
Angular的常用路由守卫有5种,按照执行顺序: ① CanLoad:进入到当前路由的时候触发(若用户没有权限访问,相应的模块并不会被加载.这里是指对应组件的代码). ↓ ② CanAcitivat ...
- Angular动态创建组件之Portals
本文主要介绍使用Angular api 和 CDK Portals两种方式实现动态创建组件,另外还会讲一些跟它相关的内容.如:Angular多级依赖注入.ViewContainerRef,Portal ...
- Angular 2+ 监听路由变化动态设置页面标题
原网址:https://segmentfault.com/a/1190000009971757 路由配置代码:const routes: Routes = [{ path: 'calendar', c ...
- 使用Angular Router导航基础
名称 简介 Routes 路由配置,保存着那个URL对应着哪个组件,以及在哪个RouterOulet中展示组件. RouterOutlet 在HTML中标记路由内容呈现位置的占位符指令. Router ...
- angular学习6之路由可选参数和必选参数
有路由配置如下 const routes:Routes = [{path:'/product/:title',component:ProductDetailComponent }] 在要导航到Prod ...
- React 路由基础--React路由介绍
1.React路由介绍 现代的前端应用大多都是 SPA(单页应用程序)single page application,也就是只有一个 HTML 页面的应用程序.因为它的用户体 验更好.对服务器的压力更 ...
最新文章
- SQL语句中 left join 后用 on 还是 where,区别大了!
- 一大波物联网僵尸正在袭来,都有啥安全保护方法?
- php中并发读写文件冲突的解决方案(文件锁应用示例)
- HDU 4628 Pieces(DP + 状态压缩)
- Tkinter中常用的函数
- Sqlserver中 登录用户只能看到自己拥有权限的库
- 2021牛客多校4 - Rebuild Tree(树形dp)
- linux退出编辑器命令,LINUX中,Vi编辑器的几种模式及保存、退出等命令
- 【渝粤题库】国家开放大学2021春2402外国文学题目
- anaconda中怎么sh_【好工具】 深度学习炼丹,你怎么能少了这款工具!JupyterLab 远程访问指南...
- 辨异 —— 概率与统计
- tcp/ip协议栈总结
- 从零开始学PCR技术(二):Taq DNA酶
- 聚石塔RDS数据备份与迁移
- Spigot 算法之一 计算调和级数的和
- 视频教程-网络安全与渗透测试工程师-渗透测试
- signal(SIGCHLD, SIG_IGN)
- 在deepin运行imageapp程序
- 计算机二级两个控件之间求偶,求,全国计算机等级考试二级java历年试题及答案合集,还有上机考试真题?...
- DCHQ + EMC REX-Ray在多个云和虚拟化平台上交付容器数据库服务