AngularMVC-入門書
ユーザーインターフェイスを使用してソフトウェアを設計する場合、拡張と保守が容易になるようにコードを構造化することが重要です。 時間の経過とともに、アプリケーションのさまざまなコンポーネントの責任を分離するためのいくつかのアプローチがありました。 これらのデザインパターンに関する文献はたくさんありますが、初心者がさまざまなパターンの制限の特徴とそれらの違いを理解することは非常に混乱する可能性があります。
このチュートリアルでは、主要な2つのアプローチ、Model-View-Controller(MVC)パターンとModel-View-ViewModel(MVVM)パターンについて説明します。 MVVMパターンでは、コントローラーはViewModelに置き換えられます。 これら2つのコンポーネントの主な違いは、一方のビューともう一方のコントローラーまたはViewModelの間の依存関係の方向です。
TypeScriptとAngularで書かれたブラウザアプリケーションを使って、アイデアを開発し、例を挙げてパターンを説明します。 TypeScriptは、コードに型情報を追加するJavaScriptの拡張機能です。 このアプリケーションは、MacOS/iOSで人気のあるNotesアプリケーションを模倣します。 AngularはMVVMパターンを適用します。 掘り下げて、MVCパターンとMVVMパターンの主な違いを見てみましょう。
AngularCLIを使用してアプリケーションをセットアップする
開始するには、AngularCLIをインストールする必要があります。 最初にノードとnpm
がインストールされていることを確認してください。 まだ行っていない場合は、 node.js.org にアクセスし、指示に従ってNodeをダウンロードしてインストールします。 次に、コンピューターでターミナルを開き、npm
コマンドを実行してAngularCLIをインストールします。
npm install -g @angular/[email protected]
システム構成によっては、sudo
を使用してシステム管理者としてこのコマンドを実行する必要がある場合があります。 これにより、ng
コマンドがシステムにグローバルにインストールされます。 ng
は、Angularアプリケーションの作成、操作、テスト、およびビルドに使用されます。 選択したディレクトリでng new
を実行すると、新しいAngularアプリケーションを作成できます。
ng new AngularNotes
これにより、新しいアプリケーションに関するいくつかの質問に答えるウィザードが起動し、ディレクトリレイアウトとスケルトンコードを含むいくつかのファイルが作成されます。 最初の質問は、ルーティングモジュールを含めることに関するものです。 ルーティングを使用すると、ブラウザのパスを変更することで、アプリケーション内のさまざまなコンポーネントに移動できます。 この質問にははいと答える必要があります。 2番目の質問では、使用するCSSテクノロジーを選択できます。 非常に単純なスタイルシートのみを含めるので、プレーンなCSS形式で十分です。 質問に答えると、ウィザードは必要なすべてのコンポーネントのダウンロードとインストールを開始します。
マテリアルデザインとそのコンポーネントを使用して、アプリケーションの見栄えを良くすることができます。 これらは、アプリケーションディレクトリ内のnpm
コマンドを使用してインストールできます。 ng new
コマンドは、AngularNotes
というディレクトリを作成する必要があります。 その中に移動して、次のコマンドを実行します。
npm install --save @angular/[email protected] @angular/[email protected] @angular/[email protected] @angular/[email protected]
src
ディレクトリには、アプリケーションのソースコードが含まれています。 ここで、src/index.html
はブラウザのメインエントリポイントです。 選択したテキストエディタでこのファイルを開き、次の行を<head>
セクションに貼り付けます。 これにより、マテリアルアイコンに必要なフォントが読み込まれます。
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
src/style.css
スタイルシートにはグローバルスタイルが含まれています。 このファイルを開き、次のスタイルを貼り付けます。
@import "~@angular/material/prebuilt-themes/deeppurple-amber.css"; body { margin: 0; font-family: sans-serif; } h1, h2 { text-align: center; }
次に、src/app/app.module.ts
を開きます。 このファイルには、グローバルに利用できるようにするすべてのモジュールのインポートが含まれています。 このファイルの内容を次のコードに置き換えます。
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { FlexLayoutModule } from "@angular/flex-layout"; import { MatToolbarModule, MatMenuModule, MatIconModule, MatInputModule, MatFormFieldModule, MatButtonModule, MatListModule, MatDividerModule } from '@angular/material'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent, ], imports: [ BrowserModule, BrowserAnimationsModule, FlexLayoutModule, FormsModule, ReactiveFormsModule, MatToolbarModule, MatMenuModule, MatIconModule, MatInputModule, MatFormFieldModule, MatButtonModule, MatListModule, MatDividerModule, AppRoutingModule, ], bootstrap: [AppComponent] }) export class AppModule { }
この時点で、ファイルsrc/app/app.component.html
にアプリケーションレイアウトを作成する方法を紹介し始めることができます。 しかし、これですでにアプリケーションアーキテクチャの議論に飛び込むことができます。 代わりに、次のセクションでは、最初にモデルの実装について説明します。 次のセクションでは、ビューとそのViewModelとの関係について説明します。
モデル
モデルには、アプリケーションのビジネスエンドが含まれています。 単純なCRUD(Create Read Update Delete)アプリケーションの場合、モデルは通常、単純なデータモデルです。 より複雑なアプリケーションの場合、モデルは当然、その複雑さの増加を反映します。 ここに表示されているアプリケーションでは、モデルはテキストノートの単純な配列を保持します。 各ノートには、 ID 、タイトル、およびテキストがあります。 Angularでは、モデルはいわゆるservicesにコード化されています。 ng
コマンドを使用すると、新しいサービスを作成できます。
ng generate service Notes
これにより、src/app/notes.service.ts
とsrc/app/notes.service.spec.ts
の2つの新しいファイルが作成されます。 このチュートリアルでは、他の.spec.ts
ファイルと同様に、これらのファイルの2番目を無視できます。 これらのファイルは、コードの単体テストに使用されます。 本番環境にリリースするアプリケーションでは、そこにテストを記述します。 src/app/notes.service.ts
を開き、その内容を次のコードに置き換えます。
import { Injectable } from '@angular/core'; import { BehaviorSubject, Observer } from 'rxjs'; export class NoteInfo { id: number; title: string; } export class Note { id: number; title: string; text: string; } @Injectable({ providedIn: 'root' }) export class NotesService { private notes: Note[]; private nextId = 0; private notesSubject = new BehaviorSubject<NoteInfo[]>([]); constructor() { this.notes = JSON.parse(localStorage.getItem('notes')) || []; for (const note of this.notes) { if (note.id >= this.nextId) this.nextId = note.id+1; } this.update(); } subscribe(observer: Observer<NoteInfo[]>) { this.notesSubject.subscribe(observer); } addNote(title: string, text: string): Note { const note = {id: this.nextId++, title, text}; this.notes.push(note); this.update(); return note; } getNote(id: number): Note { const index = this.findIndex(id); return this.notes[index]; } updateNote(id: number, title: string, text: string) { const index = this.findIndex(id); this.notes[index] = {id, title, text}; this.update(); } deleteNote(id: number) { const index = this.findIndex(id); this.notes.splice(index, 1); this.update(); } private update() { localStorage.setItem('notes', JSON.stringify(this.notes)); this.notesSubject.next(this.notes.map( note => ({id: note.id, title: note.title}) )); } private findIndex(id: number): number { for (let i=0; i<this.notes.length; i++) { if (this.notes[i].id === id) return i; } throw new Error(`Note with id ${id} was not found!`); } }
ファイルの上部近くに、NoteInfo
とNote
の2つのクラス定義があります。 Note
クラスにはノートに関する完全な情報が含まれていますが、NoteInfo
にはid
とtitle
のみが含まれています。 NoteInfo
ははるかに軽量で、すべてのノートタイトルを表示するリストで使用できるという考え方です。 Note
とNoteInfo
はどちらも単純なデータクラスであり、ビジネスロジックは含まれていません。 ロジックはNotesService
に含まれており、アプリケーションのモデルとして機能します。 いくつかのプロパティが含まれています。 notes
プロパティは、Notes
オブジェクトの配列です。 この配列は、モデルの信頼できる情報源として機能します。 関数addNote
、getNote
、updateNote
、およびdeleteNote
は、モデルのCRUD操作を定義します。 これらはすべてnotes
配列に直接作用し、配列内の要素を作成、読み取り、更新、および削除します。 nextId
プロパティは、メモを参照できる一意のIDとして使用されます。
notes
配列が変更されるたびに、プライベートupdate
メソッドが呼び出されることに気付くでしょう。 このメソッドは2つのことを行います。 まず、メモをローカルストレージに保存します。 ブラウザのローカルストレージが削除されていない限り、これによりデータがローカルに保持されます。 これにより、ユーザーはアプリケーションを閉じて後で開いても、メモにアクセスできます。 実際のアプリケーションでは、CRUD操作は、データをローカルに保存する代わりに、別のサーバー上のRESTAPIにアクセスします。
update
によって実行される2番目のアクションは、notesSubject
プロパティに新しい値を発行することです。 notesSubject
は、RxJSのBehaviorSubject
であり、圧縮されたNoteInfo
オブジェクトの配列が含まれています。 BehaviorSubject
は、すべてのオブザーバーがサブスクライブできるオブザーバブルとして機能します。 このサブスクリプションは、NotesService
のsubscribe
メソッドによって可能になります。 サブスクライブしているオブザーバーは、update
が呼び出されるたびに通知されます。
モデルの実装から取り除く主なことは、モデルがビューやコントローラーの知識を持たないスタンドアロンサービスであるということです。 これは、MVCアーキテクチャとMVVMアーキテクチャの両方で重要です。 モデルは他のコンポーネントに依存してはなりません。
景色
次に、ビューに注目したいと思います。 Angularアプリケーションでは、ビューは.html
テンプレートと.css
スタイルシート内にあります。 これらのテンプレートの1つについては、ファイルsrc/app/app.component.html
ですでに説明しました。 ファイルを開き、次のコンテンツを貼り付けます。
<mat-toolbar color="primary" class="expanded-toolbar"> <span> <button mat-button routerLink="/">{{title}}</button> <button mat-button routerLink="/"><mat-icon>home</mat-icon></button> </span> <button mat-button routerLink="/notes"><mat-icon>note</mat-icon></button> </mat-toolbar> <router-outlet></router-outlet>
ちょっとしたスタイリングも加えてみませんか? src/app/app.component.css
を開き、次のスタイルを追加します。
.expanded-toolbar { justify-content: space-between; align-items: center; }
app.component
にはメインページのレイアウトが含まれていますが、意味のあるコンテンツは含まれていません。 コンテンツをレンダリングするコンポーネントをいくつか追加する必要があります。 このようにもう一度ng generate
コマンドを使用します。
ng generate component Home ng generate component Notes
これにより、2つのコンポーネントが生成されます。 各コンポーネントは、.html
、.css
、および.ts
ファイルで構成されています。 今のところ、.ts
ファイルについて心配する必要はありません。 これについては、次のセクションで説明します。 (このチュートリアルでは完全に無視している.spec.ts
ファイルもあることを忘れないでください。)
src/app/home/home.component.html
を開き、内容を次のように変更します。
<h1>Angular Notes</h1> <h2>A simple app showcasing the MVVM pattern.</h2>
次に、src/app/notes/notes.component.html
を開き、コンテンツを以下のコードに置き換えます。
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" class="notes"> <mat-list fxFlex="100%" fxFlex.gt-sm="20%"> <mat-list-item *ngFor='let note of notes'> <a> {{note.title}} </a> </mat-list-item> </mat-list> <mat-divider fxShow="false" fxShow.gt-sm [vertical]="true"></mat-divider> <mat-divider fxShow="true" fxShow.gt-sm="false" [vertical]="false"></mat-divider> <div fxFlex="100%" fxFlex.gt-sm="70%" *ngIf="!editNote" class="note-container"> <h3>{{currentNote.title}}</h3> <p> {{currentNote.text}} </p> <div fxLayout="row" fxLayoutAlign="space-between center" > <button mat-raised-button color="primary">Edit</button> <button mat-raised-button color="warn">Delete</button> <button mat-raised-button color="primary">New Note</button> </div> </div> <div fxFlex="100%" fxFlex.gt-sm="70%" *ngIf="editNote" class="form-container"> <form [formGroup]="editNoteForm"> <mat-form-field class="full-width"> <input matInput placeholder="Title" formControlName="title"> </mat-form-field> <mat-form-field class="full-width"> <textarea matInput placeholder="Note text" formControlName="text"></textarea> </mat-form-field> <button mat-raised-button color="primary">Update</button> </form> </div> </div>
付属のsrc/app/notes/notes.component.css
は次のようになります。
.notes { padding: 1rem; } .notes a { cursor: pointer; } .form-container, .note-container { padding-left: 2rem; padding-right: 2rem; } .full-width { width: 80%; display: block; }
ここまでは順調ですね!
アプリケーションのメインビューを表すsrc/app/notes/notes.component.html
をご覧ください。 テンプレート:Note.title
など、値を入力できるように見えるプレースホルダーがあります。 上記のバージョンでは、ビューはアプリケーション内のコードを参照していないようです。
MVCパターンに従う場合、ビューはデータを挿入できるスロットを定義します。 また、ボタンがクリックされるたびにコールバックを登録するためのメソッドも提供します。 この点で、ビューはコントローラーを完全に知らないままになります。 コントローラはアクティブに値を入力し、コールバックメソッドをビューに登録します。 コントローラーだけがビューとモデルの両方を認識し、2つをリンクします。
以下に示すように、AngularはMVVMパターンと呼ばれる別のアプローチを採用しています。 ここで、コントローラーはViewModelに置き換えられています。 これは次のセクションのトピックになります。
ViewModel
ViewModelは、コンポーネントの.ts
ファイルにあります。 src/app/notes/notes.component.ts
を開き、以下のコードを入力します。
import { Component, OnInit } from '@angular/core'; import { Note, NoteInfo, NotesService } from '../notes.service'; import { BehaviorSubject } from 'rxjs'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; @Component({ selector: 'app-notes', templateUrl: './notes.component.html', styleUrls: ['./notes.component.css'] }) export class NotesComponent implements OnInit { notes = new BehaviorSubject<NoteInfo[]>([]); currentNote: Note = {id:-1, title: '', text:''}; createNote = false; editNote = false; editNoteForm: FormGroup; constructor(private formBuilder: FormBuilder, private notesModel: NotesService) { } ngOnInit() { this.notesModel.subscribe(this.notes); this.editNoteForm = this.formBuilder.group({ title: ['', Validators.required], text: ['', Validators.required] }); } onSelectNote(id: number) { this.currentNote = this.notesModel.getNote(id); } noteSelected(): boolean { return this.currentNote.id >= 0; } onNewNote() { this.editNoteForm.reset(); this.createNote = true; this.editNote = true; } onEditNote() { if (this.currentNote.id < 0) return; this.editNoteForm.get('title').setValue(this.currentNote.title); this.editNoteForm.get('text').setValue(this.currentNote.text); this.createNote = false; this.editNote = true; } onDeleteNote() { if (this.currentNote.id < 0) return; this.notesModel.deleteNote(this.currentNote.id); this.currentNote = {id:-1, title: '', text:''}; this.editNote = false; } updateNote() { if (!this.editNoteForm.valid) return; const title = this.editNoteForm.get('title').value; const text = this.editNoteForm.get('text').value; if (this.createNote) { this.currentNote = this.notesModel.addNote(title, text); } else { const id = this.currentNote.id; this.notesModel.updateNote(id, title, text); this.currentNote = {id, title, text}; } this.editNote = false; } }
クラスの@Component
デコレータで、View.html
および.css
ファイルへの参照を確認できます。 一方、クラスの残りの部分では、ビューへの参照はまったくありません。 代わりに、NotesComponent
クラスに含まれるViewModelは、ビューからアクセスできるプロパティとメソッドを公開します。 これは、MVCアーキテクチャと比較して、依存関係が逆転していることを意味します。 ViewModelにはビューの知識はありませんが、ビューで使用できるモデルのようなAPIを提供します。 src/app/notes/notes.component.html
をもう一度見ると、テンプレート:CurrentNote.text
などのテンプレート補間がNotesComponent
のプロパティに直接アクセスしていることがわかります。
アプリケーションを機能させるための最後のステップは、どのコンポーネントがさまざまなルートを担当しているかをルーターに通知することです。 src/app/app-routing.module.ts
を開き、以下のコードに一致するようにコンテンツを編集します。
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { HomeComponent } from './home/home.component'; import { NotesComponent } from './notes/notes.component'; const routes: Routes = [ { path: '', component: HomeComponent }, { path: 'notes', component: NotesComponent }, ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
これにより、HomeComponent
がデフォルトルートにリンクされ、NotesComponent
がnotes
ルートにリンクされます。
メインのアプリケーションコンポーネントについては、後で実装するいくつかのメソッドを定義します。 src/app/app.component.ts
を開き、コンテンツを次のように更新します。
import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { public title = 'Angular Notes'; public isAuthenticated: boolean; ngOnInit() { this.isAuthenticated = false; } login() { } logout() { } }
コンポーネントには、title
とisAuthenticated
の2つのプロパティが含まれています。 これらの2つ目は、ユーザーがアプリケーションにログインしたかどうかを示すフラグです。 現在、false
に設定されています。 2つの空のメソッドは、ログインまたはログアウトをトリガーするコールバックとして機能します。 今のところ、空のままにしておきますが、後で記入します。
ビューを完了する
依存関係の方向に関するこの知識があれば、ボタンとフォームがViewModelでアクションを実行するようにViewを更新できます。 src/app/notes/notes.component.html
をもう一度開き、コードを次のように変更します。
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" class="notes"> <mat-list fxFlex="100%" fxFlex.gt-sm="20%"> <mat-list-item *ngFor='let note of notes | async'> <a (click)="onSelectNote(note.id)"> {{note.title}} </a> </mat-list-item> </mat-list> <mat-divider fxShow="false" fxShow.gt-sm [vertical]="true"></mat-divider> <mat-divider fxShow="true" fxShow.gt-sm="false" [vertical]="false"></mat-divider> <div fxFlex="100%" fxFlex.gt-sm="70%" *ngIf="!editNote" class="note-container"> <h3>{{currentNote.title}}</h3> <p> {{currentNote.text}} </p> <div fxLayout="row" fxLayoutAlign="space-between center" > <button mat-raised-button color="primary" (click)="onEditNote()" *ngIf="noteSelected()">Edit</button> <button mat-raised-button color="warn" (click)="onDeleteNote()" *ngIf="noteSelected()">Delete</button> <button mat-raised-button color="primary" (click)="onNewNote()">New Note</button> </div> </div> <div fxFlex="100%" fxFlex.gt-sm="70%" *ngIf="editNote" class="form-container"> <form [formGroup]="editNoteForm" (ngSubmit)="updateNote()"> <mat-form-field class="full-width"> <input matInput placeholder="Title" formControlName="title"> </mat-form-field> <mat-form-field class="full-width"> <textarea matInput placeholder="Note text" formControlName="text"></textarea> </mat-form-field> <button mat-raised-button color="primary">Update</button> </form> </div> </div>
NotesComponent
クラスのメソッドを直接参照していると、さまざまな場所で(click)
ハンドラーを確認できます。 これは、ビューがViewModelとそのメソッドについて知る必要があることを意味します。 依存関係を逆転させる理由は、ボイラープレートコードの削減です。 ViewとViewModelの間には双方向のデータバインディングがあります。 ビューのデータは、ViewModelのデータと常に同期しています。
Angularアプリに認証を追加する
適切なユーザー認証がなければ、優れたアプリケーションは完成しません。 このセクションでは、既存のAngularアプリケーションに認証をすばやく追加する方法を学習します。 Oktaは、わずか数行のコードでアプリにプラグインできるシングルサインオン認証を提供します。
Oktaの無料の開発者アカウントが必要になります。 表示されるフォームに詳細を入力し、利用規約に同意して、[開始]を押して送信するだけです。 登録が完了すると、Oktaダッシュボードに移動します。 ここでは、Oktaサービスに登録されているすべてのアプリケーションの概要を確認できます。
アプリケーションの追加をクリックして、新しいアプリケーションを登録します。 表示される次の画面で、アプリケーションのタイプを選択できます。 シングルページアプリケーションは、Angularアプリに最適です。 次のページに、アプリケーションの設定が表示されます。 ng serve
を使用してアプリケーションをテストする場合は、ポート番号を4200に変更する必要があります。
それでおしまい。 これで、後で必要になるクライアントIDが表示されるはずです。 これで、認証サービスをコードに含める準備ができました。 OktaはAngularに便利なライブラリを提供します。 アプリケーションのルートディレクトリで次のコマンドを実行することにより、インストールできます。
npm install @okta/[email protected] --save
app.module.ts
を開き、OktaAuthModule
をインポートします。
import { OktaAuthModule } from '@okta/okta-angular';
さらに下の同じファイルで、imports
のリストに以下を追加します。
OktaAuthModule.initAuth({ issuer: 'https://{yourOktaDomain}/oauth2/default', redirectUri: 'http://localhost:4200/implicit/callback', clientId: '{clientId}' })
このスニペットでは、{clientId}
を、Okta開発者ダッシュボードで取得したばかりのクライアントIDに置き換える必要があります。
パスワードなしで特定のルートにアクセスできないようにするには、src/app/app-routing.module.ts
を変更する必要があります。 OktaCallbackComponent
およびOktaAuthGuard
のインポートを追加します。
import { OktaCallbackComponent, OktaAuthGuard } from '@okta/okta-angular';
次に、ルートの配列に別のルートを追加します。
{ path: 'implicit/callback', component: OktaCallbackComponent }
implicit/callback
ルートは、ユーザーがログインプロセスを完了すると、Oktaによって呼び出されます。 OktaCallbackComponent
は結果を処理し、認証プロセスを要求したページにユーザーをリダイレクトします。 個々のルートを保護するために、次のように、そのルートにOktaAuthGuard
を追加するだけで済みます。
{ path: 'notes', component: NotesComponent, canActivate: [OktaAuthGuard] }
メインアプリケーションのViewModelを実装せずに残したことを忘れないでください。 src/app/app.component.ts
を再度開き、ファイルの先頭に次のインポートを追加します。
import { OktaAuthService } from '@okta/okta-angular';
次に、AppComponent
クラスのすべてのメソッドを実装します。
constructor(public oktaAuth: OktaAuthService) {} async ngOnInit() { this.isAuthenticated = await this.oktaAuth.isAuthenticated(); } login() { this.oktaAuth.loginRedirect(); } logout() { this.oktaAuth.logout('/'); }
やるべきことは1つだけです。 これで、ログインボタンとログアウトボタンをトップバーに追加できます。 src/app/app.component.html
を開き、</span>
を閉じた後、<mat-toolbar>
要素内にこれらの2行を追加します。
<button mat-button *ngIf="!isAuthenticated" (click)="login()"> Login </button> <button mat-button *ngIf="isAuthenticated" (click)="logout()"> Logout </button>
ログインボタンとログアウトボタンは、app.component.ts
ViewModelのlogin()
およびlogout()
メソッドにリンクされています。 これらの2つのボタンの可視性は、ViewModelのisAuthenticated
フラグによって決定されます。
これですべてです。 これで、認証を備えたMVVMアーキテクチャに基づく完全なアプリケーションができました。 アプリケーションのルートディレクトリでAngularテストサーバーを起動することで、テストできます。
ng serve
ブラウザを開き、http://localhost:4200
に移動します。 このようなものが表示されるはずです。
Angularで安全なアプリケーション開発の詳細
このチュートリアルでは、AngularがMVVMデザインパターンに基づいていることと、このパターンがよく知られているMVCパターンとどのように異なるかを示しました。 MVCパターンでは、コントローラーは、他の2つのコンポーネントによって提供されるオブザーバブルとオブザーバブルを使用して、ビューをモデルにリンクするだけです。 コントローラが接続を設定すると、ビューとモデルは直接通信しますが、誰と通信しているかはわかりません。 具体的には、コントローラーはそれ自体のアプリケーション状態を保持しません。 ビューとモデルを接続するためのファシリテーターにすぎません。 MVVMパターンでは、コントローラーはViewModelに置き換えられます。 ViewとViewModelは、双方向のデータバインディングを介してリンクされます。 それらは同じ状態を共有します。
MVCおよびMVVMのデザインパターンの詳細については、次のリンクを参照してください。
このチュートリアルのコードは、 oktadeveloper /okta-angular-notes-app-exampleから入手できます。
この投稿が気に入った場合は、私たちが公開している他の投稿も気に入っていただける可能性があります。 Twitterで@oktadevをフォローし、 YouTubeチャンネルに登録して、より優れたチュートリアルをご覧ください。