简介
处理从 API 检索和显示数据的一种方法是将用户路由到一个组件,然后在该组件的 ngOnInit
钩子中调用服务中的方法来获取必要的数据。在获取数据的同时,组件可以显示一个加载指示器。
还有另一种方法可以使用所谓的 路由解析器
,它允许您在导航到新路由之前获取数据。
可供使用的一个 API 是 Hacker News API。Hacker News 是一个用于分享链接和讨论的网站。该 API 可用于检索最受欢迎的帖子并显示有关单个帖子的信息。
在本教程中,您将实现一个路由解析器,该解析器在导航到显示收集到的数据的路由之前从 Hacker News API 获取数据。
先决条件
要完成本教程,您需要:
- 本地安装了 Node.js,您可以按照《如何安装 Node.js 并创建本地开发环境》中的步骤进行操作。
- 一些关于设置 Angular 项目的熟悉程度。
本教程已使用 Node v15.3.0、npm
v6.14.9、@angular/core
v11.0.1、@angular/common
v11.0.1、@angular/router
v11.0.1 和 rxjs
v6.6.0 进行验证。
步骤 1 — 设置项目
为了本教程的目的,您将从使用 @angular/cli
生成的默认 Angular 项目构建。
npx @angular/cli new angular-route-resolvers-example --style=css --routing --skip-tests
这将配置一个新的 Angular 项目,其中样式设置为 “CSS”(而不是 “Sass”、“Less” 或 “Stylus”),启用了路由,并跳过了测试。
导航到新创建的项目目录:
cd angular-route-resolvers-example
此时,您已经有了一个带有 @angular/router
的新的 Angular 项目。
步骤 2 — 构建解析器
让我们首先实现一个解析器,在 2 秒延迟后返回一个字符串。这个小的概念验证可以帮助您探索可以应用于更大项目的路由连接的基础知识。
首先,在一个单独的文件中为解析器创建一个单独的类:
./node_modules/@angular/cli/bin/ng generate resolver news
这将使用 @angular/cli
生成名为 news
的解析器:
import { Injectable } from '@angular/core'; import { Resolve } from '@angular/router'; import { Observable, of } from 'rxjs'; import { delay } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class NewsResolver implements Resolve<Observable<string>> { resolve(): Observable<string> { return of('Route!').pipe(delay(2000)); } }
实现 Angular 路由的 Resolve
接口需要该类具有一个 resolve
方法。该方法返回的任何内容都将是已解析的数据。
此代码将返回一个在 2 秒延迟后包装的字符串的可观察对象。
步骤 3 — 配置路由
为了体验两个不同的路由,您将需要两个新组件。home
将是登陆页面。top
将展示来自 Hacker News API 的热门帖子。
首先,使用 @angular/cli
生成 home
组件:
./node_modules/@angular/cli/bin/ng generate component home
然后,使用 @angular/cli
生成 top
组件:
./node_modules/@angular/cli/bin/ng generate component top
现在,您可以设置路由模块以包含解析器。
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { NewsResolver } from './news.resolver'; import { TopComponent } from './top/top.component'; import { HomeComponent } from './home/home.component'; const routes: Routes = [ { path: '', pathMatch: 'full', component: HomeComponent }, { path: 'top', component: TopComponent, resolve: { message: NewsResolver } } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
请注意,解析器就像服务一样提供,然后您将解析器与路由定义一起包括在内。这里已解析的数据将在 message
键下可用。
步骤 4 — 在组件中访问已解析的数据
在组件中,您可以使用 ActivatedRoute
的 snapshot
对象的 data
属性来访问已解析的数据:
import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; @Component({ ... }) export class TopComponent implements OnInit { data: any; constructor(private route: ActivatedRoute) {} ngOnInit(): void { this.data = this.route.snapshot.data; } }
现在,在组件中,您可以这样访问 Route!
消息:
<p>The message: {{ data.message }}</p>
此时,您可以编译您的应用程序:
npm start
并在 Web 浏览器中访问 localhost:4200/top
。
The message: Route!
您会注意到,当导航到 top
路由时,现在会有 2 秒的延迟,因为数据首先被解析了。
步骤 5 — 从 API 解析数据
让我们通过实际从 API 获取一些数据来使事情更真实。在这里,您将创建一个从 Hacker News API 获取数据的服务。
您将需要使用 HttpClient 来请求端点。
首先,将 HttpClientModule
添加到 app.module.ts
:
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, HttpClientModule, AppRoutingModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
然后,创建一个新的服务:
./node_modules/@angular/cli/bin/ng generate service news
这将使用 @angular/cli
生成一个名为 news
的服务:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable({ providedIn: 'root' }) export class NewsService { constructor(private http: HttpClient) { } getTopPosts() { const endpoint = 'https://hacker-news.firebaseio.com/v0/topstories.json'; return this.http.get(endpoint); } }
现在,您可以使用 NewsService
替换 NewsResolver
中的字符串代码:
import { Injectable } from '@angular/core'; import { Resolve } from '@angular/router'; import { Observable } from 'rxjs'; import { NewsService } from './news.service'; export class NewsResolver implements Resolve<any> { constructor(private newsService: NewsService) {} resolve(): Observable<any> { return this.newsService.getTopPosts(); } }
此时,如果您在浏览器中查看 top
路由,将会看到一个代表 Hacker News 上热门帖子的 id 列表。
步骤 6 — 访问路由参数
您可以使用 ActivatedRouteSnapshot
对象在解析器中访问当前路由参数。
以下是一个示例,您可以在解析器中使用 ActivatedRouteSnapshot
获取当前路由的 id
参数。
首先,使用 @angular/cli
生成一个名为 post
的解析器:
./node_modules/@angular/cli/bin/ng generate resolver news
然后,修改 post.resolver.ts
以使用 ActivatedRouteSnapshot
:
import { Injectable } from '@angular/core'; import { Resolve, ActivatedRouteSnapshot } from '@angular/router'; import { Observable } from 'rxjs'; import { NewsService } from './news.service'; @Injectable({ providedIn: 'root' }) export class PostResolver implements Resolve<any> { constructor(private newsService: NewsService) {} resolve(route: ActivatedRouteSnapshot): Observable<any> { return this.newsService.getPost(route.paramMap.get('id')); } }
接下来,向 NewsService
添加一个 getPost
方法:
// ... export class NewsService { constructor(private http: HttpClient) { } // ... getPost(postId: string) { const endpoint = 'https://hacker-news.firebaseio.com/v0/item'; return this.http.get(`${endpoint}/${postId}.json`); } }
并且在 app-routing.module.ts
中添加 PostResolver
和 post/:id
路由:
// ... import { PostResolver } from './post.resolver'; // ... const routes: Routes = [ // ... { path: 'post/:id', component: PostComponent, resolve: { newsData: PostResolver } } ]; // ...
接下来,创建新的 PostComponent
:
./node_modules/@angular/cli/bin/ng generate component post
然后,修改 post.component.ts
以使用快照数据:
import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; @Component({ ... }) export class PostComponent implements OnInit { data: any; constructor(private route: ActivatedRoute) { } ngOnInit(): void { this.data = this.route.snapshot.data; } }
并修改 post.component.html
以显示 title
:
<p>{{ data.newsData.title }}</p>
现在,如果用户访问 http://localhost:4200/post/15392112
,将会解析帖子 id 为 15392112
的数据。
步骤 7 — 处理错误
如果在获取数据时出现错误,您可以使用 RxJS 的 catch 操作符在解析器中捕获并处理错误。例如:
import { Injectable } from '@angular/core'; import { Resolve } from '@angular/router'; import { Observable, of } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { NewsService } from './news.service'; @Injectable() export class NewsResolver implements Resolve<any> { constructor(private newsService: NewsService) {} resolve(): Observable<any> { return this.newsService.getTopPosts().pipe(catchError(() => { return of('data not available at this time'); })); } }
或者,您可以返回一个 EMPTY
observable 并将用户重定向到根路径:
import { Injectable } from '@angular/core'; import { Router, Resolve } from '@angular/router'; import { Observable, EMPTY } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { NewsService } from './news.service'; @Injectable() export class NewsResolver implements Resolve<any> { constructor(private router: Router, private newsService: NewsService) {} resolve(): Observable<any> { return this.newsService.getTopPosts().pipe(catchError(() => { this.router.navigate(['/']); return EMPTY; })); } }
如果在从 API 检索数据时出现错误,这两种方法都将带来更好的用户体验。
结论
在本教程中,您实现了一个路由解析器,该解析器在导航到显示收集到的数据的路由之前从 Hacker News API 获取数据。这是通过利用 @angular/router
、@angular/common/http
和 rxjs
实现的。
如果您想了解更多关于 Angular 的知识,请查看我们的 Angular 专题页面,了解更多练习和编程项目。