【Angular】実装メモ~Router編~
-
画面遷移や表示制御を行う
-
詳細は以下を参照
実装
画面遷移の設定
-
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の実装
-
画面遷移時に認証状態を確認する
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編~
-
FormGroupで実装
-
入力フォームを次の仕様で作る
-
縦並び
-
-
バリデーションエラー時はボタンを非活性
-
UIライブラリにAngular Material + flex-layoutを使用
-
実装イメージ
(初期表示)
(入力時)
(エラー時)
実装
-
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概要
-
Reduxの思想をAngularで実装するためのライブラリ
-
Effectsなど拡張機能あり
-
用語集
Component
-
Angularのコンポーネント
Action
-
「何が起こったか」という情報を持ったオブジェクト
-
コンポーネントからのイベントをトリガーとしてActionオブジェクトをStoreにDispatchする
Reducer
-
ActionオブジェクトとStateを受け取りStateを更新する
-
更新する場合はStateを新規のオブジェクトとして作成する
Store
-
Stateを保存する場所
Selector
-
Storeから特定のデータを取り出す
Effects
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が更新される
-
データフロー詳細
-
ユーザー入力からActionを作成
-
StoreへActionをdispatch(送信)
-
dispatchされたActionとStateをReducerへ渡す
-
Reducerが作成した新しいStateをStoreが保存
Reduxの3原則
-
Reduxの設計思想
-
下記より抜粋
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を使う
-
バージョンは3.0.2
-
ライブラリの詳細は以下参考
-
実装
ライブラリのインストール
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」タブから確認できる
プライバシーポリシー
個人情報の利用目的
当ブログでは、お問い合わせや記事へのコメントの際、名前やメールアドレス等の個人情報を入力いただく場合がございます。 取得した個人情報は、お問い合わせに対する回答や必要な情報を電子メールなどをでご連絡する場合に利用させていただくものであり、これらの目的以外では利用いたしません。
広告について
当ブログでは、第三者配信の広告サービス(Googleアドセンス)を利用しており、ユーザーの興味に応じた商品やサービスの広告を表示するため、クッキー(Cookie)を使用しております。 クッキーを使用することで当サイトはお客様のコンピュータを識別できるようになりますが、お客様個人を特定できるものではありません。
Cookieを無効にする方法やGoogleアドセンスに関する詳細は「広告 – ポリシーと規約 – Google」をご確認ください。
アクセス解析ツールについて
当ブログでは、Googleによるアクセス解析ツール「Googleアナリティクス」を利用しています。このGoogleアナリティクスはトラフィックデータの収集のためにクッキー(Cookie)を使用しております。トラフィックデータは匿名で収集されており、個人を特定するものではありません。
コメントについて
当ブログへのコメントを残す際に、IP アドレスを収集しています。 これはブログの標準機能としてサポートされている機能で、スパムや荒らしへの対応以外にこのIPアドレスを使用することはありません。 なお、全てのコメントは管理人が事前にその内容を確認し、承認した上での掲載となります。あらかじめご了承ください。
【RxJS】ユースケースから理解するRxJS
ユースケース
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);
}
);
}
}
-
実行結果
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にそれぞれ値が流れてきてから処理を実施している
-
非同期通信でエラーが発生した場合
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側でエラーを検知
-
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);
}
);
}
}
-
実行結果
共通化して処理の切り出し
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>;
}