序章

APIからのデータの取得と表示を処理する1つの方法は、ユーザーをコンポーネントにルーティングし、そのコンポーネントのngOnInitフックで、サービスのメソッドを呼び出して必要なデータを取得することです。 データを取得している間、おそらくコンポーネントはロードインジケーターを表示できます。

route resolverと呼ばれるものを使用する別の方法があります。これにより、新しいルートに移動する前にデータを取得できます。

使用可能なAPIの1つは、 Hacker NewsAPIです。 Hacker Newsは、リンクを共有して議論するためのWebサイトです。 APIを使用して、最も人気のある投稿を取得し、個々の投稿に関する情報を表示できます。

このチュートリアルでは、収集されたデータを表示するルートに移動する前に、HackerNewsAPIからデータを取得するルートリゾルバーを実装します。

前提条件

このチュートリアルを完了するには、次のものが必要です。

このチュートリアルは、ノードv15.3.0、npm v6.14.9、@angular/core v11.0.1、@angular/common v11.0.1、@angular/router v11.0.1、およびrxjsv6.6.0。

ステップ1—プロジェクトの設定

このチュートリアルでは、@angular/cliで生成されたデフォルトのAngularプロジェクトからビルドします。

  1. npx @angular/cli new angular-route-resolvers-example --style=css --routing --skip-tests

これにより、スタイルが「CSS」(「Sass」、「Less」、「Stylus」ではなく)に設定され、ルーティングが有効になり、テストがスキップされる新しいAngularプロジェクトが構成されます。

新しく作成されたプロジェクトディレクトリに移動します。

  1. cd angular-route-resolvers-example

この時点で、@angular/routerを使用した新しいAngularプロジェクトがあります。

ステップ2—リゾルバーを構築する

2秒の遅延後に文字列を返すリゾルバーを実装することから始めましょう。 この小さな概念実証は、大規模なプロジェクトに適用できる配線ルートの基本を探るのに役立ちます。

まず、独自のファイルにリゾルバー用の別のクラスを作成します。

  1. ./node_modules/@angular/cli/bin/ng generate resolver news

これにより、@angular/cliを使用して、newsという名前のリゾルバーが生成されます。

src / app / news.resolver.ts
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コンポーネントを生成します。

  1. ./node_modules/@angular/cli/bin/ng generate component home

次に、@angular/cliを使用して、topコンポーネントを生成します。

  1. ./node_modules/@angular/cli/bin/ng generate component top

これで、リゾルバーを含めるようにルーティングモジュールを設定できます。

src / app / app-routing.module.ts
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—コンポーネント内の解決されたデータへのアクセス

コンポーネントでは、ActivatedRoutesnapshotオブジェクトのdataプロパティを使用して、解決されたデータにアクセスできます。

src / app / top / top.component.ts
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!メッセージにアクセスできます。

src / app / top / top.module.html
<p>The message: {{ data.message }}</p>

この時点で、アプリケーションをコンパイルできます。

  1. npm start

そして、Webブラウザでlocalhost:4200/topにアクセスします。

Output
The message: Route!

topルートに移動すると、データが最初に解決されるため、2秒の遅延があることがわかります。

ステップ5—APIからのデータの解決

APIから実際にデータを取得して、より現実的なものにしましょう。 ここでは、HackerNewsAPIからデータを取得するサービスを作成します。

エンドポイントをリクエストするには、HttpClientが必要です。

まず、HttpClientModuleapp.module.tsに追加します。

src / app / 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 { }

次に、新しいサービスを作成します。

  1. ./node_modules/@angular/cli/bin/ng generate service news

これにより、@angular/cliを使用して、newsという名前のサービスが生成されます。

src / app / news.service.ts
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に置き換えることができます。

src / app / news.resolver.ts
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という名前のリゾルバーを生成します。

  1. ./node_modules/@angular/cli/bin/ng generate resolver news

次に、post.resolver.tsActivatedRouteSnapshotを使用するように変更します。

src / app / post.resolver.ts
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に追加します。

src / app / news.service.ts
// ...

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`);
  }
}

そして、PostResolverpost/:idルートをapp-routing.module.tsに追加します。

src / app / app-routing.module.ts
// ...

import { PostResolver } from './post.resolver';

// ...

const routes: Routes = [
  // ...
  {
    path: 'post/:id',
    component: PostComponent,
    resolve: { newsData: PostResolver }
  }
];

// ...

次に、新しいPostComponentを作成します。

  1. ./node_modules/@angular/cli/bin/ng generate component post

次に、スナップショットデータを使用するようにpost.component.tsを変更します。

src / app / 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を表示します。

src / app / post / post.component.html
<p>{{ data.newsData.title }}</p>

これで、ユーザーがhttp://localhost:4200/post/15392112にアクセスすると、投稿ID15392112のデータが解決されます。

ステップ7—エラーの処理

データのフェッチ中にエラーが発生した場合は、RxJSのcatch演算子を使用して、リゾルバーでエラーをキャッチして処理できます。 たとえば、次のようなものです。

src / app / news.resolver.ts
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を返し、ユーザーをルートパスに戻すこともできます。

src / app / news.resolver.ts
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トピックページで演習とプログラミングプロジェクトを確認してください。