Angular でフォームの自動保存と Post のキューイングを実装する
概要
Angular アプリで次の要件を実現したい。
コードは Github にあります。
Post のキューイング
- フォームの値を API で Post する場合は、キューに追加して順番に処理します。
- ユーザ操作でフォームの値を Post した時に自動保存中だった場合は、自動保存が完了してから実施します。
フォームの自動保存
- 1分間隔で自動保存を実施します。
- 自動保存はフォームが編集済みの場合のみ、フォームの値を Post のキューに追加します。
実装
Angular アプリを作成
基本的な Angular アプリの作成手順なので詳細は省きます。
-
Angular アプリを新規作成します。
-
コンポーネントに入力フォームを実装します。
-
サービスに API 処理を実装します。
-
アプリを次のコマンドで実行します。
npm run start
-
アプリをブラウザで開きます。
モックを作成
Mock Service Worker をセットアップしてモックを作成します。
セットアップ方法の詳細は Mock Service Worker の公式サイトを参照してください。
-
Mock Service Worker をセットアップします。
-
モックを作成します。
// src/mocks/handlers.js import { rest } from "msw"; export const handlers = [ rest.post("/user", (req, res, ctx) => { const RESPONSE_STATUS_CODE = 200; const RESPONSE_DELAY = 0; return res(ctx.status(RESPONSE_STATUS_CODE), ctx.delay(RESPONSE_DELAY)); }), ];
-
アプリで
Add user
ボタンを押下して、API のレスポンスが返ってくることを確認します。
フォームの自動保存を実装
-
Add user
ボタンを押下したらフォームを未編集に設定し、自動保存で Post されないようにします。// src/app/app.component.ts onSubmit(): void { // フォームを未編集に設定します。 this.formGroup.markAsPristine(); // フォームの dirty を false に設定 this.formGroup.markAsUntouched(); // フィー無の touched = false に設定 this.appService .postUser({ name: this.formGroup.value.name || '', }) .subscribe((status) => { console.log('post result:', status); }); }
-
コンポーネントに自動保存用のプロパティを追加します。
// src/app/app.component.ts private readonly AUTO_SAVE_INTERVAL = 60000; // 自動保存を1分間隔で実施 private autoSave: NodeJS.Timer | null = null; // 自動保存処理
-
コンポーネントに自動保存を有効化するメソッドを作成します。
// src/app/app.component.ts private enableAutoSave(): void { console.log('Enable auto save'); // 自動保存が既に有効の場合はメソッドを抜けます。 if (this.autoSave !== null) { return; } // 自動保存処理を設定します。 this.autoSave = setInterval(() => { // フォームが編集済みの場合は Post します。 if (this.formGroup.touched && this.formGroup.dirty) { this.appService .postUser({ name: this.formGroup.value.name || '', }) .subscribe((status) => { console.log('post result:', status); }); } }, this.AUTO_SAVE_INTERVAL); }
-
コンポーネントを初期化する際に自動保存を有効化します。
// src/app/app.component.ts ngOnInit(): void { this.enableAutoSave(); }
-
コンポーネントに自動保存を無効化するメソッドを作成します。
// src/app/app.component.ts private disableAutoSave(): void { console.log('Disable auto save'); // 自動保存が既に無効の場合はメソッドを抜けます。 if (this.autoSave === null) { return; } clearInterval(this.autoSave); this.autoSave = null; }
-
コンポーネントを破棄する際に自動保存を無効化します。
// src/app/app.component.ts ngOnDestroy(): void { this.disableAutoSave(); }
Post のキューイングを実装
-
サービスの Post メソッドの引数に
isAutoSave
を追加して Post したのが自動保存なのかユーザ操作なのか判定し、ユーザ操作ならスナックバーで結果を表示します。// src/app/app.service.ts postUser(user: User, isAutoSave: boolean): Observable<number> { console.log('Post user: ', user); return this.http.post('/user', user, { observe: 'response' }).pipe( map((res) => res.status), catchError((err: HttpErrorResponse) => of(err.status)), tap((status) => { const msg: string = status === 200 ? 'Successfully added user.' : 'Failed to add user.'; // 自動保存が Post した場合はコンソールに結果を出力します。 if (isAutoSave) { console.log(`Auto save: ${msg}`); // ユーザ操作で Post した場合はスナックバーに結果を表示します。 } else { console.log(`On submit: ${msg}`); this.snackBar.open(msg); } }) ); }
-
サービスにキューイング用のインターフェイスを定義します。
// src/app/app.service.ts export interface PostUserQueue { user: User; isAutoSave: boolean; }
-
サービスにキューイング用のプロパティを追加します。
// src/app/app.service.ts private postUserQueue = new Subject<PostUserQueue>(); public postUserQueue$: Observable<number>;
-
サービスのコンストラクタでキューイング用のプロパティを設定します。
// src/app/app.service.ts constructor(private http: HttpClient, private snackBar: MatSnackBar) { this.postUserQueue$ = this.postUserQueue.pipe( concatMap((queue) => this.postUser(queue.user, queue.isAutoSave)) ); }
-
サービスにキューに追加するためのメソッドを追加します。
// src/app/app.service.ts addToPostUserQueue(queue: PostUserQueue) { console.log('Add To PostUserQueue: ', queue); this.postUserQueue.next(queue); }
-
コンポーネントを初期化する際にキューを購読します。
// src/app/app.component.ts ngOnInit(): void { // キューの購読を開始して、Post を順番に実行します。 this.appService.postUserQueue$.subscribe((result) => console.log('Queue result: ', result) ); this.enableAutoSave(); }
-
Add user
ボタンを押下したらフォームの値をキューに追加します。// src/app/app.component.ts onSubmit(): void { this.formGroup.markAsPristine(); this.formGroup.markAsUntouched(); const queue: PostUserQueue = { user: { name: this.formGroup.value.name || '', }, isAutoSave: false, // ユーザ操作 }; // フォームの値をキューに追加します。 this.appService.addToPostUserQueue(queue); }
-
自動保存でフォームの値をキューに追加します。
// src/app/app.component.ts private enableAutoSave(): void { console.log('Enable auto save'); if (this.autoSave !== null) { return; } this.autoSave = setInterval(() => { if (this.formGroup.touched && this.formGroup.dirty) { const queue: PostUserQueue = { user: { name: this.formGroup.value.name || '', }, isAutoSave: true, // 自動保存 }; // フォームの値をキューに追加します。 this.appService.addToPostUserQueue(queue); } }, this.AUTO_SAVE_INTERVAL); }
Discussion