Angular + Amplify DataStoreを試してみる #reinvent

2019.12.16

どうも!大阪オフィスの西村祐二です。

この記事はAngular Advent Calendar 2019の16日目の記事です。

re:Invent 2019期間中に、Amplifyの新機能「Amplify DataStore」が発表されました!今回は、この機能をAngularで試してみたいと思います。

「Amplify DataStore」を使うことによってデバイスやブラウザのストレージを使ったオフライン対応などが簡単にできたり、開発者はローカルのデータを扱うような感覚で、簡単に分散されたデータを扱うことができます。

速報記事は下記になります。Amplify DataStoreの説明は下記を参照ください。

【速報】デバイスのデータストア向けの新機能「Amplify DataStore」が公開されました! #reinvent

https://aws-amplify.github.io/docs/js/datastore

試してみる

環境

Angular CLI: 8.3.20
Node: 12.13.0
OS: darwin x64
Angular: 8.2.14

準備

CLIなどのツールをインストールしておきます。

$ npm i -g @angular/cli
$ npm i -g @aws-amplify/cli@latest

Angularで雛形のサンプルアプリケーションを作ります。

$ ng new datastore-demo --style=scss --routing
$ cd datastore-demo

amplifyの設定など追加する初期セットアップをします。

$ npx amplify-app@latest

必要なライブラリをインストールしておきます。

$ npm i @aws-amplify/core @aws-amplify/datastore

GraphQLスキーマを変更

今回のサンプルでは下記のスキーマを使用します。

amplify/backend/api//schema.graphql

enum PostStatus {
  ACTIVE
  INACTIVE
}

type Post @model {
  id: ID!
  title: String!
  rating: Int!
  status: PostStatus!
}

ファイルの編集が終わったら下記コマンドを実行します。

$ npm run amplify-modelgen

コマンドの実行が完了するとCFnであったり、GraphQLスキーマであったり、いろいろなファイルを生成してくれます。

AWSリソースをデプロイ

下記コマンドを実行してAWSリソースをデプロイします。

$ npm run amplify-push

npm run amplify-pushでエラーが出る場合amplify initを実行してから再度実行すると解消するかもしれません。

デプロイが完了すると

  • DynamoDB
  • AppSync

などのリソースが作成されます。

▼AppSyncに作成されたAPI

▼DynamoDBに作成されたテーブル

src/aws-exports.jsにappsyncなどのエンドポイントや認証情報が記載されたファイルが生成されます。Githubに載せるときは取り扱いにご注意ください。

Angularであれば、environmentsのファイルに他の情報と一緒に記載し、管理するほうが行儀がいいかもしれません。

初期設定を行う

AngularでAmplifyを利用する際、はじめに設定を変更しておきます。

tsconfig.app.json

"compilerOptions": {
    "types" : ["node"]
}

polyfills.ts

(window as any).global = window;
(window as any).process = {
  env: { DEBUG: undefined },
};

global.Buffer = global.Buffer || require('buffer').Buffer;

実装していく

ロジック

下記のように実装しました。

動作確認が目的のため、必要最低限の機能のみ実装しています。また、サンプル実装なので実装内容についてはご了承ください。

一部、エラーが解決できず、anyを利用しています。

src/app/app.component.ts

import { Component, OnInit } from '@angular/core';
import Amplify from '@aws-amplify/core';
import { DataStore, Predicates } from '@aws-amplify/datastore';
import { Post, PostStatus } from '../models';

import { environment } from '../environments/environment';
import { from } from 'rxjs';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  title = 'datastore-demo';
  data = from(DataStore.query(Post, Predicates.ALL));
  constructor() {
    Amplify.configure(environment.amplify.AppSync);
  }
  ngOnInit() {
    this.subscription();
  }
  create() {
    DataStore.save(
      new Post({
        title: `New title ${Date.now()}`,
        rating: 1,
        status: PostStatus.ACTIVE
      })
    );
  }
  deleteAll() {
    DataStore.delete(Post, Predicates.ALL);
  }
  list() {
    this.data = from(DataStore.query(Post, Predicates.ALL));
  }
  delete(id: string) {
    this.fetch(id).subscribe(item => DataStore.delete(item));
  }
  fetch(id: string) {
    return from(DataStore.query(Post as any, id));
  }
  update(id: string) {
    this.fetch(id).subscribe(item => {
      DataStore.save(
        Post.copyOf(item as any, updated => {
          updated.title = `title ${Date.now()}`;
          updated.rating = 4;
        })
      );
    });
  }
  subscription() {
    DataStore.observe(Post as any).subscribe(msg => {
      this.list();
      console.log(msg);
    });
  }
}

特徴的なところを簡単に説明します。

  • 6行目:src/aws-exports.jsに記載された情報はenvironmentsのファイルに移動させています。
  • 16行目:Async Pipeを使いたいのでfromで囲ってobservableにしています。
  • 21行目:データの変更があったら画面を更新するために、subscriptionで購読するようにしています。
  • 23行目:DataStoreを使ってitemを登録する処理です。idは自動的に付与されます。
  • 44行目:itemを更新する処理です。titleとratingを更新します。versionは自動的に更新されます。

画面

動作確認が目的のため、必要最低限の機能のみ実装します。また、サンプル実装なので実装内容についてはご了承ください。

  • 表にはDynamoDBに登録されているデータが表示されます。
  • 「NEW」ボタンで新しいitemを登録します。
  • 「DELETE ALL」ボタンですべてのデータを削除します。
  • delete itemidを入力してボタンをクリックすると入力したitemを削除します。
  • update itemidを入力してボタンをクリックするとtitleratingversionが更新されます。

src/app/app.component.html

<h1>
  {{ title }}
</h1>

<table border="1">
  <tr>
    <th>id</th>
    <th>title</th>
    <th>rating</th>
    <th>_version</th>
  </tr>
  <tr *ngFor="let item of data | async">
    <td>{{ item.id }}</td>
    <td>{{ item.title }}</td>
    <td>{{ item.rating }}</td>
    <td>{{ item._version }}</td>
  </tr>
</table>

<div>
  <input type="button" value="NEW" (click)="create()" />
  <input type="button" value="DELETE ALL" (click)="deleteAll()" />
</div>

<div>
  delete item:<input #id />
  <button (click)="delete(id.value); id.value = ''">delete</button>
</div>
<div>
  update item:<input #id />
  <button (click)="update(id.value); id.value = ''">update</button>
</div>

動作確認

下記コマンドでローカル実行し、http://localhost:4200にアクセスします。

$ ng serve

chromeのdevtoolで確認するとIndexedDBにdatabaseが作成されていることがわかります。

  • 追加

画面のNEWボタンをクリックします。

すると、DynamoDBには下記のようなデータが追加されます。作成更新時刻、バージョン情報など付随情報が自動的に追加されています。

  • 更新

idを入力して更新してみます。購読しているので、他のブラウザで開いた状態でも、自動的に更新されます。

  • 削除

削除を実行すると、DynamoDBのデータ自体は削除されず、バージョンのインクリメントと、deleteフラグの付与とTTLの設定がされる挙動となっていました。

オフラインモードを試す

Datastoreを使っていれば、オフラインモード用にコードを追加・変更は不要です。

下記Gifのように、ネットワークを切った状態でも問題なく、データの追加・更新ができました。

また、画面リロードしてもデータが取得されました。

ネットワークを復活させると、サーバと通信がはじまり、バージョン情報の付与など実行され同期されたことがわかります。

さいごに

Angular + Amplify DataStoreを試してみました。

かなり強力な機能でした。

AmplifyとAWS AppSyncと組み合わせるとことで、オフライン対応したアプリケーションを簡単に構築することができました。

オフライン時の挙動を開発者側で実装せずに済むのでかなりの工数削減が期待できそうです。

ただ、DynamoDBのデータを削除してもアプリケーションでは表示され続けるなどといったことがあったので、挙動はきちんと理解しておく必要があるかと思います。

上記はブラウザのIndexedDBに保存されるデータベースを削除することで解消しました。

興味のある方は是非試してみてください。

誰かの参考になれば幸いです。