【Angular】実装メモ~Router編~

Routing

 

実装

 

画面遷移の設定

  • app-routing.module.ts内に定義する

 

初期画面の設定

  • パス指定無しの場合のリダイレクト設定

  • pathMatch: 'full'はパスの完全一致

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

 

ルートの設定

{ path: 'top', component: TopComponent },

 

子ルートの設定

  • パスは「/${親path}/${子path}」

    • 例の場合は/top/child

{ 
 path: 'top',
 component: TopComponent,
 children: [
  {
     path: 'child',
     component: ChildComponent,
  },
]
},

 

パスに一致するものが無い場合の設定

{ path: '**', component: TopComponent }

 

 

画面遷移処理の実装

 

HTMLに実装

  • /を外すと子ルートへのパスになる

<a routerLink="/top">画面遷移</a>

 

Componentに実装

import { Component, OnInit } from '@angular/core';
// インポートを追加
import { Router } from '@angular/router';

@Component({
 selector: 'app-test',
 templateUrl: './test.component.html',
 styleUrls: ['./test.component.scss'],
})
export class TestComponent implements OnInit {
 // RouterをInjection
 constructor(private router: Router) {}

 ngOnInit(): void {}

 onClickButton(){
   // 画面遷移
   this.router.navigate(['/top']);
}
}

 

 

Guard

Guradの種類

NO 種類 概要 ユースケース
1 canActivate ・画面遷移時に実行される ・戻り値がfalseの場合は画面遷移しない ・画面遷移時の認証状態チェック
2 canActivateChild ・処理タイミングはcanActivateと同じ ・子ルートの制御に使用する ・権限ごとの表示項目制御
3 canDeactivate ・画面から離れる時に実行される ・画面から離れる時の確認
4 canLoad ・モジュールを読み込み時に実行される ・権限ごとの遅延ロード可否
5 resolve ・画面遷移前に実行される ・データ取得を行い遷移先に渡す ・外部APIへのアクセス等でデータ取得に時間がかかる場合に使用する ・canActivate等とは違い処理時間がかかっても空白画面が表示されない ※serviceとして作成する ・外部APIからデータが取得できた場合だけ特定画面に遷移

 

生成コマンド

  • Angular-CLIのコマンド

  • 実装するGuardを選択する

ng g guard ${ガード名}

 

Guardの実装

例)canActivate

  • 画面遷移時に認証状態を確認する

import { Injectable } from '@angular/core';
import {
 CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router,} from '@angular/router';
import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import { AppStore, Storekey } from 'src/app/state/store/store-definition';
import { tap } from 'rxjs/operators';

@Injectable({
 providedIn: 'root',
})
export class AuthGuard implements CanActivate {
 /** 認証状態 */
 isloggedIn$: Observable<boolean>;

 constructor(private store: Store<AppStore>, private router: Router) {
   this.isloggedIn$ = store.select(Storekey.isloggedIn);
}

 canActivate(
   next: ActivatedRouteSnapshot,
   state: RouterStateSnapshot
):
   | Observable<boolean | UrlTree>
   | Promise<boolean | UrlTree>
   | boolean
   | UrlTree {
   return this.isloggedIn$.pipe(
     tap((loggedIn) => {
       // 認証されていない場合に特定画面に遷移
       if (!loggedIn) {
         this.router.navigate(['/login']);
      }
    })
  );
}
}

 

ルートに設定

{ path: 'top', component: TopComponent, canActivate: [AuthGuard] }

【Angular】実装メモ~Form編~

Form

  • FormGroupで実装

  • 入力フォームを次の仕様で作る

    • 縦並び

    • バリデーション付き

    • バリデーションエラー時はボタンを非活性

    • UIライブラリにAngular Material + flex-layoutを使用

 

 

実装イメージ

(初期表示)

f:id:kanmi-program:20201130191051p:plain

 

(入力時)

f:id:kanmi-program:20201130191109p:plain

 

(エラー時)

f:id:kanmi-program:20201130191125p:plain

 

 

実装

  • app.module

// インポートを追加
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';
import { FlexLayoutModule } from '@angular/flex-layout';

// NgModuleに追加
imports: [
   // Angular Materialのモジュールを追加
   MatInputModule,
   MatButtonModule,
   // flex-layoutのモジュールを追加
   FlexLayoutModule,
]

 

  • component

import { Router } from '@angular/router';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { UserActions } from 'src/app/state/action/user-actions';
import { AppStore } from 'src/app/state/store/store-definition';

import { integerValidator } from "src/app/validator/sample-validator";

@Component({
 selector: 'app-test',
 templateUrl: './test.component.html',
 styleUrls: ['./test.component.scss'],
})
export class TestComponent implements OnInit {
 constructor(
   private fb: FormBuilder,
   private router: Router,
   private store: Store<AppStore>
) {}

 public sampleForm: FormGroup;
 ngOnInit() {
   this.initForm();
}

 // フォームの初期化処理
 initForm() {
   this.sampleForm = this.fb.group({
     // バリデーターの設定
     // 第一引数に初期値、第二引数にバリデーター
     userId: ['', [Validators.required, integerValidator()]],
     password: ['', Validators.required],
  });
}

 login(value: any) {

   // ログインActionをdispatch
   this.store.dispatch(UserActions.login());
   // ユーザーID追加Actionをdispatch
   this.store.dispatch(UserActions.addUser({ userId: value.userId }));

   // 画面遷移
   this.router.navigate(['state']);
}
}

 

  • html

<form class="example-form" [formGroup]="sampleForm" (ngSubmit)="login(sampleForm.value)">
 <div fxLayout="column">
   <mat-form-field appearance="fill">
     <mat-label>ユーザーID</mat-label>
     <input type="url" matInput formControlName="userId" placeholder="userId" required>
     <mat-error *ngIf="sampleForm.controls.userId.hasError('integerValidator')">
      数字のみです
     </mat-error>
     <mat-error *ngIf="sampleForm.controls.userId.hasError('required')">
      ユーザーIDを入力してください
     </mat-error>
   </mat-form-field>

   <mat-form-field appearance="fill">
     <mat-label>パスワード</mat-label>
     <input type="password" matInput formControlName="password" placeholder="password" required>
     <mat-error *ngIf="sampleForm.controls.password.hasError('required')">
      パスワードを入力してください
     </mat-error>
   </mat-form-field>

   <button type="submit" mat-raised-button color="primary" [disabled]="!sampleForm.valid">ログイン</button>

 </div>
</form>

 

・customValidator

import { AbstractControl, ValidatorFn } from '@angular/forms';


const REG_INTEGER = /^[0-9]*$/

export function integerValidator(): ValidatorFn {
 return (control: AbstractControl): { [key: string]: any } | null => {
   // 値のチェック
   const result = !REG_INTEGER.test(control.value);
   // チェック結果を返却
   return result ? { integerValidator: { value: control.value } } : null;
};
}

【Angular】Ngrx概要

 

Ngrx?

 

f:id:kanmi-program:20201126224833p:plain



 

用語集

Component

 

Action

  • 「何が起こったか」という情報を持ったオブジェクト

  • コンポーネントからのイベントをトリガーとしてActionオブジェクトをStoreにDispatchする

 

Reducer

  • ActionオブジェクトとStateを受け取りStateを更新する

  • 更新する場合はStateを新規のオブジェクトとして作成する

 

Store

  • Stateを保存する場所

 

Selector

  • Storeから特定のデータを取り出す

 

Effects

  • 外部APIとの通信などをComponentから切り分ける

  • Reduxからの拡張機能

 

Service

  • Angularのサービス

 

 

実装

インストール

Store

npm install @ngrx/store --save
ng add @ngrx/store@latest

 

Effects

  • 使う人だけ

npm install @ngrx/effects --save
ng add @ngrx/effects@latest

 

Devtool

  • 使う人だけ

  • productionビルド時はログ参照のみになる

npm install @ngrx/store-devtools --save
ng add @ngrx/store-devtools@latest

 

<参考>

https://ngrx.io/guide/store/install

https://ngrx.io/guide/store-devtools/install

https://ngrx.io/guide/effects/install

 

 

Storeの作成

  • Stateとして管理するものを定義

 

store-definition.ts

import { LoggedInReducer, UserIdReducer } from '../reducer/user-reducer';

/**
* Key定義
*/
export enum Storekey {
 isloggedIn = 'isloggedIn',
 userId = 'userId',
}

/**
* Store定義
*/
export const StoreDefinition = {
 isloggedIn: LoggedInReducer,
 userId: UserIdReducer,
};

/**
* 型定義
*/
export interface AppStore {
 isloggedIn: boolean;
 userId: string;
}

 

 

Actionの作成

  • Stateを参照するActionを定義

 

user-actions.ts

import { createAction, props } from '@ngrx/store';

/**
* ユーザーAction
*/
export const UserActions = {
 login: createAction('user/login'),
 logout: createAction('user/logout'),
 // Actionの引数としてuserIdを定義
 addUser: createAction('user/add', props<{ userId: string }>()),
 deleteUser: createAction('user/delete'),
};

 

Reducerの作成

  • Actionに紐づく処理を定義

 

user-reducers.ts

import { Action, createReducer, on } from '@ngrx/store';
import { UserActions } from '../action/user-actions';

/** ログイン状態 */
export const loggedIn: boolean = false;
/** ユーザーID */
export const userId: string = '';

const loggedInReducer = createReducer(
  loggedIn,
  on(UserActions.login, (state) => {
    // 変更後のstateを返却(新しいオブジェクトとして作成)
    return true;
  }),
  on(UserActions.logout, (state) => {
    return false;
  })
);

const userIdReducer = createReducer(
  userId,
  // Actionの引数としてuserIdを受け取る
  on(UserActions.addUser, (state, { userId }) => {
    return userId;
  }),
  on(UserActions.deleteUser, (state) => {
    return '';
  })
);

export function LoggedInReducer(state: boolean, action: Action) {
  return loggedInReducer(state, action);
}

export function UserIdReducer(state: string, action: Action) {
  return userIdReducer(state, action);
}

 

NgModuleに設定

  • インストール時のng addで自動で追加される

  • Store定義を手動で追加

 

app.module.ts

import { StoreModule } from '@ngrx/store';
import { StoreDefinition } from './state/store/store-definition';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';

imports: [
    // Store定義を追加
    StoreModule.forRoot(StoreDefinition),
    // Devtoolの設定
    StoreDevtoolsModule.instrument({
      maxAge: 25,
      logOnly: environment.production,
    }),
]

 

Componentから呼び出す

  • 定義したActionをStoreにDispatchする

  • 入力した内容でStateを更新する

  • 遷移先のComponentで更新されたStateを取得する

 

遷移元component

import { Router } from '@angular/router';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { UserActions } from 'src/app/state/action/user-actions';
import { AppStore, Storekey } from 'src/app/state/store/store-definition';

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.scss'],
})
export class TestComponent implements OnInit {
  constructor(
    private fb: FormBuilder,
    private router: Router,
    private store: Store<AppStore>
  ) {}

  public sampleForm: FormGroup;
  ngOnInit() {
    this.initForm();
  }

  initForm() {
    this.sampleForm = this.fb.group({
      userId: ['', Validators.required],
      password: ['', Validators.required],
    });
  }

  login(value: any) {
    // ログインActionをdispatch
    this.store.dispatch(UserActions.login());
    // ユーザーID追加Actionをdispatch
    this.store.dispatch(UserActions.addUser({ userId: value.userId }));

    // 画面遷移
    this.router.navigate(['/state']);
  }
}

 

遷移先component

import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { AppStore, Storekey } from 'src/app/state/store/store-definition';

@Component({
  selector: 'app-state',
  templateUrl: './state.component.html',
  styleUrls: ['./state.component.scss'],
})
export class StateComponent implements OnInit {
  /** ログイン状態 */
  isloggedIn$: Observable<boolean>;
  /** ユーザーID */
  userId$: Observable<string>;

  constructor(private store: Store<AppStore>) {
    // storeから取得
    this.isloggedIn$ = store.select(Storekey.isloggedIn);
    this.userId$ = store.select(Storekey.userId);
  }

  ngOnInit(): void {}
}

 

遷移先html

<!-- 取得したstateを表示 -->
<p>ログイン状態:{{isloggedIn$ | async}}</p>
<p>ユーザーID:{{userId$ | async}}</p>

【概要】Reduxを始める

 

 

 

概要

  • UIの状態管理を行うライブラリ

    • 状態:ログイン済、未ログインとか

  • Fluxの概念を拡張して作成

  • 中規模~大規模アプリに向いている

 

基本構成

  • 基本は以下の3つ

    • State:状態

    • View:画面

    • Actions:イベント

  • データフロー

    • 初期状態のState(状態)に基づいてView(画面)が表示される

    • ViewからActions(ボタン押下等)が実行される

    • Actionの種類に基づいてStateが更新される

    • 更新されたStateに基づいてViewが更新される

f:id:kanmi-program:20201120223746p:plain



 

 

データフロー詳細

  1. ユーザー入力からActionを作成

  2. StoreへActionをdispatch(送信)

  3. dispatchされたActionとStateをReducerへ渡す

  4. Reducerが作成した新しいStateをStoreが保存

f:id:kanmi-program:20201120224717g:plain

 

 

Reduxの3原則

 

1. Single source of truth

  • アプリ内でStoreは1つのみ

  • Global Stateは単独のオブジェクトとしてStoreに保存

    • どこからでも同じものが取得できる

 

2. State is read-only

  • Stateを直接変更しない

  • Stateの変更はActionをStoreへdispatchする方法のみ

    • 状態の変更が一元化されることで競合が起きない

 

3. Mutations are written as pure functions

  • Reducerは純粋関数にする

    • 関数の実行に副作用無し

    • 関数の戻り値は入力パラメータ(State、Action)のみに依存(参照透過性)

    • 非同期処理やランダム値の計算はしない

  • 既存のStateオブジェクトを更新ではなく、新規にStateオブジェクトを作成する

 

 

用語集(Redux)

Action

  • 「状態を変更する」という情報を持ったオブジェクト

  • typeプロパティ(定数)を必ず持つ

  • typeプロパティの命名規則は「domain/eventName」

    • domain:アクションが属する機能またはカテゴリ

    • eventName:発生した特定のイベント

 

ActionCreator

  • Actionを作成するメソッド

  • Actionを作るのみ

 

Store

  • State(状態)を保管している場所

  • アプリ内で1つだけ存在

  • Storeの機能は以下

    • Stateの保持

    • Stateへのアクセス機能(getState)

    • Stateの更新機能(dispatch)

    • リスナーの登録機能(subscribe)

 

Dispatch

  • Stateを更新する機能

    • StoreにActionを送信し、新規のStateを作成してStoreのStateを更新

 

State

  • アプリの状態

 

Reducer

  • ActionとStateを受け取り、新しいStateを返すメソッド

  • 純粋関数として作る

 

 

 

 

【Angular】IndexedDBを使う

前提

  • AngularでIndexedDBにアクセス

  • ライブラリに Dexie.jsを使う

 

 

IndexedDB?

 

実装

ライブラリのインストール

npm install --save dexie

 

 

DB定義の作成

import Dexie from 'dexie';

export class RamenDB extends Dexie {
   
 // ストア定義
 menu: Dexie.Table<IMenu, string>;
 recipi: Dexie.Table<IRecipi, string>;

 /** DBバージョン */
 dbversion: number = 1;

 constructor() {
   // DB名
   super('RamenDB');

   // ストア定義
   this.version(this.dbversion).stores({
     menu: '++menuId, name, price',
     recipi: 'menuId, men, soup, ingredients',
  });

   // ストア名
   this.menu = this.table('menu');
   this.recipi = this.table('recipi');
}
}

export interface IMenu {
 name: string;
 price: number;
}

export interface IRecipi {
 menuId: number;
 men: string;
 soup: string;
 ingredients: Array<string>;
}

 

データアクセス用のサービスクラスを作成

import { Injectable } from '@angular/core';
import { from, Observable } from 'rxjs';

import { IMenu, RamenDB } from '../table/ramen-table';

@Injectable({
 providedIn: 'root',
})
export class RamenDBService {
 ramenTable = new RamenDB();

 constructor() {}

 // メニューテーブル

 /**
  * メニューを取得
  * @param menuId
  */
 public getMenu(menuId: number): Observable<IMenu> {
   return from(this.ramenTable.menu.get(menuId));
}

 /**
  * メニューを全件取得
  */
 public getAllMenu(): Observable<IMenu[]> {
   return from(this.ramenTable.menu.toArray());
}

 /**
  * メニューの追加
  * @param menu
  */
 public addMenu(menu: IMenu): Observable<number> {
   return from(this.ramenTable.menu.add(menu));
}

 /**
  * メニューの更新
  * @param menuId
  * @param menu
  */
 public updateMenu(menuId: number, menu: IMenu) {
   return from(this.ramenTable.menu.update(menuId, menu));
}

 /**
  * メニューの削除
  * @param menuId
  */
 public deleteMenu(menuId: number) {
   return from(this.ramenTable.menu.delete(menuId));
}
}

 

DBの内容を確認

  • DBの内容はデバッグツールの「Application」タブから確認できる

f:id:kanmi-program:20201116235201p:plain

 

 

 

 

プライバシーポリシー

個人情報の利用目的

当ブログでは、お問い合わせや記事へのコメントの際、名前やメールアドレス等の個人情報を入力いただく場合がございます。 取得した個人情報は、お問い合わせに対する回答や必要な情報を電子メールなどをでご連絡する場合に利用させていただくものであり、これらの目的以外では利用いたしません。

広告について

当ブログでは、第三者配信の広告サービス(Googleアドセンス)を利用しており、ユーザーの興味に応じた商品やサービスの広告を表示するため、クッキー(Cookie)を使用しております。 クッキーを使用することで当サイトはお客様のコンピュータを識別できるようになりますが、お客様個人を特定できるものではありません。

Cookieを無効にする方法やGoogleアドセンスに関する詳細は「広告 – ポリシーと規約 – Google」をご確認ください。

アクセス解析ツールについて

当ブログでは、Googleによるアクセス解析ツール「Googleアナリティクス」を利用しています。このGoogleアナリティクスはトラフィックデータの収集のためにクッキー(Cookie)を使用しております。トラフィックデータは匿名で収集されており、個人を特定するものではありません。

コメントについて

当ブログへのコメントを残す際に、IP アドレスを収集しています。 これはブログの標準機能としてサポートされている機能で、スパムや荒らしへの対応以外にこのIPアドレスを使用することはありません。 なお、全てのコメントは管理人が事前にその内容を確認し、承認した上での掲載となります。あらかじめご了承ください。

【RxJS】ユースケースから理解するRxJS

 

環境

f:id:kanmi-program:20201105234221p:plain

 

 

ユースケース

Observableを作りたい

  • new Observableで値を流していく

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

@Component({
 selector: 'app-rxjs',
 templateUrl: './rxjs.component.html',
 styleUrls: ['./rxjs.component.scss'],
})
export class RxjsComponent implements OnInit {
 constructor() {}

 ngOnInit(): void {}

 /**
  * 注文処理
  */
 order() {
   const menu = new Observable((observable) => {
     try {
       observable.next('お冷');
       setTimeout(() => {
         observable.next('チャーハン');
         observable.next('醤油拉麺');
         setTimeout(() => {
           observable.complete();
        }, 5000);
      }, 3000);
    } catch (e) {
       observable.error(e);
    }
  });

   // 時間測定用
   let time = 0;
   const timer = setInterval(() => {
     time++;
  }, 1000);

   menu.subscribe(
     // 値が流れてきた時の処理
    (result) => {
       console.log(`[${time}秒] 提供:${result}`);
    },
     // エラーが発生した時の処理
    (error) => {
       console.error(error);
    },
     // 処理が完了した時の処理
    () => {
       console.log(`[${time}秒] お会計`);
       clearInterval(timer);
    }
  );
}
}
  • 実行結果

f:id:kanmi-program:20201105234401p:plain



 

 

 

PromiseをObservableに変換したい

  • AmplifyとかのライブラリはPromiseで実装されているのでObservableに統一したい

  • fromを使う

import { Component, OnInit } from '@angular/core';
import { from } from 'rxjs';
import { Storage } from 'aws-amplify';

@Component({
 selector: 'app-rxjs',
 templateUrl: './rxjs.component.html',
 styleUrls: ['./rxjs.component.scss'],
})
export class RxjsComponent implements OnInit {
 constructor() {}

 ngOnInit(): void {}

 /**
  * 注文処理
  */
 orderRamen() {
   from(Storage.list('', { level: 'protected' })).subscribe((result) => {
     console.log(result);
  });
}
}

 

 

複数の非同期通信の結果を待ち合わせて取得する

  • ZIPを使う

  • それぞれの非同期通信の実行結果を取得してログに出力するだけ

import { Component, OnInit } from '@angular/core';
import { from, zip } from 'rxjs';
import { map } from 'rxjs/operators';

@Component({
 selector: 'app-rxjs',
 templateUrl: './rxjs.component.html',
 styleUrls: ['./rxjs.component.scss'],
})
export class RxjsComponent implements OnInit {
 constructor() {}

 ngOnInit(): void {}

 /**
  * 注文処理
  */
 orderRamen() {
   // 注文を受けた順に格納(1人目「太麺、醤油」 2人目「細麺、塩」3人目「たまご麺、味噌」)
   let menOrder = from(['太麺', '細麺', 'たまご麺']);
   let soupOrder = from(['醤油', '塩', '味噌']);

   zip(menOrder, soupOrder)
    .pipe(
       map(([men, soup]) => {
         // 順番に結果を取得して提供
         console.log(`[麺]:${men}, [スープ]:${soup}`);
      })
    )
    .subscribe();
}
}
  • 実行結果

    • observableにそれぞれ値が流れてきてから処理を実施している

f:id:kanmi-program:20201105234401p:plain

 

 

非同期通信でエラーが発生した場合

import { Component, OnInit } from '@angular/core';
import { from, throwError, zip } from 'rxjs';
import { map } from 'rxjs/operators';

@Component({
 selector: 'app-rxjs',
 templateUrl: './rxjs.component.html',
 styleUrls: ['./rxjs.component.scss'],
})
export class RxjsComponent implements OnInit {
 constructor() {}

 ngOnInit(): void {}

 /**
  * 注文処理
  */
 orderRamen() {
   // 注文を受けた順に格納(1人目「太麺、醤油」 2人目「細麺、塩」3人目「たまご麺、味噌」)
   let menOrder = from(['太麺', '細麺', 'たまご麺']);
   let soupOrder = from(['醤油', '塩', '味噌']);
   let error = throwError("落としたException");

   zip(menOrder, soupOrder, error)
    .pipe(
       map(([men, soup]) => {
         // 順番に結果を取得して提供
         console.log(`[麺]:${men}, [スープ]:${soup}`);
      })
    )
    .subscribe(
      (result)=>{},
      (error)=>{
         console.log(error);
      }
    );
}
}
  • 実行結果

    • subscribe側でエラーを検知

f:id:kanmi-program:20201105234304p:plain

 

 

subscribe側に値を渡したい

  • map内でreturnを使えばobservable.nextに値が設定される

import { Component, OnInit } from '@angular/core';
import { from, zip } from 'rxjs';
import { map } from 'rxjs/operators';

@Component({
 selector: 'app-rxjs',
 templateUrl: './rxjs.component.html',
 styleUrls: ['./rxjs.component.scss'],
})
export class RxjsComponent implements OnInit {
 constructor() {}

 ngOnInit(): void {}

 /**
  * 注文処理
  */
 orderRamen() {
   // 注文を受けた順に格納(1人目「太麺、醤油」 2人目「細麺、塩」3人目「たまご麺、味噌」)
   let menOrder = from(['太麺', '細麺', 'たまご麺']);
   let soupOrder = from(['醤油', '塩', '味噌']);

   zip(menOrder, soupOrder)
    .pipe(
       map(([menSozai, soupSozai]) => {
         // 順番に結果を取得して提供
         return { men: menSozai, soup: soupSozai };
      })
    )
    .subscribe(
      (result) => {
         console.log(result);
      },
      (error) => {
         console.log(error);
      }
    );
}
}
  • 実行結果

f:id:kanmi-program:20201105234450p:plain



 

 

通化して処理の切り出し

import { Component, OnInit } from '@angular/core';
import { from, Observable, zip } from 'rxjs';
import { map } from 'rxjs/operators';

@Component({
 selector: 'app-rxjs',
 templateUrl: './rxjs.component.html',
 styleUrls: ['./rxjs.component.scss'],
})
export class RxjsComponent implements OnInit {
 constructor() {}

 ngOnInit(): void {}

 /**
  * 注文処理
  */
 orderRamen() {
   // 注文を受けた順に格納(1人目「太麺、醤油」 2人目「細麺、塩」3人目「たまご麺、味噌」)
   let menOrder = from(['太麺', '細麺', 'たまご麺']);
   let soupOrder = from(['醤油', '塩', '味噌']);

   this.createRamen(menOrder, soupOrder).subscribe((result) => {
     console.log(`[麺]:${result.men}, [スープ]:${result.soup}`);
  });
}

 /**
  * 作成処理
  * @param menOrder 麺の注文
  * @param soupOrder スープの注文
  */
 private createRamen(
   menOrder: Observable<string>,
   soupOrder: Observable<string>
): Observable<any> {
   return zip(menOrder, soupOrder).pipe(
     map(([menSozai, soupSozai]) => {
       // 順番に結果を取得して提供
       return { men: menSozai, soup: soupSozai };
    })
  );
}
}

 

 

非同期処理の実行結果を使って非同期処理を実行する

  • 以下のオペレーターを仕様によって使い分ける

    • concatMap

      • 値が流れてきた順番で実行

    • mergeMap

      • 準備ができたものから随時実行

    • switchMap

      • 流れてきた値の処理中に再度値が流れてきたら前の処理はキャンセル

 

例)Cognitoのトークンを取得してAPI Gatewayに通信する

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { RestGetInput } from '../aws/api/data/rest-get-input';

import { RestService } from '../aws/api/rest.service';
import { CognitoService } from '../aws/auth/cognito.service';

@Injectable({
 providedIn: 'root',
})
export class OrderService {
 private API_NAME = 'RAMEN';
 private API_PATH = '/order';

 constructor(
   private restService: RestService,
   private cognitoService: CognitoService
) {}

 /**
  * 注文情報取得
  * @param orderId
  */
 public getOrder(orderId: string): Observable<IOrderInfo> {
   let restGetInput = new RestGetInput();

   restGetInput.apiName = this.API_NAME;
   restGetInput.path = this.API_PATH;
   restGetInput.init = { queryStringParameters: orderId };

   return new Observable((observer) => {
     // Token取得
     this.cognitoService
      .getAuthToken()
      .pipe(
         switchMap((token) => {
           restGetInput.token = token;
           // GET通信
           return this.restService.get(restGetInput);
        })
      )
       // 通信処理の確認
      .subscribe(
        (result) => {
           // ステータスコードチェック
           const statusCode = result.statusCode;
           if (statusCode !== 200) {
             // エラー通知
             observer.error('取得処理に失敗');
             return;
          }

           const respnse = result.response;

           // 処理結果を通知
           observer.next({
             orderId: respnse.orderId,
             order: respnse.order,
          });
        },
        (error) => {
           // エラー通知
           observer.error('取得処理に失敗');
        }
      );
  });
}
}

/**
* 注文情報
*/
interface IOrderInfo {
 orderId: string;
 order: Array<string>;
}