Help us understand the problem. What is going on with this article?

AngularとFirebaseで4ヶ月掛けて作ったポートフォリオ

SS 2020-04-02 14.25.40.png

はじめに

フロントエンドエンジニアとして就職するために開発したSPAです。
制作期間: 2019年12月〜2020年3月 (約120日)

ポートフォリオ
https://miru.page/

GitHubのリポジトリ
https://github.com/uotya/miru

使用技術

  • Angular9
  • AngularMaterial
  • Firebase (Auth, Database, Storage, Functions)
  • Algolia
  • Stripe
  • GitHub Actions

アプリの概要

どんなアプリ?

URLがたくさん貼られたNAVERまとめのような記事が、シンプルなUIで作れます。
Image from Gyazo

ページ紹介

トップページ

ボタンをイラストで飾ることでAngularMaterialのUIキット感を抑えています。
未ログインの場合は、ログインを促すダイアログを表示されます。

(イラストは制作していません 🤦)
Image from Gyazo

記事の作成

アイコンボタンでURLの入力欄を増やしたり、減らしたり出来ます。
各記事ページ下部から編集や削除も行えます。

また、入力されたURLから、自作のAPIによってOGPとFaviconを取得します。

Image from Gyazo

記事の一覧

「自分の投稿」と「いいねが多い投稿」を一覧表示出来ます。
いずれのページも無限スクロールを用いた追加読み込みで、1度に読み込む記事の数を抑えています。

記事の検索

Firestoreには全文検索の機能が無いので、公式ドキュメントでも推奨されている外部サービスのAlgoliaと連携することで実装しています。
Image from Gyazo

開発者へ寄付

Stripeを使って課金機能も実装しました。
顧客データをStripe側に保管しているので、2回目以降の決済にはカード情報の登録は不要です。
Image from Gyazo

その他の機能

  • PWA対応
  • TwitterAPIでアバターの自動同期
  • アバターの変更 (トリミング含む)
  • データベースの定期バックアップ
  • ダイナミックレンダリングによるOGP対応
  • アカウントの削除

ディレクトリ構成

この画像だけでは分かりづらいとは思うのですが、ログイン・CRUD・検索・課金を実装したアプリのファイル規模の参考程度にはなるかと思います。

  • モジュールはページ単位で作成
  • DRY原則に則ってミニマムなコンポーネントを複数箇所で使い回す
  • 複数のModuleで重複したインポートをしない

これらのポイントを意識しながら開発しました。
SS 2020-04-02 16.47.15.png

苦労した実装

開発に着手したのは12月上旬ですが、当時はHTMLとSassとJavaScript(Progateでかじった程度)の知識しかありませんでした。

AngularとFirebaseをキャッチアップしながら開発したので、もちろん詰まる点ばかりで、しばらくは暗黒の森を彷徨うような気持ちで開発していました。

その中でも、特に苦戦した実装をピックアップしました。

データベース設計

画像のように、記事のドキュメントの中にユーザー名やアバターのURLを含めてしまうと、名前やアバターが変更された際にそのユーザーが投稿したすべての記事を更新しなければなりません。
SS 2020-04-03 13.06.42.png

Firestoreのリクエスト数を減らすためにも「記事」と「ユーザー」のデータは別々のコレクションで管理することにしました。

保存先を分けることはAngularFireの基本が理解出来ていれば、何ら難しいことではないのですが、別コレクションにある「記事のデータ」と「ユーザーのデータ」を取得して1つのObservableに統合するにはRxJSの解像度の高い理解が必須だったため、苦戦しました。

ちなみにこの実装は、同じように詰まった人のためにこちらの記事で解説しています。

いいね機能

いいねは想定外の不具合に見舞われたこともあり、実装手順を理解してから、実際に実装が終わるまで1番長かったです。
大まかな処理フローは以下のような感じです。具体的に詰まった点を紹介しておきます。

  1. フロントで表示用の変数をカウントアップorダウン
  2. Serviceでいいねを押した記事のIDを保存or削除
  3. CloudFunctionsで記事データ内のいいね数をカウントアップorダウン

記事データ内にいいね数のフィールドを持たせているのに、フロントでも表示用の変数を使う理由としては、Functionsを伴う処理は数秒のラグが生じるためです。

べき等処理

FunctionsでFirestoreトリガーを使うと、ごく稀に2回以上発火してしまうことがあり、公式ドキュメントでもべき等処理を実装することが推奨されています。

カウントアップorダウンが2回以上発火されるといいね数が正確性が損なわれるので、いいねにはべき等処理を実装しました。

一覧表示をする際の不具合

いいね数をフロントの変数で管理するため、記事のデータは常に最新のものをキャッチし続けるのではなく、take(1)で一度だけ取得する実装にしました。
その際にこの手順を踏むと、キャッシュの影響で1記事しか一覧表示されないことがありました。

  1. 特定のドキュメントを取得する
  2. コレクションを取得して一覧表示する

対処法についてはAngularFireのIssueで議論されており、以下のようにgetメソッドを用いてサーバーから直接データを取得する方法で対処しました。

collection('users').get({ source: 'server' });

さいごに

今回のポートフォリオの制作を通じて、数日でCRUDアプリが作れるくらいの自力が付きました。
また、日本語情報の少ないAngularで開発したことによって、英語のドキュメントやIssueに対する抵抗感もさっぱり無くなりました。

挫折せずに最後まで楽しくやり切れたのは、間違いなく付きっきりで教えてくれたメンターのNinoさんのおかげです。本当にありがとうございました🙇‍♂️

リポジトリのCollaboratorとして、コードレビューもして頂きながら開発しました。

Ninoさんが運営しているスクール「CAMP」のリンクも貼っておきます。
興味がある方はぜひ相談してみてください。
https://to.camp/about

最後まで読んで頂き、ありがとうございました!


Twitter
https://twitter.com/uotya
GitHub
https://github.com/uotya

otyamaru
フロントエンドエンジニア
https://twitter.com/uotya
necomesi
株式会社ネコメシは、三軒茶屋にオフィスを構えるウェブ制作会社です。社名はゆるくても仕事はまじめです。サイトリニューアル、新規サービス立ち上げ、フロントエンドの各種実装など、ぜひお手伝いさせてください。
https://necomesi.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away