フロントエンド用のMVCサービスの理解:角度



導入
このポストは、MVCアーキテクチャがフロントエンドアプリケーションをどのように作成するかを理解するために3つのポストのシリーズの3番目です.目的はJavaScript/typescriptをオブジェクト指向言語として使用するアプリケーションに向けてJavaScriptをスクリプト言語として使用するWebページを発展させることによってフロントエンドアプリケーションを構築する方法を理解することです.
この第3のポストにおいて、アプリケーションはtypescriptを使用した第2のバージョンから角度を使用して造られる.したがって、この記事は、アプリケーションがTypeScriptから角度に移行されるところです.しかし、アプリケーションのすべての部分がどのように関連し、どのように構造化されるかを理解することは非常に重要です.角度では、DOMについて忘れることができます.ビュー.TSファイルは、我々のアプリから消えます.
最後に、最後の記事では、我々のコードをアングルフレームワークと統合するように変換します.
  • Part 1. Understanding MVC Services for Front End: VanillaJS
  • Part 2. Understanding MVC Services for Front End: TypeScript
  • Part 3. Understanding MVC Services for Front End: Angular

  • プロジェクトアーキテクチャ
    私たちが構築しようとしているものを理解するためのイメージより貴重なものは何もありません.

    このアプリケーションは、ドキュメントのDOMを修正し、すべての操作を実行する単一のJavaScriptまたはTypeScriptファイルを使用して構築することができますが、これは強く結合されたコードであり、我々はこのポストに適用しようとするものではありません.
    MVCアーキテクチャとはMVCは3層/パーツのアーキテクチャです
  • モデル-アプリケーションのデータを管理します.彼らがサービスに言及されるので、モデルは貧血(彼らは機能が不足します)です.
  • View/テンプレート-ユーザーがアプリケーションと対話するページ/GUI.
  • コントローラ-サービスとビューの間のリンク.
  • 以下に、我々の問題ドメインにあるファイル構造を示します.

    インデックス.HTMLファイルは、ルート要素を使用してアプリケーション全体を動的に構築するキャンバスとして機能します.
    最後に、私たちのファイルアーキテクチャは以下のtypescriptファイルから成ります:
  • ユーザ.モデル.ts -ユーザの属性(モデル).
  • ユーザ.サービスTS -ユーザーのすべての操作を管理します.
  • 利用者コンポーネント.TS -サービスとビューに参加する担当者.
  • 利用者コンポーネント.HTML -表示画面をリフレッシュし、変更する責任があります.
  • Appモジュールは以下のようなものです.
    import { FormsModule, ReactiveFormsModule } from '@angular/forms';
    
    import { AppComponent } from './app.component';
    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { UserService } from './shared/services/user.service';
    import { UsersComponent } from './views/users/users.component';
    
    @NgModule({
      declarations: [AppComponent, UsersComponent],
      imports: [BrowserModule, FormsModule, ReactiveFormsModule],
      providers: [UserService],
      bootstrap: [AppComponent]
    })
    export class AppModule {}
    
    BrowserModule、FormModuleおよびReactiveFormモジュールの3つのモジュールを使用することがわかります.最初のモジュールは、基本的な構造と属性のディレクティブを角度から取得するために使用され、一方、2番目と3番目のモジュールは、フォームを作成するために使用します.この例では、ユーザーモジュールが定義されていません.このモジュールには、ユーザサービスとユーザコンポーネントが含まれていることがわかります.実際には、ユーザーコンポーネントを2つのコンポーネント(リストとフォーム)に分割できますが、この例では、JavaScriptから角度への進化を示すことができます.

    モデル(貧血)
    この例では、最初に構築したクラスがアプリケーションモデルです.モデル.クラス属性から構成されるTSと、ランダムIDを生成するプライベートメソッド(これらのIDはサーバー内のデータベースから来る可能性があります).
    モデルには次のフィールドがあります.
  • 一意の値.
  • 名前.ユーザ名.
  • 年齢.利用者の年齢
  • コンプリート.リストからユーザーを横断できるかどうかを知るブール.
  • ユーザークラスは、typescriptを使用してタイプされました.しかし、ユーザーコンストラクタは、LocalStorageから、または、フォームによって、ユーザーデータ入力から提供されるプレーンオブジェクトを受信する.このプレーンオブジェクトは、任意のプレーンオブジェクトをインスタンス化することはできませんが、定義されたインターフェイスを満たすような方法で、ユーザーインターフェイスに準拠する必要があります.
    ユーザ.モデル.以下にTSを示します.
    export interface UserDto {
      name: string;
      age: string;
      complete: boolean;
    }
    
    export class User {
      public id: string;
      public name: string;
      public age: string;
      public complete: boolean;
    
      constructor(
        { name, age, complete }: UserDto = {
          name: null,
          age: null,
          complete: false
        }
      ) {
        this.id = this.uuidv4();
        this.name = name;
        this.age = age;
        this.complete = complete;
      }
    
      uuidv4(): string {
        return (([1e7] as any) + -1e3 + -4e3 + -8e3 + -1e11).replace(
          /[018]/g,
          (c: number) =>
            (
              c ^
              (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
            ).toString(16)
        );
      }
    }
    
    

    サービス
    ユーザの操作はサービス中で行われる.サービスはすべての論理負荷がそれらにあるので、モデルが貧血であるのを許容することです.この特定のケースでは、配列を使用して、すべてのユーザーを格納し、読み取り、変更、作成、削除に関する4つのメソッドをビルドします.このサービスでは、LocalStorageからユーザークラスに抽出されたオブジェクトをインスタンス化するモデルを使用することに注意してください.LocalStorageはデータを格納するだけでなく、格納されたデータのプロトタイプではないからです.バックエンドからフロントエンドまで移動するデータと同じですが、クラスをインスタンス化しません.
    クラスのコンストラクタは以下の通りです.
    constructor() {
      const users: UserDto[] = JSON.parse(localStorage.getItem('users')) || [];
      this.users = users.map(user => new User(user));
    }
    
    ユーザ変数として、ユーザがクラスのプロトタイプオブジェクトに変換された後にすべてのユーザを格納するユーザ変数としてクラス変数を定義していることに注意してください.
    我々がサービスで定義しなければならない次のものは、我々が開発したい操作の各々です.以下の操作は以下のように入力します.
     add(user: User) {
        this.users.push(new User(user));
        this._commit(this.users);
      }
    
      edit(userID: string, userToEdit: User) {
        this.users = this.users.map(user =>
          user.id === userID
            ? new User({
                ...user,
                ...userToEdit
              })
            : user
        );
    
        this._commit(this.users);
      }
    
      delete(userID: string) {
        this.users = this.users.filter(({ id }) => id !== userID);
        this._commit(this.users);
      }
    
      toggle(userID: string) {
        this.users = this.users.map(user =>
          user.id === userID
            ? new User({ ...user, complete: !user.complete })
            : user
        );
    
        this._commit(this.users);
      }
    
    それは、我々のデータストア(我々のケースlocalstorage)で実行される操作を保存するのに責任があるコミットメソッドを定義されていないままです.
    _commit(users: User[]) {
      localStorage.setItem('users', JSON.stringify(users));
    }
    
    このメソッドは、サービスを作成するときにバインディングされているコールバック関数を呼び出しません.すなわち、角度はコントローラとテンプレートの間の結合を行います.
    ファイルユーザ.サービスtsは以下の通りです.
    import { User, UserDto } from "../models/user.model";
    
    export class UserService {
      public users: User[];
    
      constructor() {
        const users: UserDto[] = JSON.parse(localStorage.getItem("users")) || [];
        this.users = users.map(user => new User(user));
      }
    
      _commit(users: User[]) {
        localStorage.setItem("users", JSON.stringify(users));
      }
    
      add(user: User) {
        this.users.push(new User(user));
        this._commit(this.users);
      }
    
      edit(userID: string, userToEdit: User) {
        this.users = this.users.map(user =>
          user.id === userID
            ? new User({
                ...user,
                ...userToEdit
              })
            : user
        );
    
        this._commit(this.users);
      }
    
      delete(userID: string) {
        this.users = this.users.filter(({ id }) => id !== userID);
        this._commit(this.users);
      }
    
      toggle(userID: string) {
        this.users = this.users.map(user =>
          user.id === userID
            ? new User({ ...user, complete: !user.complete })
            : user
        );
    
        this._commit(this.users);
      }
    }
    

    見解
    これは、シリーズの前のポストと比較して最も変化する部分です.この場合、DOMはDOMで動作する必要はありません.しかし、適切にテンプレートを定義する必要があります.
    この例で作成されたテンプレート(角エンリッチ型HTMLバージョン)は以下のようになります.
    <h1>Users</h1>
    
    <form [formGroup]="userForm" (ngSubmit)="add(userForm.value)">
      <input
        type="text"
        placeholder="Name"
        name="name"
        formControlName="name"
      /><input
        type="text"
        placeholder="Age"
        name="age"
        formControlName="age"
      /><button>Submit</button>
    </form>
    <ul class="user-list">
      <li *ngFor="let user of users">
        <input type="checkbox" (change)="toggle(user)" [checked]="user.complete" />
        <span>
          <s *ngIf="user.complete; else uncompleteName">{{ user.name }}</s>
          <ng-template #uncompleteName>{{ user.name }}</ng-template>
        </span>
        <span
          #age
          contenteditable="true"
          class="editable"
          (focusout)="edit(user, age)"
        >
          <s *ngIf="user.complete; else uncompleteAge">{{ user.age }}</s>
          <ng-template #uncompleteAge>{{ user.age }}</ng-template></span
        >
        <button class="delete" (click)="delete(user)">Delete</button>
      </li>
    </ul>
    
    この例は角のチュートリアルではありませんが、JavaScript -> typescript ->角からWebアプリケーションの進化を見ることができる一連の投稿です.しかし、私たちは、以前のポストの多くのDOM操作コードが* NGTOと* NGIFのような2つの構造的ディレクティブを提供することによって角度によって解決されたことに注意します.
    この例では角度が私たちを助けてくれたもう1つの興味深い点は、操作を実行するためにハンドラーを送信することによって両方の当事者間の接続を行わなくても、コントローラにテンプレートを接続しているので、反応形式の使用です.

    コントローラ
    このアーキテクチャの最後のファイルはコントローラ( user . component . ts )です.コントローラは依存関係の注入(DI)によって(サービスとFormBuilder)が持つ2つの依存関係を受け取ります.これらの依存関係は、プライベート変数のコントローラに格納されます.
    コントローラは、ビュー(テンプレート)に接続され、サービスを呼び出す属性を管理するだけです.正確に我々の最初のJavaScriptコードまたは前のポストの第2のタイプスクリプト版のように.この場合、フレームワークはDOMに関連するすべてのタスクを残しました.
    ファイルユーザ.コンポーネント.tsは以下の通りです.
    import { Component, OnInit } from '@angular/core';
    
    import { FormBuilder } from '@angular/forms';
    import { UserService } from 'src/app/shared/services/user.service';
    
    @Component({
      selector: 'app-users',
      templateUrl: './users.component.html',
      styleUrls: ['./users.component.css']
    })
    export class UsersComponent implements OnInit {
      public users;
      public userForm;
    
      constructor(
        private userService: UserService,
        private formBuilder: FormBuilder
      ) {
        this.userForm = this.formBuilder.group({
          name: '',
          age: ''
        });
      }
    
      ngOnInit() {
        this.refreshUsers();
      }
      refreshUsers() {
        this.users = this.userService.users;
      }
    
      add(userForm) {
        this.userService.add(userForm);
        this.refreshUsers();
        this.userForm.reset();
      }
      delete({ id }) {
        this.userService.delete(id);
        this.refreshUsers();
      }
      edit(user, { innerText: age }) {
        const { id } = user;
        this.userService.edit(id, { ...user, age });
        this.refreshUsers();
      }
      toggle({ id }) {
        this.userService.toggle(id);
        this.refreshUsers();
      }
    }
    

    結論
    この第3のポストでは、我々は、プロジェクトがanemicモデルが使用されるMVCアーキテクチャの後に構成されたWebアプリケーションを開発しました、そして、論理に対する責任はサービスにあります.
    このポストの教訓は、異なるファイルにおけるプロジェクトの構造化を、異なる責任とどのようにモデル/サービスとコントローラと完全に独立しているかを理解することです.
    また、このポストでは、私たちは、私たちが私たちが開発するすべてのWebアプリケーションにおいて全く同じであるDOMに関連した反復的な仕事について忘れることができるように、アプリケーションからTypeScriptから角度へのアプリケーションを移行させました.
    私の推薦は、JavaScriptに関連した最初のポストから始まり、使用するアーキテクチャを理解することです.次のステップは、TypeScript(2番目のポスト)を適用することによってコードを強化することです.そして、コードがフレームワークに適応されたこのポストを最終的に確認します.
    もちろん、これは角度チュートリアルではなく、技術からフレームワークまでのコードの進化についてです.
    このポストのGithub支店はhttps://github.com/Caballerog/Angular-MVC-Users
    当初公開https://www.carloscaballero.io 2019年11月12日