🅰️

Angular でフォームの自動保存と Post のキューイングを実装する

2022/11/07に公開

概要

Angular アプリで次の要件を実現したい。

コードは Github にあります。

Post のキューイング

  • フォームの値を API で Post する場合は、キューに追加して順番に処理します。
    • ユーザ操作でフォームの値を Post した時に自動保存中だった場合は、自動保存が完了してから実施します。

フォームの自動保存

  • 1分間隔で自動保存を実施します。
  • 自動保存はフォームが編集済みの場合のみ、フォームの値を Post のキューに追加します。

実装

Angular アプリを作成

基本的な Angular アプリの作成手順なので詳細は省きます。

Github のコミット

  1. Angular アプリを新規作成します。

  2. コンポーネントに入力フォームを実装します。

  3. サービスに API 処理を実装します。

  4. アプリを次のコマンドで実行します。

    npm run start
    
  5. アプリをブラウザで開きます。

モックを作成

Mock Service Worker をセットアップしてモックを作成します。

セットアップ方法の詳細は Mock Service Worker の公式サイトを参照してください。

Github のコミット

  1. Mock Service Worker をセットアップします。

  2. モックを作成します。

    // 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));
      }),
    ];
    
  3. アプリで Add user ボタンを押下して、API のレスポンスが返ってくることを確認します。

フォームの自動保存を実装

Github のコミット

  1. 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);
          });
      }
    
  2. コンポーネントに自動保存用のプロパティを追加します。

    // src/app/app.component.ts
      private readonly AUTO_SAVE_INTERVAL = 60000;  // 自動保存を1分間隔で実施
      private autoSave: NodeJS.Timer | null = null; // 自動保存処理
    
  3. コンポーネントに自動保存を有効化するメソッドを作成します。

    // 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);
      }
    
  4. コンポーネントを初期化する際に自動保存を有効化します。

    // src/app/app.component.ts
      ngOnInit(): void {
        this.enableAutoSave();
      }
    
  5. コンポーネントに自動保存を無効化するメソッドを作成します。

    // src/app/app.component.ts
      private disableAutoSave(): void {
        console.log('Disable auto save');
        // 自動保存が既に無効の場合はメソッドを抜けます。
        if (this.autoSave === null) {
          return;
        }
    
        clearInterval(this.autoSave);
        this.autoSave = null;
      }
    
  6. コンポーネントを破棄する際に自動保存を無効化します。

    // src/app/app.component.ts
      ngOnDestroy(): void {
        this.disableAutoSave();
      }
    

Post のキューイングを実装

Github のコミット

  1. サービスの 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);
            }
          })
        );
      }
    
  2. サービスにキューイング用のインターフェイスを定義します。

    // src/app/app.service.ts
    export interface PostUserQueue {
      user: User;
      isAutoSave: boolean;
    }
    
  3. サービスにキューイング用のプロパティを追加します。

    // src/app/app.service.ts
      private postUserQueue = new Subject<PostUserQueue>();
      public postUserQueue$: Observable<number>;
    
  4. サービスのコンストラクタでキューイング用のプロパティを設定します。

    // 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))
        );
      }
    
  5. サービスにキューに追加するためのメソッドを追加します。

    // src/app/app.service.ts
      addToPostUserQueue(queue: PostUserQueue) {
        console.log('Add To PostUserQueue: ', queue);
        this.postUserQueue.next(queue);
      }
    
  6. コンポーネントを初期化する際にキューを購読します。

    // src/app/app.component.ts
      ngOnInit(): void {
        // キューの購読を開始して、Post を順番に実行します。
        this.appService.postUserQueue$.subscribe((result) =>
          console.log('Queue result: ', result)
        );
    
        this.enableAutoSave();
      }
    
  7. 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);
      }
    
  8. 自動保存でフォームの値をキューに追加します。

    // 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