Angularルーターでルートリゾルバーを使用する方法
序章
APIからのデータの取得と表示を処理する1つの方法は、ユーザーをコンポーネントにルーティングし、そのコンポーネントのngOnInit
フックで、サービスのメソッドを呼び出して必要なデータを取得することです。 データを取得している間、おそらくコンポーネントはロードインジケーターを表示できます。
route resolver
と呼ばれるものを使用する別の方法があります。これにより、新しいルートに移動する前にデータを取得できます。
使用可能なAPIの1つは、 Hacker NewsAPIです。 Hacker Newsは、リンクを共有して議論するためのWebサイトです。 APIを使用して、最も人気のある投稿を取得し、個々の投稿に関する情報を表示できます。
このチュートリアルでは、収集されたデータを表示するルートに移動する前に、HackerNewsAPIからデータを取得するルートリゾルバーを実装します。
前提条件
このチュートリアルを完了するには、次のものが必要です。
- Node.jsはローカルにインストールされます。これは、Node.jsのインストール方法とローカル開発環境の作成に従って実行できます。
- Angularプロジェクトのセットアップにある程度精通している。
このチュートリアルは、ノード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
これにより、スタイルが「CSS」(「Sass」、「Less」、「Stylus」ではなく)に設定され、ルーティングが有効になり、テストがスキップされる新しいAngularプロジェクトが構成されます。
新しく作成されたプロジェクトディレクトリに移動します。
- 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—ルートの構成
2つの異なるルートを体験するには、2つの新しいコンポーネントが必要になります。 home
がランディングページになります。 そして、top
は、HackerNewsAPIからのトップ投稿を特集します。
まず、@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
にアクセスします。
OutputThe message: Route!
top
ルートに移動すると、データが最初に解決されるため、2秒の遅延があることがわかります。
ステップ5—APIからのデータの解決
APIから実際にデータを取得して、より現実的なものにしましょう。 ここでは、HackerNewsAPIからデータを取得するサービスを作成します。
エンドポイントをリクエストするには、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);
}
}
これで、NewsResolver
の文字列コードをNewsService
に置き換えることができます。
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
ルートを見ると、HackerNewsのトップ投稿のid
を表す番号のリストが表示されます。
ステップ6—ルートパラメータへのアクセス
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'));
}
}
次に、getPost
メソッドをNewsService
に追加します。
// ...
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`);
}
}
そして、PostResolver
とpost/:id
ルートをapp-routing.module.ts
に追加します。
// ...
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
にアクセスすると、投稿ID15392112
のデータが解決されます。
ステップ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からデータを取得するときにエラーが発生した場合、これら2つのアプローチによりユーザーエクスペリエンスが向上します。
結論
このチュートリアルでは、収集されたデータを表示するルートに移動する前に、HackerNewsAPIからデータを取得するルートリゾルバーを実装しました。 これは、@angular/router
、@angular/common/http
、およびrxjs
を利用することによって実現されました。
Angularについて詳しく知りたい場合は、Angularトピックページで演習とプログラミングプロジェクトを確認してください。