フロントエンド | 株式会社Altus-Five / 株式会社Altus-Five は、技術力で勝負するシステム開発会社です。 Mon, 01 Sep 2025 00:37:48 +0000 ja hourly 1 https://wordpress.org/?v=6.8.2 /wp-content/uploads/2025/01/cropped-favicon-32x32.png フロントエンド | 株式会社Altus-Five / 32 32 08 専門業者向けB2B ECサイト /works/2025/08/29/%e5%b0%82%e9%96%80%e6%a5%ad%e8%80%85%e5%90%91%e3%81%91b2b-ec%e3%82%b5%e3%82%a4%e3%83%88/ /works/2025/08/29/%e5%b0%82%e9%96%80%e6%a5%ad%e8%80%85%e5%90%91%e3%81%91b2b-ec%e3%82%b5%e3%82%a4%e3%83%88/#respond Fri, 29 Aug 2025 11:30:36 +0000 /?p=753 プロジェクト概要 専門業者向けB2B ECサイトの保守開発において、複雑なドメイン知識と最新技術を組み合わせた包括的なソリューションを提供しました。レガシーシステムの段階的リプレースと機能追加により、お客様の事業継続性を […]

The post 08 専門業者向けB2B ECサイト first appeared on 株式会社Altus-Five.

]]>
プロジェクト概要

専門業者向けB2B ECサイトの保守開発において、複雑なドメイン知識と最新技術を組み合わせた包括的なソリューションを提供しました。レガシーシステムの段階的リプレースと機能追加により、お客様の事業継続性を保ちながらシステムの競争力向上を実現し、持続可能な成長基盤を構築いたしました。

技術要素

  • Python
  • BigQuery
  • TypeScript
  • React
  • Next.js
  • CI/CD
  • 継続的デリバリー
  • AI駆動開発

プロジェクトの特徴

  • 複雑なドメイン知識への横断的対応力
    多岐にわたる専門分野を理解し統合的にシステム設計
  • レガシーシステムの段階的リプレース戦略
    事業を止めずに安全かつ効率的にシステム近代化を実現
  • 最新技術の積極的導入
    AI駆動開発など先端技術を活用した開発効率の向上
  • 非機能要件への配慮した設計
    パフォーマンス・セキュリティを重視した堅牢な基盤構築

成果

  • 事業継続性を保ちながらのシステム近代化の完遂
  • 段階的リプレースによるシステム移行リスクの最小化
  • CI/CD導入による開発・運用効率の大幅向上
  • レガシー技術から最新技術への段階的移行の成功

Altus-Fiveの強み

複雑なドメインへの深い理解力と横断的対応力、レガシーシステムの近代化ノウハウ、最新技術の積極的導入により、お客様の事業を止めることなくシステム価値を向上させる提案力と実行力を発揮いたします。技術的課題と事業課題を同時に解決する総合的なソリューション提供が私たちの強みです。

The post 08 専門業者向けB2B ECサイト first appeared on 株式会社Altus-Five.

]]>
/works/2025/08/29/%e5%b0%82%e9%96%80%e6%a5%ad%e8%80%85%e5%90%91%e3%81%91b2b-ec%e3%82%b5%e3%82%a4%e3%83%88/feed/ 0
05 電力会社様向け卒FITシステム開発 /works/2025/08/29/post-fit-system-development-for-power-companies/ /works/2025/08/29/post-fit-system-development-for-power-companies/#respond Fri, 29 Aug 2025 08:35:40 +0000 /?p=746 再生可能エネルギーの固定価格買取制度(FIT)満了後のスイッチング業務を支援するシステムを開発。シームレスな申請手順と外部連携機能で業務効率化とユーザー満足度向上を実現。リリースタイミングに合わせた柔軟な仕様調整力が強みです。

The post 05 電力会社様向け卒FITシステム開発 first appeared on 株式会社Altus-Five.

]]>
プロジェクト概要

国の再生可能エネルギー固定価格買取制度(FIT)満了後のスイッチングを支援するシステムを開発。シームレスな申請手順と外部連携機能で業務効率化に貢献しました。
リリースタイミングに合わせた仕様調整力で、顧客の要望に柔軟に対応しました。

技術要素

  • Java11
  • Spring Boot 2
  • Thymeleaf
  • JQuery
  • PostgreSQL

プロジェクトの特徴

  • シームレスな申請手順
    ブラウザの読み込みを最小限に抑えた快適な操作性
  • OCCTO連携機能
    外部機関との連携による業務自動化
  • 柔軟な仕様調整
    リリース時期に合わせた迅速な対応

成果

  • スイッチング業務の効率化
  • ユーザー満足度の向上
  • 外部連携による業務自動化
  • 期日厳守のリリース

Altus-Fiveの強み

リリースタイミングに合わせた仕様調整力と最新技術への対応力で、顧客の要望に柔軟かつ迅速に応えることができる点が弊社の強みです。

The post 05 電力会社様向け卒FITシステム開発 first appeared on 株式会社Altus-Five.

]]>
/works/2025/08/29/post-fit-system-development-for-power-companies/feed/ 0
03 医療機器メーカー様向け 精度管理システム /works/2025/08/29/medical-device-quality-management-system/ /works/2025/08/29/medical-device-quality-management-system/#respond Fri, 29 Aug 2025 05:32:46 +0000 /?p=732 プロジェクト概要 医療機器メーカー様向けに、精度管理システムを再構築・機能拡張・保守運用まで一貫してご支援しました。従来は Java + RDB による構成でしたが、大量データ処理の限界に直面していました。そこで弊社は、 […]

The post 03 医療機器メーカー様向け 精度管理システム first appeared on 株式会社Altus-Five.

]]>
プロジェクト概要

医療機器メーカー様向けに、精度管理システムを再構築・機能拡張・保守運用まで一貫してご支援しました。
従来は Java + RDB による構成でしたが、大量データ処理の限界に直面していました。そこで弊社は、HPE Ezmeral Data Fabric – Customer Managed (MapR) を活用した新アーキテクチャを導入。大幅なパフォーマンス改善を実現させました。

特徴・導入効果

  • 大規模データ処理基盤の導入
    • MapRとSparkを活用し、精度管理データの集計を高速化。
  • リアルタイム処理
    • MapR DB + ojai によるリアルタイム計測を実装。
    • フロント画面には Highcharts を採用し、計測結果のグラフ表示を実現。
  • 継続的な進化
    • サーバーサイドからクライアントサイドまで、機能追加・保守運用を継続。
    • 高度なデータ処理からUI/UX改善まで、ワンストップで支援。

技術要素

サーバーサイド

  • HPE Ezmeral Data Fabric (MapR) / MySQL
  • Java / Spring / Scala / Spark / Kafka / Drill / Elastic Search / R Script

クライアントサイド

  • Angular / Angular Material / TypeScript / RxJS / Node.js / Highcharts

成果

本プロジェクトにより、医療機器の精度管理に関わる 大量データを効率的に処理 できるようになりました。
その結果、

  • 計測・集計のスピード向上
  • データの信頼性確保
  • 分析・改善サイクルの短縮化

を実現し、医療現場における品質向上・業務効率化に大きく貢献しました。

💡Altus-Fiveの強み

  • 効率的なフロントエンド開発
    • フロント画面再構築では Angular Material をベースとしたデザインテンプレートを活用し、工数削減と品質担保を両立。
  • バックエンド~フロントエンドまで一気通貫対応
    • データ基盤、リアルタイム処理、フロントの可視化までワンストップで提供。
  • パフォーマンスと信頼性の両立
    • 医療機器分野において求められる「高精度 × 高速性 × 安定稼働」を実現。

本案件のように 大規模データ処理・可視化システム の開発・運用に強みを持っています。
「パフォーマンス改善したい」「大規模データを効率的に扱いたい」といった課題をお持ちの企業様は、ぜひご相談ください。

The post 03 医療機器メーカー様向け 精度管理システム first appeared on 株式会社Altus-Five.

]]>
/works/2025/08/29/medical-device-quality-management-system/feed/ 0
Angularのエラー処理について考える(実装編) /blog/2019/04/10/angular-error-hadling-implement/ /blog/2019/04/10/angular-error-hadling-implement/#respond Wed, 10 Apr 2019 09:31:00 +0000 http://43.207.2.176/?p=298 前回の記事では、Angularのエラー処理について設計時の目線で整理しました。今回は、それを実装するときに、どんなことになるのか、実例を添えて説明します。 エラー分類と画面側の実装 前回の設計編に記述したエラー分類別に、 […]

The post Angularのエラー処理について考える(実装編) first appeared on 株式会社Altus-Five.

]]>
前回の記事では、Angularのエラー処理について設計時の目線で整理しました。
今回は、それを実装するときに、どんなことになるのか、実例を添えて説明します。

エラー分類と画面側の実装

前回の設計編に記述したエラー分類別に、画面側に実装するエラー処理について、一覧化しました。
画面固有の機能として実装すべきものか、あるいは、共通処理として実装すべきかについて、区別します。

#エラー画面側の実装
1-1サーバー接続不能なし
1-2通信タイムアウトなし
2-1未認証なし
2-2権限不一致なし
2-3セキュリティ保護なし
2-4バグなし
3-1排他制御エラーPageコンポーネントでAppErrorをcatchして実装する
3-2ユニーク制約違反エラー同上
3-3データが存在しないエラー同上
3-4バリデーションエラー同上
3-5画面機能固有のエラーPageコンポーネントかサービスクラスで実装する

共通処理としての実装例

例外クラス

まずは、例外クラスを作って、 error を catch したときに、区別できるようにします。

export namespace AppError {
  export function isInstance(error: BaseError, clazz) {
    return error.name && error.name === clazz.name;
  }

  export class BaseError extends Error {
    constructor(message?: string, error?: Error) {
      super(message);
      this.name = 'AppError.BaseError';
      this.message = message;
      if (error) {
        this.stack += `\nCaused by: ${error.message}`;
        if (error.stack) {
          this.stack += `\n${error.stack}`;
        }
      }
    }
  }

  export class ApiError extends BaseError {
    private response: HttpResponseBase;

    constructor(message?: string, response?: HttpResponseBase, error?: Error) {
      super(message, error);
      this.name = 'ApiError';
      this.response = response;
    }
  }

  export class BadRequest extends ApiError {
    constructor(message?: string, response?: HttpResponseBase, error?: Error) {
      super(message, response);
      this.name = 'BadRequest';
    }
  }

  export class Unauthorized extends ApiError {
    constructor(message?: string, response?: HttpResponseBase, error?: Error) {
      super(message, response);
      this.name = 'Unauthorized';
    }
  }

  ・・・

  export class ApiErrorFactory {
    public static getError(res: HttpResponseBase): ApiError {
      let error: ApiError = null;
      switch (res.status) {
        case 400:
          error = new AppError.BadRequest(null, res);
          break;
        case 401:
          error = new AppError.Unauthorized(null, res);
          break;
        case 403:
          error = new AppError.Forbidden(null, res);
          break;

        ・・・
      }
      return error;
    }
  }

※ AppError.isInstanceは Typescript の instanceof が意図した結果を返してくれないので、各エラークラス内に、クラス名を保持するようにして、それと一致するかをチェックするためのユーティリティです。

インターセプター

http通信のエラーを処理するためのインターセプターです。

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
  constructor(private router: Router, private alertService: AppAlertService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    if (req.reportProgress) {
      throw new AppError.BaseError('not implements');
    }
    return next.handle(req).pipe(
      map((event: HttpEvent<any>) => {
        if (event instanceof HttpResponse) {
          const err = this.handleAppError(event);
          if (err) {
            throw err;
          }
        }
        return event;
      }),
      catchError((errRes: HttpErrorResponse) => {
        if (errRes.error instanceof ErrorEvent) {
          const message = `An error occurred: ${errRes.error.message}`;
          this.errorLog(message);
          this.alertService.error(message);
        } else {
          const err = this.handleAppError(errRes);
          if (err) {
            throw err;
          }
        }
        return throwError(errRes);
      })
    );
  }

  private handleAppError(event: HttpResponseBase) {
    const err = AppError.ApiErrorFactory.getError(event);
    if (err === null) {
      return err;
    }
    if (AppError.isInstance(err, AppError.Unauthorized)) {
      this.errorLog(err);
      this.router.navigate(['/login']);
      return null;
    }
    if (AppError.isInstance(err, AppError.Forbidden)) {
      this.errorLog(err);
      this.router.navigate(['/error/403']);
      return null;
    }
    if (AppError.isInstance(err, AppError.ServerError)) {
      this.errorLog(err);
      this.router.navigate(['/error/500']);
      return null;
    }
    if (AppError.isInstance(err, AppError.Maintenance)) {
      this.errorLog(err);
      this.router.navigate(['/error/503']);
      return null;
    }
    return err;
  }

  private errorLog(message: string | Error) {
    if (message instanceof Error) {
      const err = message;
      message = `${err.message}: ${err.stack}`;
    }
    console.error(message);
  }
}

アラート表示用のサービス

タイムアウトなどのErrorEventが発生したときには、特定のコンポーネントに依存しないSnackBarで、アラートメッセージを表示する例です。

@Injectable()
export class AppAlertService {
  constructor(private snackBar: MatSnackBar, private zone: NgZone) {}

  error(message: string) {
    this.zone.run(() => {
      const snackBar = this.snackBar.open(message, 'OK', {
        verticalPosition: 'bottom',
        horizontalPosition: 'center',
      });
      snackBar.onAction().subscribe(() => {
        snackBar.dismiss();
      });
    });
  }
}

バグなどのグローバルエラーハンドラー

バグ発生時などの不測のエラーでは、2次被害が出ないように、システムエラーの画面にリダイレクトさせる例です。

@Injectable({
  providedIn: 'root',
})
export class AppErrorHandler implements ErrorHandler {
  constructor(private injector: Injector) {}

  handleError(error) {
    this.errorLog(error);
    const router = this.injector.get(Router);
    const zone = this.injector.get(NgZone);
    zone.run(() => {
      router.navigateByUrl('/error/500');
    });
  }

  private errorLog(message: string | Error) {
    if (message instanceof Error) {
      const err = message;
      message = `${err.message}: ${err.stack}`;
    }
    console.error(message);
  }
}

画面側の実装例

排他制御エラー

ページコンポーネントの〇〇更新サービスを実行する箇所で、例外をcatchする例

return this.hogeService.update(data).pipe(
  map((result) => {・・・}),
  catchError((err) => {
    if (AppError.isInstance(err, AppError.BadRequest)) {
      this.errorMessage = '他のユーザーによって更新されています。最初からやり直してください';
      return EMPTY;
    } else if (AppError.isInstance(err, AppError.NotFound)) {
      this.errorMessage = '他のユーザーによって削除されています。最初からやり直してください';
      return EMPTY;
    }
    return throwError(err);
  })
);

ユニーク制約違反エラー

ページコンポーネントの〇〇保存サービスを実行する箇所で、例外をcatchする例

return this.hogeService.update(data).pipe(
  map((result) => {・・・}),
  catchError((err) => {
    if (AppError.isInstance(err, AppError.Conflict)) {
      this.errorMessage = 'キーが重複しました。最初からやり直してください';
      return EMPTY;
    }
    return throwError(err);
  })
);

データが存在しないエラー

データ存在しないときに、どうするのかは、画面固有の仕様として策定する必要があります。
よくあるパターンを上げてみます。

  • システム例外として、404の画面に遷移する例
  return this.hogeService.get(data).pipe(
    map((result) => {・・・}),
    catchError((err) => {
      if (AppError.isInstance(err, AppError.NotFound)) {
        this.router.navigate(['/error/404']);
        return EMPTY;
      }
      return throwError(err);
    })
  );
  • データがないときには、別のページに遷移する例(例えば遷移前の画面が一覧画面だったら、その画面にリダイレクトする)
  return this.hogeService.get(data).pipe(
    map((result) => {・・・}),
    catchError((err) => {
      if (AppError.isInstance(err, AppError.NotFound)) {
        this.router.navigate(['/hoges/list']);
        return EMPTY;
      }
      return throwError(err);
    })
  );
  • データがないときは、エラーメッセージを表示する
  return this.hogeService.get(data).pipe(
    map((result) => {・・・}),
    catchError((err) => {
      if (AppError.isInstance(err, AppError.NotFound)) {
        this.errorMessage = 'データが存在しません';
        return EMPTY;
      }
      return throwError(err);
    })
  );

バリデーションエラー

サーバー側でバリデーションエラーが発生することもあるでしょう。 コンポーネント内のエラーメッセージにセットする例です。

return this.hogeService.register(data).pipe(
  map((result) => {・・・}),
  catchError((err) => {
    if (AppError.isInstance(err, AppError.BadRequest)) {
      this.errorMessage = 'XXXXXXXXXXXX';
      return EMPTY;
    }
    return throwError(err);
  })
);

The post Angularのエラー処理について考える(実装編) first appeared on 株式会社Altus-Five.

]]>
/blog/2019/04/10/angular-error-hadling-implement/feed/ 0
Angularのエラー処理について考える(設計編) /blog/2019/03/30/angular-error-hadling-design/ /blog/2019/03/30/angular-error-hadling-design/#respond Sat, 30 Mar 2019 14:51:00 +0000 http://43.207.2.176/?p=300 システムの開発をしていると、約30%は、エラー処理の対策になると聞いたことがあります。30%という数字は、根拠がないので置いとくとして、システムを構築する上で、エラーに対処することは、 非常に重要な設計要素であることは、 […]

The post Angularのエラー処理について考える(設計編) first appeared on 株式会社Altus-Five.

]]>
システムの開発をしていると、約30%は、エラー処理の対策になると聞いたことがあります。
30%という数字は、根拠がないので置いとくとして、システムを構築する上で、エラーに対処することは、 非常に重要な設計要素であることは、間違いありません。

本記事は、私がAngularの開発を通じてエラー処理について、考えたことを整理してみました。 尚、モバイルアプリなども含めると、いろいろ複雑になるので、デスクトップ利用に限定された、業務システムということで、検討範囲を絞り込んでいます。

エラー処理設計の目的

エラー処理を設計するにあたり、検討しておきたいことを挙げてみました。

  • 発生し得るすべてのエラーを明らかにする
  • エラー発生時の振る舞いが統一されている
  • 各画面への重複した設計記述および実装を減らす
  • 漏れなく穴をふさぐ

このあたりは、Angular(SPA)だからというよりも、システムとして共通の目的ですね。

エラー分類

プロジェクトの開始時点では、すべてのエラーを洗い出すことが難しいかもしれないので、典型的なエラーとして分類するところから始めます。
そして、画面の設計、実装過程で、見つかったエラーを、この分類に当てはめて、具体的な仕様として定義していくのが、良いかと思います。
この分類にハマらないものが出てきたら、その都度検討して、新しい分類を追加していくことにします。

(エラー分類1) 致命的エラー

システムが動作する前提となるハードウェアあるいはミドルウェアが異常な状態を示すエラー。

  • 1-1 localStorageのエラー ログイン状態の保持などに、localStorageを使用する場合があり、CookieをOffにしている場合や、一部ブラウザのプライベートモードの場合にエラーになることがある。
  • 1-2 メンテナンス中 APIサーバーがメンテナンス中の状態。

APIサーバーに接続できないときのエラーとかは、ここに分類されてもよいかもしれませんが、瞬間的なネットワーク断は、特にモバイルアプリなどでは、よくあることで、致命的ではないため、ここには分類しませんでした。

🌞 エラー時の振る舞い例
2つくらい考えられると思います。

  • ログイン画面を表示してエラーメッセージとして、エラー原因を表示する
  • システムエラーの500画面、あるいはメンテナンス中の503画面を表示して、同じくエラー原因を表示する

どちらにしても、エラー原因をメッセージとして表示することで、利用者が次のアクションが取れるようにしておくと良いでしょう。

(エラー分類2) 業務継続不可能なエラー

利用する機能の前提要件を満たしていなくて、業務が行えないエラー。

  • 2-1 未認証 ログインしないで、業務画面(URL)にアクセスがあった場合。
  • 2-2 権限不一致 アクセス権限のない、業務画面(URL)にアクセスがあった場合
  • 2-3 セキュリティ保護 XSRF、不正操作、認証時間のタイムアウトなど、セキュリティ保護策によるエラー
  • 2-4 バグ バグによって想定外の状態になった場合

🌞 エラー時の振る舞い例
未認証の場合は、ログイン画面にリダイレクトされるのが、よくある仕様だと思います。
権限不一致とセキュリティ保護は、403画面を表示して不正操作のヒントを与えないのが良いと思います。 バグも、2次被害の恐れがあるので、500画面を表示して続く操作が出来ないようにするのが良いでしょう。

(エラー分類3) 業務継続可能なエラー

発生したエラーは、エラーを除去(解除)することで、業務が継続できるエラー。

  • 3-1 排他制御エラー 複数端末で同一データに同時更新があった場合のエラー。
  • 3-2 ユニーク制約エラー 同一データ(同一キー)が同時に登録された場合のエラー。
  • 3-3 データが存在しないエラー 存在するハズのデータが無かった場合のエラー。 例えば、一覧で選択して詳細ページに遷移するようなケースで、選択したデータが、他の端末で一瞬早く削除された場合など。
  • 3-4 バリデーションエラー 入力値のエラー。 入力値を修正することで、業務継続が可能。
  • 3-5 通信タイムアウト タイムアウトが発生して、期待するデータの取得あるいは、更新ができなかった状態。
  • 3-6 サーバー接続不能 APIサーバーに接続できない状態。
    クライアント側でネットワーク障害があった場合などが想定されます。
    特に、モバイルアプリの場合は、ネットワーク断の状態は、日常的に発生するエラーなので、リトライ処理で自動回復させる仕組みを用意することもあると思います。
    担当したプロジェクトは、デスクトップアプリに限定されていたので、APIサーバーに接続できないときのリトライは行わずに、ユーザーの再操作を期待するUIにしました。
  • 3-7 画面機能固有のエラー 画面機能によるので、個別画面の仕様として規定する。

🌞 エラー時の振る舞い例
排他制御エラーとユニーク制約エラーは、操作画面上にエラーメッセージを表示して、やり直しが出来るようにします。 バリデーションも同じです。再入力するためのガイダンスを表示します。 通信タイムアウトについては、システムの特性によります。私が担当したプロジェクトでは、通信がタイムアウトしたことを、Snackbarで表示させて、再操作を即すようなUIにしました。 画面機能固有のエラーは、ここでは割愛します。

エラー発生元と処理方式

エラーが発生する場所としては、次が考えられます。

  • Web APIの実行時
  • localStorage アクセス時
  • バグ
  • 機能固有の仕様

それぞれの発生元別に、エラーを整理します。

API実行時のエラー処理

APIの実行では、2種類のエラーが発生する可能性があります。
@see https://angular.jp/guide/http#%E3%82%A8%E3%83%A9%E3%83%BC%E3%83%8F%E3%83%B3%E3%83%89%E3%83%AA%E3%83%B3%E3%82%B0

  • エラーステータス APIがHTTPのステータスコードでエラーを返す
  • ErrorEvent ネットワークエラーやrxjs過程のバグでErrorEventが発生する

実装としては、http通信に関連したエラー処理はインターセプターで行い、画面個別での対処が必要なエラー(例えばunique制約の発生など)に対しては、例外をthrowすることで画面側に処理を連携する方式としました。

@see https://angular.jp/guide/http#%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%81%A8%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%82%BB%E3%83%97%E3%83%88

インターセプター内でのhttpステータスコード毎の処理

  • 400 Bad Request
    パラメータのエラーや、validationエラーなどが想定される。
    インターセプターからは、独自の例外クラス AppError.BadRequest をthrowする。
    Page Componentの中では、例外をcatchしてエラー内容を画面に反映する。
  • 401 Unauthorized
    インターセプター内で、@angular/router/Router#navigate にloginのURLをセットして、ログイン画面に遷移する。
  • 403 Forbidden
    アクセス不可のエラーなので、403画面に遷移する。
  • 404 Not Found
    詳細画面や編集画面で対象データが存在しない場合などが想定される。
    インターセプターからは、独自の例外クラス AppError.NotFoundをthrowする。
    Page Componentの中では、例外をcatchしてエラー内容を画面に反映する。
    一覧画面で一覧の内容がない場合は、画面内に反映する。
    詳細画面や編集画面でURL内にIDが含まれる場合は、@angular/router/Router#navigate に404画面のURLをセットして、404画面に遷移する。
    一覧画面の親データ(例えばアカウント一覧の組織自体が無い)が無い場合も同様に404画面に遷移する
  • 409 Conflict
    データ登録で一意性制約違反や排他エラーが発生した場合などが想定される。
    インターセプターからは、 AppError.Conflict を throw する。
    Page Componentの中でcatchしてエラーに対応した処理を実行する。
  • 500 Internal Server Error
    このエラーは、バグなので、システムエラーの500画面に遷移する。
  • 503 Maintenance
    このエラーも、メンテナンス中の503画面に遷移する。

※AppError は独自の例外クラスを作成してあるものとします

ErrorEvent毎の処理

同じく、インターセプターでのErrorEvent発生時の処理です。

  • タイムアウト
    アラートをSnackbarで表示する。
  • 接続不能
    同じく、Snackbarで表示する。

localStorage アクセス時のエラー処理

エラー分類1の致命的なエラーの処理を行う。

バグのエラー処理

バグは、globalエラーハンドラーで処理することとして、画面側では、例外をcatchしない。

画面固有のエラー処理

エラーの共通仕様としては、定義しないで、画面あるいは、サービスクラスで実装する。


実装編の記事も読んでみて下さい。

The post Angularのエラー処理について考える(設計編) first appeared on 株式会社Altus-Five.

]]>
/blog/2019/03/30/angular-error-hadling-design/feed/ 0
AngularJS vs React それぞれの利点と特徴(React編) /blog/2017/02/24/angularjs-vs-react-2/ /blog/2017/02/24/angularjs-vs-react-2/#respond Thu, 23 Feb 2017 17:48:00 +0000 http://43.207.2.176/?p=425 「AngularJS vs React」前回のAngular編では、Angular 2でToDoツールを作成していきました。今回は、Reactを用いて同様の機能を持ったToDoツールを作成することで、両者の特徴を明確にし […]

The post AngularJS vs React それぞれの利点と特徴(React編) first appeared on 株式会社Altus-Five.

]]>
「AngularJS vs React」前回のAngular編では、Angular 2でToDoツールを作成していきました。今回は、Reactを用いて同様の機能を持ったToDoツールを作成することで、両者の特徴を明確にしたいと思います。

Reactの特徴

リアクティブプログラミング(Reactive programming)を志向

リアクティブプログラミングを志向していることが、Reactという名前の所以です。 リアクティブプログラミングについては、過去記事で解説していますので、ご参照ください。

リアクティブプログラミングの概観と、各言語での実装について

JSXによる、仮想DOM(Virtual DOM)を用いた実装

最近のフロントフレームワークの多くでは、仮想DOMという技術が取り入れられ、主流になりつつあります。その仮想DOMを定着させたのがReactです。

Reactでは「コンポーネント」を生成し、UIを構築します。コンポーネントはインスタンス化して使用します。このコンポーネントのインスタンスのことをエレメントと呼びます。

var Hello = React.createClass({
  render() {
    return (
      <div><span>hello</span></div>
    )
  }
})

上記のコード中に、HTMLタグとしておなじみの<div>が入っていると思います。これは、実はReactで定義されているコンポーネントです。このHTMLタグ(のようなもの)をJSXと呼びます。 一般に、 <div>などをDOM(Document Object Model)と総称しますよね。このことから、JSXは仮想DOMと呼ばれる概念に属します。

Fluxによる実装

Fluxというアーキテクチャを理解しないとReactの良さを最大限に引き出すことができません。簡単に概要を記載します。

  • クライアントサイドの設計パターン
  • React同様、Facebookにより開発された
  • データの流れが常に一方通行(MVCとは対極にある)
  • オブザーバーパターンが根幹にある
  • Action, View, Dispatcher, Storeという4部品に別れている

Fluxはオブザーバーパターン(Observer pattern)を実現

オブザーバーパターンはデザインパターンの一つです。Observerとは観察者という意味の単語で、ここではオブジェクト自身が観察者に通知する仕組みのことを端的に表しています。 オブザーバーパターンについては、下記記事でより詳しく説明を行っています。

・リアクティブプログラミングの概観と、各言語での実装について

Fluxではデータの流れが常に一方通行

MVCモデルですと、データはControllerからModel、ModelからView、ModelからControllerというように、自由に行き交います。 そのためプロジェクトが大きくなっていくにつれて、ソースを追うのが大変になっていきます。

しかし、Fluxを使用した場合のデータは常に一方通行になるため、大型のプロジェクトでも管理しやすく、チーム開発も円滑になります。

Fluxの部品

Fluxの各部品の概要を記載します。Reactはこの中のViewを担当します。

  • Action
    • UIをクリックしたり、HTTPリクエストが届いたときなどの非同期イベントが発生すると呼ばれる
    • 各イベントの動作を決定
      • Storeにそれをメッセージとして伝える
  • Dispatcher
    • ActionからStoreへいくための橋渡し
    • facebook/fluxはこの機能のみを提供している
  • Store
    • データをためる場所
    • MVCでいうならModel的な役割
  • View
    • 今回はReact
    • 他のフレームワークでも代用可能

また今回はFluxとReactの概要を知るために、まずはfacebook/fluxを使用してみます。facebook/fluxは、名前の通りFacebook, Inc.が開発しており、今回は以下のメリットから導入しています。

軽量な設計のため、必然的に自分でコードを書く部分が多くなる
よって、Fluxを学ぶのに適している
導入が簡単

Fluxをサポートするフレームワークは他にもいくつかあり、有名どころではReduxなどがあります。

ReactでToDoツールを実装する

ReactはフルスタックフレームワークであるAngular 2とはまったく異なり、MVC(Model-View-Controller)のViewのみを担当します。

今回作成するToDoツールの最終的なディレクトリ構造は以下のようになります。

├── index.html
├── package.json
├── src
│   ├── containers
│   │   └── AppContainer.js
│   ├── data
│   │   ├── Counter.js
│   │   ├── Todo.js
│   │   ├── TodoActionTypes.js
│   │   ├── TodoActions.js
│   │   ├── TodoDispatcher.js
│   │   ├── TodoDraftStore.js
│   │   └── TodoStore.js
│   ├── root.js
│   └── views
│       └── AppView.js
└── webpack.config.js

src以下にアプリケーションのソースコードを配置していきます。

インストール

以下のpackage.jsonを用意します。今回はwebpackとBabelを使用して作業を進めていきます。

package.json

{
  "name": "flux todo",
  "version": "1.0.0",
  "description": "",
  "repository": "",
  "author": "xxxxx",
  "main": "bundle.js",
  "scripts": {
    "build": "webpack ./src/root.js ./bundle.js",
    "watch": "webpack ./src/root.js ./bundle.js --watch",
  },
  "dependencies": {
    "classnames": "^2.2.3",
    "flux": "3.1.2",
    "immutable": "^3.8.0",
    "react": "^15.0.2",
    "react-dom": "^15.0.1"
  },
  "devDependencies": {
    "babel-core": "^6.7.6",
    "babel-loader": "^6.2.4",
    "babel-plugin-syntax-async-functions": "^6.5.0",
    "babel-plugin-syntax-flow": "^6.5.0",
    "babel-plugin-syntax-jsx": "^6.5.0",
    "babel-plugin-syntax-object-rest-spread": "^6.5.0",
    "babel-plugin-syntax-trailing-function-commas": "^6.5.0",
    "babel-plugin-transform-flow-strip-types": "^6.5.0",
    "babel-plugin-transform-object-rest-spread": "^6.6.5",
    "babel-plugin-transform-react-jsx": "^6.7.5",
    "babel-plugin-transform-regenerator": "^6.5.2",
    "babel-plugin-transform-runtime": "^6.5.2",
    "babel-preset-es2015": "^6.5.0",
    "webpack": "^1.13.0"
  }
}

以下コマンドでインストールします。

$ npm install

ビルドするときは以下コマンドで実行します。

$ npm build

インデックスファイルの作成(index.html)

初めはindex.htmlを作成します。webpackを使用してビルドしており、bundle.jsが最終的に作成されます。index.html内ではそれを読み込んでいます。

index.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>ToDo</title>
  </head>
  <body>
    <section id="todoapp"></section>
    <script src="./bundle.js"></script>
  </body>
</html>

Reactが関連している記述はsectionというタグです。

<section id="todoapp"></section>

最終的にid=todoapp内にReactで設定した内容を表示します。ReactではJSXを使用するため、HTMLライクなコードをシンプルに書くことができます。JSXについては冒頭でも説明しましたが、後ほどまた登場します。

一番最初に読み込まれるjsファイルの作成(src/root.js)

package.jsonに、buildコマンドとしてwebpack ./src/root.js ./bundle.jsと指定しています。そのためsrc/root.jsが最初に読み込まれるようになっています。

src/root.js

import AppContainer from './containers/AppContainer';
import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(<AppContainer />, document.getElementById('todoapp'));

ブラウザに表示するためには、ReactDOMを使用します。HTMLのIDを取得して、見つかったタグ内に表示させます。

また、ReactDOM.renderの第一引数に<AppContainer />という見慣れないタグを指定していますね。 これは自作のコンポーネントである src/containers/AppContainer.jsの中身をrenderメソッドで表示するためのものです。

Containerの作成(/src/containers/AppContainer.js)

Store、Action、ViewなどをまとめるContainerを作成します。

/src/containers/AppContainer.js

import AppView from '../views/AppView';
import {Container} from 'flux/utils';
import TodoStore from '../data/TodoStore';
import TodoDraftStore from '../data/TodoDraftStore';
import TodoActions from '../data/TodoActions';

function getStores() {
  return [
    TodoStore,
    TodoDraftStore,
  ];
}

function getState() {
  return {
    todos: TodoStore.getState(),
    draft: TodoDraftStore.getState(),

    onAdd: TodoActions.addTodo,
    onDeleteTodo: TodoActions.deleteTodo,
    onToggleTodo: TodoActions.toggleTodo,
    onUpdateDraft: TodoActions.updateDraft,
  };
}

export default Container.createFunctional(AppView, getStores, getState);

facebook/fluxにはFlux Utilsというライブラリがあります。今回はContainerというReact Componentのラッパーを使用しています。

import {Container} from 'flux/utils';
,
,
,
export default Container.createFunctional(AppView, getStores, getState);

ContainerはStore(MVCにおけるModel的な役割を果たす部品)からデータを受け取ります。そのデータが更新されていればContainer配下のComponentに通知し、Componentは更新後のデータを反映して再描画します。 この設計により、データ変更の監視機能を、自分で実装する必要がなくなります。

function getStores() {
  return [
    TodoStore,
    TodoDraftStore,
  ];
}

function getState() {
  return {
    todos: TodoStore.getState(),
    draft: TodoDraftStore.getState(),

    onAdd: TodoActions.addTodo,
    onDeleteTodo: TodoActions.deleteTodo,
    onToggleTodo: TodoActions.toggleTodo,
    onUpdateDraft: TodoActions.updateDraft,
  };
}

export default Container.createFunctional(AppView, getStores, getState);

getStores()関数にContainerと連携するStoreを定義します。そしてgetState()に変更を検知したときに実行されるActionを定義していきます。

Actionの作成(src/data/TodoActions.js)

  • Action
    • UIをクリックしたり、HTTPリクエストが届いたときなどの非同期イベントが発生すると呼ばれる
    • 各イベントの動作を決定
      • Storeにそれをメッセージとして伝える
import TodoActionTypes from './TodoActionTypes';
import TodoDispatcher from './TodoDispatcher';

const Actions = {
  addTodo(text) {
    TodoDispatcher.dispatch({
      type: TodoActionTypes.ADD_TODO,
      text,
    });
  },

  deleteTodo(id) {
    TodoDispatcher.dispatch({
      type: TodoActionTypes.DELETE_TODO,
      id,
    });
  },

  toggleTodo(id) {
    TodoDispatcher.dispatch({
      type: TodoActionTypes.TOGGLE_TODO,
      id,
    });
  },

  updateDraft(text) {
    TodoDispatcher.dispatch({
      type: TodoActionTypes.UPDATE_DRAFT,
      text,
    });
  },
};

export default Actions;

ActionではStoreにデータを送るための処理を記述していきます。ActionはあくまでStoreにデータを送るだけなので、ロジックはStoreに委譲します。 またDispacher部分が今回facebook/fluxに処理を任せている部分になります。

addTodo(text) {
 TodoDispatcher.dispatch({
   type: TodoActionTypes.ADD_TODO,
   text,
 });
},

ActionではStoreに渡すべきtypeと、引数であるstateを渡します。各タイプについては、src/data/TodoActionTypes.jsにenumのように定義しておくと、どのようなActionかが一目でわかり、管理しやすくなります。

src/data/TodoActionTypes.js

const ActionTypes = {
  ADD_TODO: 'ADD_TODO',
  DELETE_TODO: 'DELETE_TODO',
  TOGGLE_TODO: 'TOGGLE_TODO',
  UPDATE_DRAFT: 'UPDATE_DRAFT',
};

export default ActionTypes;

Storeの作成(src/data/TodoStore.js)

  • Store
    • データをためる場所
    • MVCでいうならModel的な役割

src/data/TodoStore.js

import Counter from './Counter';
import Immutable from 'immutable';
import {ReduceStore} from 'flux/utils';
import Todo from './Todo';
import TodoActionTypes from './TodoActionTypes';
import TodoDispatcher from './TodoDispatcher';

class TodoStore extends ReduceStore {
  constructor() {
    super(TodoDispatcher);
  }

  getInitialState() {
    return Immutable.OrderedMap();
  }

  reduce(state, action) {
    switch (action.type) {
      case TodoActionTypes.ADD_TODO:
        // Don't add todos with no text.
        if (!action.text) {
          return state;
        }
        const id = Counter.increment();
        return state.set(id, new Todo({
          id,
          text: action.text,
          complete: false,
        }));

      case TodoActionTypes.DELETE_TODO:
        return state.delete(action.id);

      case TodoActionTypes.TOGGLE_TODO:
        return state.update(
          action.id,
          todo => todo.set('complete', !todo.complete),
        );

      default:
        return state;
    }
  }
}


export default new TodoStore();

タスクのリストを管理するTodoStore.jsです。タスク一つひとつに関しては、TodoDraftStore.jsで管理します。

  reduce(state, action) {
    switch (action.type) {
      case TodoActionTypes.ADD_TODO:
        ,,,
      case TodoActionTypes.DELETE_TODO:
        ,,,
      case TodoActionTypes.TOGGLE_TODO:
        ,,,
      default:
        return state;
    }
  }

先ほどのActionで指定していたtypeなどはStore内のreduceメソッドで受取ます。switch文で分岐して各実装を定義していきます。

ReduceStoreクラスの継承

Storeクラスを作る際にReduceStoreというクラスを継承しています。

class TodoStore extends ReduceStore {

ReduceStoreは、facebook/flux 2.1.0から加わった、Flux Utilsに含まれるライブラリの一つです。 ReduceStoreは、自身の状態の変更をContainerに自動で反映することができます。 (2.1.0以前はevent emitterを使用して、プログラマが自分で通知のソースコードを書いていました)

src/data/TodoActions.js

export default Container.createFunctional(AppView, getStores, getState);

先ほどTodoActions.jsで関連付けしているので、これで変更の通知機能をFluxに任せることができます。

src/data/TodoDispatcher.js

  • Dispatcher
    • ActionからStoreへいくための橋渡し
    • facebook/fluxはこの機能のみ提供している

src/data/TodoDispatcher.js

import {Dispatcher} from 'flux';

export default new Dispatcher();

facebook/fluxを読み込んでいるだけです。このクラスにDispacher部分の処理を丸投げします。

ユーザが入力中かどうかの判定(src/data/TodoDraftStore.js)

入力中のToDoを管理するTodoDraftStore.jsについても見ていきます。

import {ReduceStore} from 'flux/utils';
import TodoActionTypes from './TodoActionTypes';
import TodoDispatcher from './TodoDispatcher';

class TodoDraftStore extends ReduceStore {
  constructor() {
    super(TodoDispatcher);
  }

  getInitialState() {
    return '';
  }

  reduce(state, action) {
    switch (action.type) {
      case TodoActionTypes.ADD_TODO:
        return '';

      case TodoActionTypes.UPDATE_DRAFT:
        return action.text;

      default:
        return state;
    }
  }
}

export default new TodoDraftStore();

src/data/TodoStore.jsとほぼ同じ作りになっています。こちらもReduceStoreを継承して実装します。 Actionを作成し、Dispatcherが用意できれば同じようなStoreは簡単に作成することができます。

ToDoオブジェクトの作成(src/data/Todo.js)

ToDoリストそのものを表すオブジェクトを作成します。属性を定義したクラスを用意するだけです。

src/data/Todo.js

import Immutable from 'immutable';

const Todo = Immutable.Record({
  id: '',
  complete: false,
  text: '',
});

export default Todo;

今回は、Reactと直接の関連はありませんが、immutable.jsを使用しています。

immutable.jsはFacebook, Inc.が開発したJavaScriptのライブラリで、immutableなコレクションを提供してくれます(listやmapを簡単に扱えることが利点)。 Immutable.Recordを使用すると、JavaScriptでimmutableなクラスが作成できます。

Viewの作成(src/views/AppView.js)

  • View
    • 主にreactがviewの役目になる
    • 他のフレームワークでも代用可能
import React from 'react';

function AppView(props) {
  return (
    <div>
      <Header {...props} />
      <Main {...props} />
      <Footer {...props} />
    </div>
  );
}

const ENTER_KEY_CODE = 13;
function Header(props) {

  const addTodo = () => props.onAdd(props.draft);
  const onKeyDown = (event) => {
    if (event.keyCode === ENTER_KEY_CODE) {
      props.onAdd(props.draft);
    }
  }
  const onChange = (event) => props.onUpdateDraft(event.target.value);
  return (
    <header id="header">
      <h1>todos</h1>
      <input
        id="new-todo"
        placeholder="What needs to be done?"
        value={props.draft}
        onKeyDown={onKeyDown}
        onChange={onChange}
      />
    </header>
  );
}

function Main(props) {
  if (props.todos.size === 0) {
    return null;
  }
  return (
    <section id="main">
      <ul id="todo-list">
        {[...props.todos.values()].reverse().map(todo => (
          <li key={todo.id}>
            <div className="view">
              <input
                className="toggle"
                type="checkbox"
                checked={todo.complete}
                onChange={() => props.onToggleTodo(todo.id)}
              />
              <label>{todo.text}</label>
              <button
                className="destroy"
                onClick={() => props.onDeleteTodo(todo.id)}
              >削除</button>
            </div>
          </li>
        ))}
      </ul>
    </section>
  );
}

function Footer(props) {
  if (props.todos.size === 0) {
    return null;
  }

  const remaining = props.todos.filter(todo => !todo.complete).size;
  const phrase = remaining === 1 ? ' item left' : ' items left';

  return (
    <footer id="footer">
      <span id="todo-count">
        <strong>
          {remaining}
        </strong>
        {phrase}
      </span>
    </footer>
  );
}

export default AppView;

最後にReactのViewの部分について解説します。少し長くなりますが、段階に分けて説明します。

AppView関数

function AppView(props) {
  return (
    <div>
      <Header {...props} />
      <Main {...props} />
      <Footer {...props} />
    </div>
  );
}

コンポーネントを読み込んでいる部分です。各コンポーネントにはpropsという引数を与えています。

propsとstate

Reactにはデータを管理する変数が2つあり、非常に混乱しやすいです。ほとんど同じように利用できるのですが、これらには明確な使い分けがあります。

  • props
    • 親コンポーネントから渡されたプロパティ
    • propsはimmutableであるべき
  • state
    • そのコンポーネントが持っているプロパティ
      • コンポーネントからコンポーネントへは渡されない
    • stateは可変であるべき

src/containers/AppContainer.js

export default Container.createFunctional(AppView, getStores, getState);

こちらで定義されたAppViewは、AppContainer内でStore(getStores)やAction(getState)などと一緒に登録されています。こうすることによって、Store内のプロパティがpropsで受け取れるようになっています。

なので、今回のAppContainer.jsで定義されたpropsの場合は以下のように値やメソッドが使用できます。

props.todos

props.onAdd(props.draft);
…props (Spread Attributes)

このドットが続く記法を使用すると、変数の中身を展開して引数としてそのまま渡すことができます。これはSpread AttributesというJSXの記法です。

const props = { foo: "foo", bar: "bar" };

// 通常通りの記法
return <Child foo={props.foo} bar={props.bar} />
// Spread Attributesを使用
return <Child {...props} />

タスクを登録するテキストフォーム(Header)の作成

タスクを登録するフォームのコンポーネントを作成します。

const ENTER_KEY_CODE = 13;
function Header(props) {

  const addTodo = () => props.onAdd(props.draft);
  const onKeyDown = (event) => {
    if (event.keyCode === ENTER_KEY_CODE) {
      props.onAdd(props.draft);
    }
  }
  const onChange = (event) => props.onUpdateDraft(event.target.value);
  return (
    <header id="header">
      <h1>todos</h1>
      <input
        id="new-todo"
        placeholder="What needs to be done?"
        value={props.draft}
        onKeyDown={onKeyDown}
        onChange={onChange}
      />
    </header>
  );
}

AppViewsからStoreのpropsが渡されています。propsにはコンテナで登録したStoreのプロパティやメソッドを使用したり、値を取得できたります。

return (
    <header id="header">
      <h1>todos</h1>
      <input
        id="new-todo"
        placeholder="What needs to be done?"
        value={props.draft}
        onKeyDown={onKeyDown}
        onChange={onChange}
      />
    </header>
);

冒頭でも説明しましたが、これがJSXです。一見するとHTMLタグのようですが、すべてJSXの記法です。 またコンポーネントは必ず一つのJSXを返すことがルールになっています。

JSXこそがReactの最大の特徴といっても過言ではありません。javascriptとHTML(擬似)を分離せずに記述できるようになっています。そうすることによって、jQueryなどによるDOM操作の煩わしさを排除することができます。

const onChange = (event) => props.onUpdateDraft(event.target.value);

onChangeなどのイベントはpropsから取得しており、名前をつけてイベントとして渡しているだけになります。 View内にロジックを書く必要がなく、領分がはっきりと分かれているのがわかると思います。

const addTodo = () => props.onAdd(props.draft);

const onKeyDown = (event) => {
if (event.keyCode === ENTER_KEY_CODE) {
    props.onAdd(props.draft);
  }
}

const onChange = (event) => props.onUpdateDraft(event.target.value);

タスクの登録はheaderコンポーネントで行います。ここで入力中のToDoを管理するTodoDraftStore(props.draft)が使用されています。

src/data/TodoStore.js

return state.set(id, new Todo({
  id,
  text: action.text,
  complete: false,
}));

props.onAddのロジックはsrc/data/TodoStore.jsに記載されています。props.draftで入力中であったFormの文章をStateに登録しています。 先ほど記述したように stateの値は可変です。stateの値は即座にViewに反映されるので、画面に追加されたToDoが表示されます。

タスクの一覧を表示するパーツ(Main)の作成

Main関数は、タスクを一覧表示するリスト機能を担います。

function Main(props) {
  if (props.todos.size === 0) {
    return null;
  }
  return (
    <section id="main">
      <ul id="todo-list">
        {[...props.todos.values()].reverse().map(todo => (
          <li key={todo.id}>
            <div className="view">
              <input
                className="toggle"
                type="checkbox"
                checked={todo.complete}
                onChange={() => props.onToggleTodo(todo.id)}
              />
              <label>{todo.text}</label>
              <button
                className="destroy"
                onClick={() => props.onDeleteTodo(todo.id)}
              >削除</button>
            </div>
          </li>
        ))}
      </ul>
    </section>
  );
}

JSXで一つ注意しなければならないのは、タグにclass=が使用できない点です。これはJSXではclassName=と書かなければなりません。なぜならclassはJavaScriptの予約語であるためです。

{[...props.todos.values()].reverse().map(todo =>

props.todosでループで回したあとは、todo.idtodo.textのように自然に値を取り出すことができます。

Footer関数は、未完了タスクの総数を表示するといった機能を担います。

function Footer(props) {
  if (props.todos.size === 0) {
    return null;
  }

  const remaining = props.todos.filter(todo => !todo.complete).size;
  const phrase = remaining === 1 ? ' item left' : ' items left';

  return (
    <footer id="footer">
      <span id="todo-count">
        <strong>
          {remaining}
        </strong>
        {phrase}
      </span>
    </footer>
  );
}

こちらは特に迷うようなところはないでしょう。propsからtodosを取得して、計数して表示させているだけです。

再びAppView

function AppView(props) {
  return (
    <div>
      <Header {...props} />
      <Main {...props} />
      <Footer {...props} />
    </div>
  );
}

そして一番上のAppViewに戻ってみると、すべてのコンポーネントをJSXで記載してreturnしていることがわかると思います。

export default Container.createFunctional(AppView, getStores, getState);

あとはsrc/containers/AppContainer.jsでコンテナに登録し、

src/root.js

import AppContainer from './containers/AppContainer';
import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(<AppContainer />, document.getElementById('todoapp'));

root.jsにReactDOM.renderを定義します。

<section id="todoapp"></section>

そしてHTMLタグに表示させるように、id=todoappを設置すれば完了です。

Reactについてのまとめ

  • JSXが使用できるので、DOM操作で混乱することが少ない
  • 値を変更したときの監視はFluxなどに任せることができるのでコード量が少ない
  • Action→Dispatcher→Store→Viewとデータの流れが決まっているので、あとでソースを読んだときにわかりやすい
  • Fluxのフレームワークの種類も豊富なので、違うものを使用すればもっと楽に開発できる

AngularJSとReact、それぞれの比較

コンポーネント指向フレームワークとしての差異

Angularは、Angular 2以降コンポーネント指向になりました。このことにより、どちらも基本的にはコンポーネントごとにパーツを作るようになりました。

Angular 2

Angular 2は@NgModule内でコンポーネントを定義しています。

app/app.module.ts

declarations: [
    AppComponent,
    TodoHeaderComponent,
    TodoContentComponent,
    TodoFooterComponent,
],

React

ReactはAppViewでコンポーネントを定義しています。

src/views/AppView.js

function AppView(props) {
  return (
    <div>
      <Header {...props} />
      <Main {...props} />
      <Footer {...props} />
    </div>
  );
}

Angularがコンポーネント志向を採用したことで、Angular 1のときよりは差が縮まったと思います。ただ設計が同じでもReactはJSXを使用しているため、やはり記法に大きな違いがあります。

Flux上のReact、フルスタックフレームワークのAngular 2

ReactはFluxと密接な関係にあるため、アーキテクチャに関する学習コストがかかります。一方、あくまでViewのためのフレームワークですので、小規模な導入も可能です。 Reactと併用できるFluxのフレームワークは多数あり、組み合わせによってはまったく違う開発が出来そうです。そのことから、適材を選びやすいという印象を受けました。

一方でAngular 2はフルスタックフレームワークです。途中からライブラリを変更するなどは難しいと思います。Angular2を選んだ場合は、もしも他のAltJsを使用したいと思っても、TypeScriptと密接に関連性があるため変更は容易ではありません。また、ルーティング機能などのライブラリもAngular2に含まれているため、使いづらい部分があってもバージョンアップを待つか、自分で独自に拡張するしかありません。他のライブラリへの移行は難しいと思います。そういったことから、部分的に導入できるReactに対して、Angularは最初から全てを学ばないといけないのが難点だなと感じました。

ただ、一度身につけてしまえばAngularの方が楽なのかなと思いました。ReactはFlux, ReactRouter, JSX/ES2015など周辺ツールが多いので、取捨選択が大変です。

Viewの差異

ReactのView

今回のコードでは、src/views/AppView.js内のHeaderコンポーネントに記載されています。

src/views/AppView.js

const ENTER_KEY_CODE = 13;
function Header(props) {

  const addTodo = () => props.onAdd(props.draft);
  const onKeyDown = (event) => {
    if (event.keyCode === ENTER_KEY_CODE) {
      props.onAdd(props.draft);
    }
  }
  const onChange = (event) => props.onUpdateDraft(event.target.value);
  return (
    <header id="header">
      <h1>todos</h1>
      <input
        id="new-todo"
        placeholder="What needs to be done?"
        value={props.draft}
        onKeyDown={onKeyDown}
        onChange={onChange}
      />
    </header>
  );
}

Angular2のView

Angular 2がHTMLファイルに記載するのに対し、ReactはJSXを使用しているので、この点が大きく違います。

app/components/header/header.component.ts

import {Component} from '@angular/core';
import {TodoService} from '../../services/todo.service';

@Component({
  selector: 'todo-header',
  templateUrl: 'app/components/header/header.html'
})

export class TodoHeaderComponent {

  title:string;

  constructor(private service:TodoService) {}
  addTodo() {
    if (this.title != null && this.title.trim().length) {
      this.service.add(this.title);
      this.title = null;
    }
  }
}

header.html

<header class="well">
  <h1>Todos</h1>
  <form class="form-inline">
    <div class="form-group">
      <label>新しいTodo:</label>
      <input class="form-control" name="title" [(ngModel)]="title">
    </div>
    <button class="btn btn-primary" (click)="addTodo()">追加</button>
  </form>
</header>

JSXのもっとも良い点は、めちゃくちゃになりがちなjQueryなどのDOM操作を一掃できるところだと思います。JSXに置き換えることよって、JSと連動したタグはすべて管理できますし、ReactのViewを見ればタグの仕様はすべてわかります。

ただし、デザイナーやエンジニア(コーダーなど)とJSXの知識を共有しておかなければなりません。自分一人しか知識を持っていない状態では作業しづらいのが難点です。

Angular 2の場合は、従来のようなテンプレート方式を採用しているので、

  1. デザイナーがデザインを作成する
  2. コーダーがHTMLコードを書く
  3. フロントエンジニアがAngularのコードを書く

という流れがやりやすいと思いました。

最後に、個人的な感想として、JSXは「ものすごく好き嫌いが分かれる」と直観しました。明確なメリットがないと、会社によっては導入が難しいかもしれません。

参考

The post AngularJS vs React それぞれの利点と特徴(React編) first appeared on 株式会社Altus-Five.

]]>
/blog/2017/02/24/angularjs-vs-react-2/feed/ 0
AngularJS vs React それぞれの利点と特徴(Angular編) /blog/2017/01/30/angularjs-vs-react/ /blog/2017/01/30/angularjs-vs-react/#respond Mon, 30 Jan 2017 12:53:00 +0000 http://43.207.2.176/?p=264 React or Angular 2 ー どちらを使うべきか 筆者は普段バックエンド担当をしており、フロントエンドの世界に深い知見を持っておりません。しかし、JavaScript界隈の進化のスピードの速さには、日々驚かさ […]

The post AngularJS vs React それぞれの利点と特徴(Angular編) first appeared on 株式会社Altus-Five.

]]>
React or Angular 2 ー どちらを使うべきか

筆者は普段バックエンド担当をしており、フロントエンドの世界に深い知見を持っておりません。しかし、JavaScript界隈の進化のスピードの速さには、日々驚かされます。フレームワークの数も今や膨大な数になっています。

その中でも選択肢に上がりやすいのがFacebook Inc.が開発している「React.js」、そしてGoogleが開発している「AngularJS」ではないかと思います。 これらのフレームワークの良さは、小規模なプロジェクトでよいので、開発を行ってみて初めて違いがわかるものだと思います。

なので、今回は両方のフレームワークで同じツールを作成し、実装過程と成果物を比較して、両者のフレームワークの良いところ・悪いところを検証したいと思います。

目標 – ToDoツールの実装

以下の機能を持つ、簡単なToDoツールを作成します。

  • タスクの登録
  • タクスの削除
  • タスクの完了

まず最初に、それぞれのフレームワークの概要を記載します。

Reactの概要

  • Facebook Inc.が開発
  • MVCでいうView部分のみをサポートしたフレームワーク
  • Viewのみなので、他のJSフレームワークとの連携も可能
  • JSXというJavaScriptの拡張文法を用いることができ、コード内にHTMLに似た記述ができる
  • 仮想DOMと呼ばれるレンダリング機能がある。(詳しくはReactの章で記載)

Angular 2の概要

  • GoogleとMicrosoftが共同で開発(Microsoftの参画はAngular 2から)
    • AngularJS 1とAngular 2は互換性が低い
  • コンポーネントベースである
  • フルスタックフレームワークである
  • Angular 2やAngular 1.xでは、TypeScriptでの記述が可能になった

Angular 2でToDoツールを実装する

ちなみに、Angular4が2017年3月にリリース予定ですが、Angular 2と互換性があるということなので、Angular 2を勉強しておいて損は無いと思います。

Angular 2はフルスタックフレームワーク

Angular 2はフルスタックフレームワークであり、開発に必要なものはすべて含まれています。例えば、Vue.jsではルーティングのためにVue Routerを使用しますが、Angular 2には標準でルーティング機能が備わっています。 また、ユニットテストや、KarmaやJasmineによるE2Eテストも非常に組み込みやすくなっています。

Angular 2はTypeScriptで書ける

Angular 2の何よりの特徴は、TypeScriptで書くことを前提に設計されているところです。Microsoftの参画によりTypeScriptが採用されることになり、同言語の機能が使えるようになりました。 そのため、クラスや継承といった概念を使用しやすく、コードの品質を低いコストで保つことができます。

もちろんBabelなどを使用してES6やES5でも書けますが、Angular公式はあくまでTypeScriptを推奨しています。

コンポーネント指向

Angular 1ではMVCやMVW(Model-View-Whatever)というデザインパターンが採用されてきましたが、Angular 2からはコンポーネント指向で設計されるようになりました。 コンポーネントごとにプログラムを分けることができるので、わかりやすい設計を行うことができます。

例えば、今回作るToDoツールの場合は、上記のように3つのコンポーネントに分ける形で実装していきます。 この特徴のため、Angular 2はAngular 1よりも大きなアプリケーションの開発に向いていると言われています。

Angular 2でToDoを作成する

今回作成するToDoツールの最終的なディレクトリ構造は以下のようになります。(最後にビルドされると、.jsファイル等が生成されます)

.
├── app
│   ├── app.component.ts
│   ├── app.html
│   ├── app.module.ts
│   ├── components
│   │   ├── content
│   │   │   ├── content.component.ts
│   │   │   └── content.html
│   │   ├── footer
│   │   │   ├── footer.component.ts
│   │   │   └── footer.html
│   │   └── header
│   │       ├── header.component.ts
│   │       └── header.html
│   ├── main.ts
│   ├── models
│   │   └── todo.model.ts
│   └── services
│       └── todo.service.ts
├── index.html
├── node_modules
├── package.json
├── systemjs.config.js
└── tsconfig.json

app以下にアプリケーションのコードを配置していきます。

  • components
    • コンポーネントのパーツを配置していく
    • 今回は「header」、「content」、「footer」のように分けた
  • models
    • モデルを配置する
  • service
    • ロジックを書くサービス層

インストール

以下のpackage.jsonを用意します。

nodeとnpmは必須なので下記を参考にインストールしてください。 nodeは4.x.x以上、npmは3.x.x以上が必須要件です。

・npmjs.com 02 – Installing Node.js and updating npm _ npm Documentation

{
  "name": "angular-quickstart",
  "version": "1.0.0",
  "description": "QuickStart package.json from the documentation, supplemented with testing support",
  "scripts": {
    "start": "tsc && concurrently \"tsc -w\" \"lite-server\" ",
    "lite": "lite-server",
    "tsc": "tsc",
    "tsc:w": "tsc -w"
  },
  "keywords": [],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "@angular/common": "~2.4.0",
    "@angular/compiler": "~2.4.0",
    "@angular/core": "~2.4.0",
    "@angular/forms": "~2.4.0",
    "@angular/http": "~2.4.0",
    "@angular/platform-browser": "~2.4.0",
    "@angular/platform-browser-dynamic": "~2.4.0",
    "@angular/router": "~3.4.0",

    "angular-in-memory-web-api": "~0.2.4",
    "systemjs": "0.19.40",
    "core-js": "^2.4.1",
    "rxjs": "5.0.1",
    "zone.js": "^0.7.4"
  },
  "devDependencies": {
    "concurrently": "^3.1.0",
    "lite-server": "^2.2.2",
    "typescript": "~2.0.10",

    "canonical-path": "0.0.2",
    "http-server": "^0.9.0",
    "lodash": "^4.16.4",
    "protractor": "~4.0.14",
    "rimraf": "^2.5.4",

    "@types/node": "^6.0.46",
    "@types/jasmine": "^2.5.36"
  },
  "repository": {}
}

特徴としてはTypeScriptで書き、lite-serverを使用しているところです。 lite-serverでローカルサーバを起動させつつ、ファイルの変更を監視することができます。 Package.jsonが準備できたらインストールを開始します。

$ npm install

開発中は以下コマンドでサーバを実行してください。
$ npm start

コンポーネントとモジュール

Angular 2はコンポーネント指向だと先ほど説明しましたが、その基本は2つの要素から成ります。

  • コンポーネント
  • モジュール

コンポーネント

Angular 2のコンポーネントはメタデータとロジックを持ちます。

@Component({
  selector: 'my-app',
  template: '<h1>Hello!!</h1>',
})

export class AppComponent {}

上記コードではAppComponentという名前でコンポーネントを定義しています。

@Componentで指定された部分がメタデータです。ここにテンプレートであるHTMLなどを指定し、Viewを持ちます。そしてselectorで指定されたタグに、templateを表示します。

@Componentで定義したテンプレートを、HTMLでレンダリングするためには以下のコードを記述します。

<body>
    <my-app></my-app>
</body>

上記コードは、@Componentで指定したselectorをHTMLに定義しています。HTMLにselectorを指定すると、@Componentのtemplateで定義したテンプレートがレンダリングされます。

モジュール

コンポーネントなどの実装をグループ化したものです。Angular 2も多数のモジュールから成り立っています。 ルールとして、webページを作成する際は、最低一つのモジュールを含む必要があります。(これをルートモジュールと呼びます。)

import { BrowserModule }  from '@angular/platform-browser';
import { NgModule }       from '@angular/core';
import { AppComponent }   from './app.component';

@NgModule({
  imports: [
    BrowserModule,
  ],
  declarations: [
    AppComponent,
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

まずimportで必要なモジュールを取り込みます。BrowserModuleはWebページを表示するために使用するモジュールです。 @NgModuleというデコレータでメタデータを読み込んで、 AppModuleという名前をモジュールにつけています。今回は、だいたいの定義の仕方は、コンポーネントと変わりないですね。

@NgModuleでメタデータを定義します。引数の値は、下記のようになります。

  • imports
    • モジュールが必要とする他のモジュールをimportします
  • declarations
    • モジュールが含むコンポーネントを指定します
  • bootstrap
    • ブラウザからアクセスされた際に、初めに表示されるコンポーネントを指定します

ToDoのルートコンポーネントとルートモジュールを用意する

まずは初めに読み込まれるindex.htmlを用意します。


<!DOCTYPE html>
<html>
  <head>
    <title>Angular 2 ToDo</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <script src="node_modules/core-js/client/shim.min.js"></script>

    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>

    <script src="systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
  </head>

  <body>
    <my-app></my-app>
  </body>
</html>

scriptで必要なnode_moduleを読み込んでいる他に、System.jsを利用しています。Angular 2ではSystem.jsが採用されています。

System.jsを利用すると、大量の<script>タグを書かずに済み、読み込みの順番も気にせずに使用することができます。 また、開発環境でモジュールごとにファイルを分けることもできます。

System.import('app').catch(function(err){ console.error(err); });

先ほど説明したように、<my-app>というタグ内にコンポーネントが表示されます。

<body>
    <my-app></my-app>
</body>

ルートコンポーネント

まずはルートコンポーネント(app/app.component.ts)を用意します。

import {Component} from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: 'app/app.html',
})

export class AppComponent {
}

selectorには先ほどindex.htmlにあったmy-appタグに、templateUrlで指定したapp/app.htmlが表示されます。

app/app.htmlの中身は以下のようになります。

<section class="container">
  <todo-header></todo-header>
  <todo-content [todos]="todos"></todo-content>
  <todo-footer [todos]="todos"></todo-footer>
</section>

todo-headertodo-contenttodo-footerをそれぞれ指定します。各コンポーネントごとにviewを作成できるので、非常にスッキリとした見た目になります。 各コンポーネントについては後ほど説明します。

ルートモジュール(app/app.module.ts)

同様に、ルートモジュール(app/app.module.ts)を用意します。

import { BrowserModule }  from '@angular/platform-browser';
import { FormsModule }    from '@angular/forms';
import { NgModule }       from '@angular/core';
import { AppComponent }   from './app.component';
import { TodoService }    from './services/todo.service';
import {TodoHeaderComponent} from './components/header/header.component';
import {TodoContentComponent} from './components/content/content.component';
import {TodoFooterComponent} from './components/footer/footer.component';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule
  ],
  declarations: [
    AppComponent,
    TodoHeaderComponent,
    TodoContentComponent,
    TodoFooterComponent,
  ],
  providers: [
    TodoService,
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

declarationsで各コンポーネントを読み込みます。その他、必要なモジュールもimportします。

Angular 2のDI

Angular 2ではサービスクラスを使用する際はDIを利用します。 ここでDIとはDependency Injectionの略であるため、「依存性の注入」?と直訳してしまいがちなのですが、この和訳ではよく意味が通りませんね。詳しく説明をしていきましょう。 今回のケースでDependency(依存性)とは、インスタンスがもつサービスクラスへの依存性を指しています。下記コードのTestComponentは、TodoSeriviceというサービスクラスを利用するコンポーネントとして定義されていますので、本質的にTodoSeriviceに依ったクラスです。

export class TestComponent {
  constructor(private service:TodoService) {}
}

ここで、利用するサービスクラスは事前にprovidersで設定されているものとします。

providers: [
    TodoService,
],

TestComponentクラスは、サービスを引数として外部から受け取ることでDIを実現しています。TestComponentの依存先であるTodoServiceクラスを、インスタンス生成の際に引数として渡すような設計とすることで、外から柔軟に「注入」できるようになっています。これが依存性の注入…というより、「依存先の注入」というのがより適切な訳語かもしれませんね。

なぜDIが推奨されるかというと、各クラス同士の依存性を緩和するためです。

たとえば、以下のTestComponentDependingクラスの設計は、TodoServiceクラスに強く依存している状態といえます。

class TestComponentDepending {
    service:TodoService = new TodoService();
}

一方、DIを利用したTestComponentクラスの場合、TodoServiceクラスを継承した別のクラスも引数として渡せます。よって、クラスの利用者がTestComponentクラスの中身を書き換えることなく、いろいろなサービスクラスを渡せるようになり、拡張性が高くなるのです。

app/services/todo.service.ts

ロジックの部分を決めるサービスクラスを定義していきます。

import {Injectable} from "@angular/core";
import {Todo} from "../models/todo.model";

const STORAGE_KEY = 'angular2-todo';


@Injectable()
export class TodoService {

  todos:Todo[] = [];

  constructor() {
  }

  add(title:string):void {
    let newTodo = new Todo(
      Math.floor(Math.random() * 100000), // ランダムにIDを発番する
      title,
      false
    );
    this.todos.push(newTodo);
  }

  remove(todo:Todo):void {
    const index = this.todos.indexOf(todo);
    this.todos.splice(index, 1);
  }

  toggleComplate(todo:Todo):void {
    this.todos.filter(t => t.id === todo.id)
      .map(t => t.isCompleted = !t.isCompleted);
  }

  getComplatedCount():number {
    return this.todos.filter(todo => todo.isCompleted).length;
  }
}

Todoモデルを読み込んで、タスクを追加するadd、削除するremove、完了のフラグを管理するtoggleComplate、完了の件数を数えるgetComplatedCountを追加します。

app/models/todo.model.ts

export class Todo {
  constructor(public id:number,
              public title:string,
              public isCompleted:boolean) {
  }
}

モデルについては必要な属性を定義するだけです。

最後にそれぞれのcomponentを作成していきます。それぞれコンポーネントごとにクラス定義し、テンプレートとなるhtmlを準備していきます。 まずはタスク追加フォームを定義するheaderを作成します。

  • app/components/header/header.component.ts
  • app/components/header/header.html
<header>
  <h1>Todos</h1>
  <form>
    <div>
      <label>新しいTodo:</label>
      <input name="title" [(ngModel)]="title">
    </div>
    <button  (click)="addTodo()">追加</button>
  </form>
</header>

テキストフォームの値をviewからバインディングするために、(ngModel)を使用します。tittleという名前でheader.component.tsで取得できるようにします。

またタスクの追加ができるformのbutton要素にclickイベントを追加します。Angular 2では(click)={method_name}で設定できます。

ロジック部分はTodoServiceクラスを使用するため、ここでもconstructorにてDIを利用して定義しています。

import {Component} from '@angular/core';
import {TodoService} from '../../services/todo.service';

@Component({
  selector: 'todo-header',
  templateUrl: 'app/components/header/header.html'
})

export class TodoHeaderComponent {

  title:string;

  constructor(private service:TodoService) {}

  addTodo() {
    if (this.title != null && this.title.trim().length) {
      this.service.add(this.title);
      this.title = null;
    }
  }
}

先ほどのngModelの値をバインディングするために、title:stringを設定します。そしてclickイベントで設定したaddTodoを追加します。

title:string;
@Component

メタ要素を定義します。templateUrlでテンプレートとなるviewのパスを指定します。そしてそのテンプレートの中身をselectorで表示するタグ名を設定します。

component

追加されたタスクの一覧を表示します。各タスクの削除、タスクの完了のチェックボックスなどもここに表示します。

  • app/components/content/content.component.ts
  • app/components/content/content.html
<ul class="list-group">
  <li class="list-group-item" *ngFor="let todo of todos; let i = index">
    <div class="row">
      <div class="col-xs-10">
        <label>
          <input type="checkbox"
                 (click)="toggleComplate(todo)"
                 [checked]="todo.isCompleted">
        </label>
        <span [class.complate]="todo.isCompleted">
          \{\{i + 1\}\}. \{\{todo.title\}\}
        </span>
      </div>
      <div class="col-xs-2">
         <button class="btn btn-link" (click)="deleteTodo(todo)">削除</button>
      </div>
    </div>
  </li>
  <li class="list-group-item text-danger" *ngIf="!todos.length">Todoがありません。</li>
</ul>

繰り返しにはngForディレクティブを使用します。Ngforディレクティブを利用すると、以下のローカル変数が自動的に使用することができます。 今回はindex変数を利用して、現在のindex番号を表示しています。

  • index
    • 現在処理されているオブジェクトのオフセット番号
  • last
    • 最後のオブジェクトのときにtrueを返す
  • even
    • indexが偶数のときにtrueを返す
  • todd
    • indexが奇数のときにtrueを返す
[checked]="todo.isCompleted"

タスクの完了フラグを変更するためにチェックボックスを使用します。チェックボックスでバインディングするためにはcheckedを利用します。

import {Component, Input} from '@angular/core';
import {Todo} from "../../models/todo.model";
import {TodoService} from "../../services/todo.service";

@Component({
  selector: 'todo-content',
  templateUrl: 'app/components/content/content.html'
})

export class TodoContentComponent {
  @Input()
  todos:Todo[];

  constructor(private service:TodoService) { }

  ngOnInit(): void {
    this.todos = this.service.todos;
  }

  toggleComplate(todo:Todo) {
    this.service.toggleComplate(todo);
  }

  deleteTodo(todo:Todo) {
    this.service.remove(todo);
  }
}

基本的にはheaderと同じ作りになります。@Componentでメタデータを定義して、クラスにviewのイベントで必要なメソッドを定義していきます。

@InputとngOnInit

viewとコンポーネントの間にはLifecycle Hooksという仕組みがあり、コンポーネントの生成や破棄されるタイミングでコールバックの関数を指定できます。 コンポーネントが持っているデータの更新や(今回の場合はtodos)、viewを更新した場合の変更を検知できます。

今回コンポーネントで定義されているtodosはこのLifecycle Hooksを利用して実行できるようにしています。

@Input()
todos:Todo[];
ngOnInit(): void {
    this.todos = this.service.todos;
}

ngOnInit()を利用すると、@Input()でデータバインドされた入力値を初期化後に実行することができます。つまりthis.service.todosで取得したtodosをすぐにviewに反映することができるのです。

そのほかのLifecycle Hooksに関するメソッドも記載しておきます。

  • ngOnChanges
    • @Input()でデータバインドされた入力値が変更するたびに実行されます。
  • ngDoCheck
    • すべての変更を検出すると呼ばれます。
  • ngOnDestroy
    • コンポーネントを削除する前に呼ばれます。

最後にフッターを定義します。フッターにはタスクの合計数と、「完了」のチェックボックスにマークをつけた数を表示します。

  • app/components/footer/footer.component.ts
  • app/components/footer/footer.html
<footer class="container">
  <p *ngIf="todos">Todo消化状況: \{\{getCompletedCount()\}\} / \{\{todos.length\}\}</p>
</footer>

ngIfディレクティブを利用して、todosが存在する場合のみに表示するようにしています。

import {Component, Input} from '@angular/core';
import {TodoService} from '../../services/todo.service';
import {Todo} from "../../models/todo.model";

@Component({
  selector: 'todo-footer',
  templateUrl: 'app/components/footer/footer.html'
})
export class TodoFooterComponent {
  @Input()
  todos:Todo[];

  constructor(private service:TodoService) {}

  ngOnInit(): void {
    this.todos = this.service.todos;
  }

  getCompletedCount() {
    return this.service.getComplatedCount();
  }
}

基本的には今まで作成してきたコンポーネントとほぼ作りは同じです。 ngOnInitでtodosを取得して、@Input()を利用してviewとデータをバインディングさせている他、@Componentでメタデータを定義しています。

ルートモジュール(app/app.module.ts)

すべてのコンポーネントを定義したあとに再びルートモジュールの定義を見てみましょう。

import { BrowserModule }  from '@angular/platform-browser';
import { FormsModule }    from '@angular/forms';
import { NgModule }       from '@angular/core';
import { AppComponent }   from './app.component';
import { TodoService }    from './services/todo.service';
import {TodoHeaderComponent} from './components/header/header.component';
import {TodoContentComponent} from './components/content/content.component';
import {TodoFooterComponent} from './components/footer/footer.component';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule
  ],
  declarations: [
    AppComponent,
    TodoHeaderComponent,
    TodoContentComponent,
    TodoFooterComponent,
  ],
  providers: [
    TodoService,
  ],
  bootstrap: [ AppComponent ]
})

作成したコンポーネントをメタデータとして、declarationsで登録しているのがわかると思います。

<section class="container">
  <todo-header></todo-header>
  <todo-content [todos]="todos"></todo-content>
  <todo-footer [todos]="todos"></todo-footer>
</section>

app/app.htmlの中身も再び見てみましょう。それぞれのコンポーネントで設定されたタグが定義されています。 さら contentfootertodosはここに定義されています。

Angular 2についてのまとめ

  • コンポーネント指向なので各パーツごとに分けることができ、ソースコードを分割できる
    • 今回の場合は、header, content, footerに分けた
  • @input()や各コールバックを利用して、viewとコンポーネントでの変数のバインディングが簡単にできる
  • DIを利用しているのでそれぞれのクラスの依存度が少ない
  • TypeScriptで書けるので、記述量少なく保守性の高いソースコードを書くことができる

後編では、Reactによる同様のToDoツールの実装と、両者の比較を行っています。ぜひご覧ください。

参考

The post AngularJS vs React それぞれの利点と特徴(Angular編) first appeared on 株式会社Altus-Five.

]]>
/blog/2017/01/30/angularjs-vs-react/feed/ 0