【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>;
}
【AWS】S3からダウンロードしたファイルのContent-Typeが変なんです
現象
S3からダウンロードしたファイルのContent-Typeがbinary/octet-streamになってしまう。
原因
S3へのアップロード時にContent-Typeを指定していない。
APIでのアップロードなどで忘れがち。
対策
Amplifyの場合
// ファイルアップロード
Storage.put(key, fileObject, {
level: fileLevel,
contentType: ${contentType},
progressCallback(progress) {},
});
【AWS】Amplify使ってAPI Gatewayにリクエスト送信したい
-
-
Angular CLIの環境構築は実施済とする
<参考>
Amplify Docs
https://docs.amplify.aws/start/getting-started/installation/q/integration/angular
環境
NO | 項目 | バージョン |
---|---|---|
1 | Angular | 10.0.5 |
2 | Amplify | 3.3.4 |
3 | Typescript | 3.9.5 |
実装
Angularプロジェクトを作成
ng new ${プロジェクト名}
Amplifyライブラリを追加
npm install --save aws-amplify @aws-amplify/ui-angular
AWSの設定ファイルを作成
フォーマットはAmplifyコマンドで生成されてるものに合わせました。
aws-export.jsを作成
${プロジェクトルート}/src/aws-exports.js
import Amplify from 'aws-amplify';
// cognitoやS3に接続する設定を追記する箇所
const awsmobile = {
};
export default awsmobile;
// API Gatewayの設定
Amplify.configure({
API: {
endpoints: [
{
name: '${API名}',
endpoint: '${エンドポイント}'
}
]
}
});
API名は↓を設定。
エンドポイントは↓を設定。
設定ファイル読み込み処理を実装
app.module.tsに実装しました。
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
// Amplifyと設定ファイルをインポート
import Amplify from 'aws-amplify';
import config from '../aws-exports';
// 設定ファイルを読み込ませる
Amplify.configure(config)
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Shimの追加
polyfills.tsにglobalとprocessのShimを追記する。
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
// globalとprocessのShimを追記
(window as any).global = window;
(window as any).process = {
env: { DEBUG: undefined },
};
リクエスト送信処理の実装
GET通信の場合
import { Component } from '@angular/core';
import { from } from 'rxjs';
// amplifyをインポート
import { API } from 'aws-amplify';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
title = 'AmplifyTest';
/**
* API Gatewayに通信する
*/
sendRequest() {
const apiName = '${API名}';
const path = '${APIパス}';
const init = {
// クエリパラメータを設定
queryStringParameters: {
orderId: '1234',
},
// ヘッダーを設定
headers: {
'Content-Type': 'application/json',
},
};
// API実行
// RXJSで扱いたいためPromiseをfromで変換
from(API.get(apiName, path, init)).subscribe(
(result) => {
console.log(result);
},
(error) => {
console.log(error);
}
);
}
}
※CORSでエラーが出る場合
POST通信の場合
import { Component } from '@angular/core';
import { from } from 'rxjs';
// amplifyをインポート
import { API } from 'aws-amplify';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
title = 'AmplifyTest';
/**
* API Gatewayに通信する
*/
sendPOSTRequest() {
const apiName = 'TestLambda-API';
const path = '/history';
const init = {
// ボディを追加
body: {
name: 'taro',
order: '醤油マシマシ',
date: '2020-10-11',
},
// ヘッダーを追加
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
};
// API実行
// RXJSで扱いたいためPromiseをfromで変換
from(API.post(apiName, path, init)).subscribe(
(result) => {
console.log(result);
},
(error) => {
console.log(error);
}
);
}
}
※CORSでエラーが出る場合
その他
(1)CORSでエラーになる
①CORSを適用したいAPIに以下のパスを追加する。
No | 項目 | 値 |
---|---|---|
1 | パス | {proxy+} |
2 | メソッド | OPTIONS |
②追加したパスに適当なLambdaを統合する。
※ドキュメントにCORSのレスポンスが記載されているがAPI Gateway V2の場合無視されるので、
左メニュー「開発」→「CORS」で設定する。
Lambda
※初期状態でもOK
import json
def lambda_handler(event, context):
# TODO implement
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
CORS
<参考>
REST API リソースの CORS を有効にする
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/how-to-cors.html
Angular HttpInterceptorを使いたい
AmplifyはHTTPクライアントにAxiosを使用しているためAngularのInterceptorが使用できない