# Angular ノート ## 【環境構築】 ### <参考サイト> * https://www.pc-koubou.jp/magazine/44001#section03 * https://codelab.website/angular4-tutorial-0/ --- ### ①node.jsをインストール https://nodejs.org/ja/ - [x] 古い方を選んだ方が吉、最新版だとエラーおこったりする #### nodebrewを使ってnode.jsのバージョンを切り替える 1. nodebrewをインストール `$ curl -L git.io/nodebrew | perl - setup` 2. パスを通す `$ echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.bash_profile` `$ source .bash_profile` `$ nodebrew help` 3. インストールできるバージョンを確認 `$ nodebrew ls-remote` 4. node.jsをインストール `$ nodebrew install v12.18.3` `$ nodebrew ls` 5. 使用するバージョンを設定 `$ nodebrew use v12.18.3` `$ node -v` ### ②TypeScriptをインストール `$ npm install -g typescript` ※より最新の機能を試したい場合は、夜間ビルドの最新版↓ `$ npm install -g typescript@next` ### ③Hello TypeScriptしてみる ■hello.tsファイルを作成し、以下を書く。 ```typescript const message:string = 'Hello! TypeScript!'; console.log(message); ``` ■コマンドプロンプトで`tsc hello.ts`を実行してコンパイル。コンパイル成功とすると、`hello.js`というファイルができている。 ■コマンドプロンプトで`node hello.js`を実行すると、「Hello!TypeScript!」と表示される。 ### ④Angularをインストール `$ npm install -g @angular/cli@10.0.4` ■バージョン確認 `$ ng version` ``` Angular CLI: 10.0.4 Node: 12.18.3 Package Manager: npm 6.14.15 ``` ### ⑤アプリケーションを作成する ``` $ ng new my-app # my-appはアプリ名 ? Would you like to add Angular routing? (y/N) y ? Which stylesheet format would you like to use? CSS # カーソル移動キーで選択してEnter ``` ### ⑥アプリケーションを起動する ``` $ cd my-app $ ng serve --open / $ npm start ``` ### ⑦Componentを作成する ``` $ ng generate component [component_name] ``` http://localhost:4200/ で自動的にブラウザが起動する。 ## Angular基本的なこと ### ngModel :::info * HTMLファイルのフォームで、データのやりとりを行うもの(双方向バインディング) * **[(ngModel)]** : データを指定(=value) ::: ```typescript= <div> <label>名前: <input type="text" [(ngModel)] = "member.name" placeholder="名前"> </label> </div> ``` ### *ngFor :::info * HTMLに.tsファイルからデータを渡して、HTML内でfor文を回す ::: ```typescript= <ul class="members"> <li *ngFor="let member of members"> // *ngFor <span class="badge">{{ member.id }}</span> {{ member.name }} </li> </ul> ``` ### *ngIf :::info ngIfに指定した条件で、コンテンツを表示/非表示にする ::: ```typescript= <div *ngIf="selectedMember"> <h2> {{ selectedMember.name }}</h2> <div><span>ID : </span>{{ selectedMember.id }}</div> <div> <label>名前 : <input type="text" [(ngModel)]="selectedMember.name" placeholder="名前"> </label> </div> </div> ``` ### イベントバインディング :::info * HTMLの特定のタグに設定するイベント * component.tsでイベント(メソッド)を定義する必要がある ::: ```typescript= 【***.component.tsファイル】 selectedMember: Member; onSelect(member: Member): void { this.selectedMember = member; } 【HTML】 <ul class="members"> <li *ngFor="let member of members" (click)="onSelect(member)"> <span class="badge">{{ member.id }}</span> {{ member.name }} </li> </ul> ``` ### コンポーネント間でデータを渡す :::info * 親コンポーネントに、プロパティ(データ)を渡す記述を追加する。 * 子コンポーネント側では、`@Input`を使って、プロパティを受け取る。 ::: ```typescript= 【親コンポーネント】 // [member]="selectedMember":プロパティを子に渡す <app-member-detail [member]="selectedMember"></app-member-detail> 【子コンポーネント】 export class MemberDetailComponent implements OnInit { // プロパティを受け取る @Input() member: Member; ... } // テンプレートでは、下のように記述することができる {{ member.name }} ``` ### サービス :::info * `$ ng generate service [service name]`で作成 * サーバーからデータを読み取る際に必要 * **Dependency Injection(依存性注入)**: * コンポーネント間の依存性を解決して、外部クラスを簡単に利用できるようにする、ソフトウェアパターン * 頭文字をとって「DI」「DIする」などと言う * Angularのサービスでは、コンストラクタの引数に、データ型としてサービスを定義することでDIすることができる。 ::: ```typescript= 【service.ts】 export class MemberService { getMembers(): Member[]{ return MEMBERS; // MEMBERSはmockデータ } } 【***.components.ts】 import { MemberService } from '../member.service'; // サービスはコンストラクタに定義する constructor(private memberService: MemberService) { } ... getMembers(): void{ // サービスに定義したgetMembersメソッドでモックデータを取得して、membersに代入する this.members = this.memberService.getMembers(); } // コンポーネントが初期化されるタイミングで実行されるメソッド // ライフサイクルメソッドの1つ ngOnInit(): void { this.getMembers(); } ``` ### データ取得を非同期処理にする :::info * サーバーからデータ取得する=非同期処理である必要がある * rxjsを使うと、非同期処理の設定を簡単に行える ::: ```typescript= 【service.ts】 import { Observable } from 'rxjs'; // データ型はObservable // ジェネリックにMemberデータの配列を指定 getMembers(): Observable<Member[]>{ // rxjsのofメソッド // 実行の際に渡した値をObervableオブジェクトに変換して返す関数 return of(MEMBERS); } 【***.component.ts】 getMembers(): void{ // subscribe関数は、service.tsで定義したof関数から値を受け取る // subscribe関数の中で、members関数を定義 // membersプロパティに、of関数から受け取ったmockデータを代入 members: Member[]; this.memberService.getMembers() .subscribe(members => this.members = members); } ``` ### ルーティング設定 :::info * `$ ng generate module app-routing --flat --module=app`を実行 * `app-routing`:モジュール名の後ろに必ず`-routing`を付ける * `--flat`:appディレクトリ直下に、ディレクトリを作成せずにファイルのみを作成するというオプション * `module=app`:`app.module.ts`の`imports:[]`に自動追加させるオプション ::: ```typescript= 【app-routing.module.ts】 // ルーティングに必要なモジュールをインポート // Routesはインターフェース import { RouterModule, Routes } from '@angular/router'; // ルート情報を定義 // pathにURLのパスを、componentに表示したいコンポーネントを設定 const routes: Routes = [ { path: 'members', component: MembersComponent } ] @NgModule({ imports: [ // ルーティングを有効にする設定 // 引数は先ほど定義したルート情報 RouterModule.forRoot(routes) ], // ルーティングモジュールを読み込むapp.moduleで使用可能にする exports: [ RouterModule ] }) 【app.component.html】 // 下記を追加 // ルート情報を定義したときに指定したcomponentが、表示される <router-outlet></router-outlet> ``` ### ナビゲーションリンクの追加 :::info * **`routerLink`**:Angularが提供している、ページ遷移の属性。 * href属性だと、ページ移動する際にアプリケーション全体を読み込みしてしまい、シングルページアプリケーションにはならない。 ::: ```typescript= <nav> <a routerLink="/members">社員一覧</a> </nav> ``` ### リダイレクト ```typescript= const routes: Routes = [ // pathMatch : パスが完全にマッチしているかを判定するオプション // pathにアクセスすると、redirectToにリダイレクトする { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, ] ``` ### 詳細ページ ```typescript= 【**-routing.module.ts】 const routes: Routes = [ { path: 'detail/:id', component: MemberDetailComponent }, ] 【***.component.html】 <a routerLink="/detail/{{ member.id }}">詳細ページ</a> ``` ### URLパラメータを取得するルーターの機能 ```typescript= // 以下URLのidパラメータを取得する場合 【**-routing.module.ts】 const routes: Routes = [ { path: 'detail/:id', component: MemberDetailComponent }, ] 【URLパラメーターを取得したいcomponent.ts】 constructor( // URLパラメータを取得するサービス private route: ActivatedRoute, ) { } getMember(): void { // +を付けることで数値化ができる const id = +this.route.snapshot.paramMap.get('id'); } ``` ### 戻るボタン :::info * **`Location`**:Angularで、ブラウザのブラウザバック・次のページへ機能を使えるようにしたもの。 ::: ```typescript= 【***.html】 <button (click)="goBack()">戻る</button> 【***.component.ts】 import { Location } from '@angular/common'; constructor( private location: Location ) { } goBack(): void { this.location.back(); } ``` ### RxJS :::info * **リアクティブプログラミングを行うJavaScriptライブラリ** * **リアクティブプログラミング**:流れてくるデータ(httpクライアントでやり取りするデータなど)に対して、関連性と操作を「宣言」的に記述するプログラミングの手法 * 流れてくるデータのことを、**ストリーム**(=Observable)という。 ::: ## Bootstrapを使う #### インストール・設定 1. `$ npm install bootstrap@4.5.0`(バージョン4.5.0を指定) 2. プロジェクト直下の`angular.json`ファイルに以下を追加 ```typescript= // 元からある"src/styles.css"より前に追加する "styles": [ "./node_modules/bootstrap/dist/css/bootstrap.min.css", // 追加 "src/styles.css" ], ``` ## カスタムパイプ #### Dateパイプの日付フォーマットを共通化する場合 1. カスタムパイプの作成 `$ ng generate pipe pipes/date-time` 2. 作成した`pipes/date-time.pipe.ts`を開く ```typescript= import { Pipe, PipeTransform } from '@angular/core'; // @Pipeデコレーターでパイプの機能が与えられている @Pipe({ // name部分は、テンプレートで呼び出す名前 name: 'dateTime' }) export class DateTimePipe implements PipeTransform { // テンプレートから受け取った値を処理するメソッド transform(value: unknown, ...args: unknown[]): unknown { return null; } } ``` 3. パイプを実装する formatDateをインポート ```typescript= import { formatDate } from '@angular/common'; ``` `transform()`メソッドを編集する ```typescript= export class DateTimePipe implements PipeTransform { // データ型unknownを書き換える transform(value: number, ...args: string[]): string { const format = args[0] || 'yyyy年MM月dd日 HH:mm'; return formatDate(value, format, 'en-US'); } } ``` 4. テンプレートを編集 カスタムパイプのname部分を、パイプとして指定する ```htmlembedded= <span class="datetime"> {{ info.date | dateTime }} </span> ``` ## FontAwesomeを使う #### インストール・設定 1. インストール `npm install @fortawesome/fontawesome-free@5.14.0 --save` 2. `angular.json`を開く。stylesに以下追加。(`src/styles.css`より前に追加する) ```json= "styles": ["./node_modules/@fortawesome/fontawesome-free/css/all.min.css", "src/styles.css" ], ``` 3. ローカルサーバーを再起動する `Ctrl+c`でサーバーを落とし、`$ npm start`で再起動する。 4. HTMLにfontawesomeのクラスを付与して使う。 ## エラー集 #### プロパティ 'name' に初期化子がなく、コンストラクターで明確に割り当てられていません。 ■ 原因:`ng new`コマンドを実行した際、以下の質問を「Yes」で実行し、TypeScriptの厳格チェックモードが有効になっているため。 ``` ? Do you want to enforce stricter type checking and stricter bundle budgets in the workspace?This setting helps improve maintainability and catch bugs ahead of time.For more information, see https://angular.io/strict ``` 1. `tsconfig.json`の内容が、下記と同じかを確認する。 ```json= /* To learn more about this file see: https://angular.io/config/tsconfig. */ { "compileOnSave": false, "compilerOptions": { "baseUrl": "./", "outDir": "./dist/out-tsc", "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "sourceMap": true, "declaration": false, "downlevelIteration": true, "experimentalDecorators": true, "moduleResolution": "node", "importHelpers": true, "target": "es2015", "module": "es2020", "lib": [ "es2018", "dom" ] }, "angularCompilerOptions": { "strictInjectionParameters": true, "strictInputAccessModifiers": true, "strictTemplates": true } } ``` 2. 同じ内容であれば、以下内容を削除する。 ```json= // 以下項目を削除 "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, // 「angularCompilerOptions」をまるごと削除 "angularCompilerOptions": { "strictInjectionParameters": true, "strictInputAccessModifiers": true, "strictTemplates": true } ``` ## 【チュートリアル:アプリ作成】 ### <参考サイト> * https://angular.jp/ 上の公式サイトに沿って行う --- ### ①ヒーローエディター #### **■コンポーネントの作成** `ng generate component heroes `(heroesはコンポーネント名) □プロパティの追加(heroes.component.ts) ```typescript hero = 'Windstorm'; ``` - [x] export class hoge{}でhtmlに値を渡している □ヒーローを表示する(heroes.component.html) ```html <h2>{{hero}}</h2> ``` export class hoge{}でhtmlに値を渡している □作成したコンポーネントを表示するには、AppComponentのテンプレートに追加する(app.component.html) ```html <h1>{{title}}</h1> <app-heroes></app-heroes> <!-- <app-heroes>はHeroesComponentを表示する --> ``` #### ■**インターフェース** :中身の実装を持たず、メンバーや型の定義だけ持つ。 □hero.tsをsrc/app下に作成。インターフェースを定義。 ```typescript export interface Hero { id: number; name: string; } ``` □インターフェースをインポートする(heroes.component.ts) ```typescript import { Component, OnInit } from '@angular/core'; import { Hero } from '../hero'; //インターフェースをインポート @Component({ selector: 'app-heroes', templateUrl: './heroes.component.html', styleUrls: ['./heroes.component.css'] }) export class HeroesComponent implements OnInit { // heroプロパティをHero型にリファクタリング hero: Hero = {       id: 1, name: 'Windstorm' }; constructor() { } ngOnInit() { } } ``` - [x] ヒーローを文字列からオブジェクトに変更したため、エラーが起こる #### **■オブジェクトを表示する** □テンプレートを書き換える(heroes.component.html) ```html <!-- 書き換え前 --> <h2>{{hero}}</h2> <!-- 書き換え後 --> <h2>{{hero.name | uppercase}} Details</h2> <!-- UppercasePipeで大文字表示 --> <div><span>id: </span>{{hero.id}}</div> <div><span>name: </span>{{hero.name}}</div> ``` - [x] Djangoのcontextに似てる? #### **■双方向バインディング** □テキストボックスでヒーローの名前を編集できるようにする □`[(ngModel)]`:Angularの双方向バインド構文。コンポーネントの値が変化したらテンプレートにも反映されて、テンプレートの値を変えたらコンポーネントの値に反映される。 □FormsModuleをインポートする(app.module.ts) ```typescript import { FormsModule } from '@angular/forms'; //NgModel lives here imports: [ BrowserModule, FormsModule //追加 ], ``` (heroes.component.html) ```html <div> <label for="name">Hero name: </label> <input id="name" [(ngModel)]="hero.name" placeholder="name"> </div> ``` ### ②リストの表示 #### **■ヒーローのモックを作成して一覧表示させる** □`src/app/`下に`mock-heroes.ts`を作成する ```typescript import { Hero } from './hero'; //Heroインターフェース //HEROESを配列として定義 export const HEROES: Hero[] = [ { id: 11, name: 'Dr Nice' }, { id: 12, name: 'Narco' }, { id: 13, name: 'Bombasto' }, { id: 14, name: 'Celeritas' }, { id: 15, name: 'Magneta' }, { id: 16, name: 'RubberMan' }, { id: 17, name: 'Dynama' }, { id: 18, name: 'Dr IQ' }, { id: 19, name: 'Magma' }, { id: 20, name: 'Tornado' } ]; ``` □ヒーローのモックを表示する(heroes.component.ts) ```typescript import { HEROES } from '../mock-heroes'; //HEROESモックをインポート export class HeroesComponent implements OnInit { heroes = HEROES; } ``` □`*ngFor`で一覧表示する(heroes.component.html) ```html <h2>My Heroes</h2> <ul class="heroes"> <li *ngFor="let hero of heroes"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> </ul> ``` - [x] Djangoの`{% for hofe in hoges %}`に似てる #### **■ヒーローの詳細を表示させる** □クリックイベントのバインディングを追加(heroes.component.html) ```html <li *ngFor="let hero of heroes" (click)="onSelect(hero)"> <!-- <li>をクリックすると、onSelect(hero)が実行される --> ``` □クリックイベントのハンドラーを追加(heroes.component.ts) ```typescript selectedHero?: Hero; onSelect(hero: Hero): void { this.selectedHero = hero; } ``` □詳細セクションを追加(heroes.component.html) `*ngIf`構文 ```html <div *ngIf="selectedHero"> <!-- クリックイベントが実行されたときに、<div>内を表示する --> <h2>{{selectedHero.name | uppercase}} Details</h2> <div><span>id: </span>{{selectedHero.id}}</div> <div> <label for="hero-name">Hero name: </label> <input id="hero-name" [(ngModel)]="selectedHero.name" placeholder="name"> </div> </div> ``` ### ③フィーチャーコンポーネントの作成 #### **■フィーチャーコンポーネントを作成する** □コマンドプロンプトで以下を実行 ``` ng generate component hero-detail ``` □テンプレートに、②で追加したヒーローの詳細部分をコピペ、一部変更(hero-detail.component.html) ```html // selectedHeroからheroに変更 <div *ngIf="hero"> <h2>{{hero.name | uppercase}} Details</h2> <div><span>id: </span>{{hero.id}}</div> <div> <label for="hero-name">Hero name: </label> <input id="hero-name" [(ngModel)]="hero.name" placeholder="name"> </div> </div> ``` □@Input()heroプロパティを追加(hero-detail.component.ts) Hero型のheroプロパティにバインドされる。 ```typescript import { Component, OnInit, Input } from '@angular/core'; //Inputを追加 import { Hero } from '../hero'; //追加 @Input() hero?: Hero; //追加 ``` □HeroDetailComponentを表示する(heroes.component.html) ```html <!-- 一番下に追加 --> <app-hero-detail [hero]="selectedHero"></app-hero-detail> ``` `[hero]="selectedHero`は、Angularのプロパティバインディング。 リスト内のヒーローをクリックすると、`selectedHero`が変更される。`selectedHero`が変更されると、property bindingは`hero`を更新。`HeroDetailComponent`は新しいヒーローを表示する。 ### ④サービスの追加 #### **■サービスとは** コンポーネント間で直接データの取得・保存は行うべきではない。お互いに繋がりのないコンポーネント・クラス間で情報を共有するために、サービスを使う。 □サービスの作成 コマンドプロンプトで以下を実行 ``` ng generate service hero ``` □`Hero`、`HEROES`をインポートする(hero.service.ts) ```typescript import { Hero } from './hero'; import { HEROES } from './mock-heroes'; //hetHeroesメソッドでHeroモックを返す getHeroes(): Hero[]{ return HEROES; } ``` □サービスを使えるようにする(hero.service.ts) ```typescript //ルートインジェクターに登録する @Injectable({ providedIn: 'root', }) ``` □サービスをインポートする(heroes.component.ts) ```typescript import { HeroService } from '../hero.service'; //heroesプロパティの定義を置き換える heroes: Hero[] = []; constructor(private heroService: HeroService) {} //メソッドを追加し、サービスからヒーローデータを取得できるようにする getHeroes(): void { this.heroes = this.heroService.getHeroes(); } //AngularがHeroesComponentインスタンスを生成した後、適切なタイミングで呼び出す ngOnInit() { this.getHeroes(); } ``` #### **■Observalデータとは** * Observalデータが必要なワケ * さっき実装した`HeroService.getHeroes()`は同期的なメソッド。`HeroService`が即座にヒーローデータを取得できる。 * しかし実際のアプリでリモートサーバーからヒーローデータを取得しようとすると、`HeroSerivce`はサーバーのレスポンスを待たなければならず、`getHeroes()`は即座にヒーローデータを返すことができない。 * →つまり、`HeroService.getHeroes()`は非同期処理をする必要がある * 非同期のデータの流れの手続き * observalはsbscribeすることでデータが流れ始める * データを流したあとキャンセル可能 □`Observable`をインポートする(hero.service.ts) ```typescript import {Observable, of} from 'rxjs'; getHeroes(): Observable<Hero[]> { //修正 const heroes = of(HEROES); //修正 return heroes; } ``` □observalをsubscribeする(heroes.components.ts) ```typescript= //以下のように書き直す getHeroes(): void { this.heroService.getHeroes() .subscribe(heroes => this.heroes = heroes); } ``` #### **■メッセージを表示する** □コマンドプロンプトで次を実行し`Message Component`を作成 `$ ng generate component message` □`MessagesComponent`を表示するために`AppComponent`のテンプレートに追加(app.component.html) ```html //追加 <app-messages></app-messages> ``` □`MessageService`の作成 `$ ng generate service message` (message.service.ts) ```typescript import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class MessageService { messages: string[] = []; //新たなメッセージをmessagesへ追加するメソッド add(message: string) { this.messages.push(message); } //messagesの値を初期化するメソッド clear() { this.messages = []; } } ``` □MessagesServiceをインポートする(hero.service.ts) ```typescript import { MessageService } from './message.service'; constructor(private messageService: MessageService) { } getHeroes(): Observable<Hero[]> { const heroes = of(HEROES); //以下を追加 //addメソッド:ヒーローが取得されたときにメッセージを送信する this.messageService.add('HeroService: fetched heroes'); return heroes; } ``` □HeroServiceからのメッセージを表示する(messages.component.ts) ```typescript //MessageServiceをインポートする import { MessageService } from '../message.service'; //コンストラクターにpublicなmessageServiceプロパティを宣言する constructor(public messageService: MessageService) {} ``` □MessageServiceへバインドする(messages.component.html) ```html <!-- ngIf=表示するメッセージがある場合のみ表示する処理 --> <div *ngIf="messageService.messages.length"> <h2>Messages</h2> <button class="clear" (click)="messageService.clear()">Clear messages</button> <div *ngFor='let message of messageService.messages'> {{message}} </div> </div> ``` □メッセージ送信・表示、ユーザーの選択履歴を表示する(heroes.component.ts) ```typescript import { MessageService } from '../message.service'; export class HeroesComponent implements OnInit { ... ... constructor(private heroService: HeroService, private messageService:MessageService) {} onSelect(hero: Hero): void { this.selectedHero = hero; this.messageService.add(`HeroesComponent: Selected hero id=${hero.id}`); } } ``` ### ⑤ナビゲーションの追加 #### **■AppRoutingModuleを追加する** □`$ ng generate module app-routing --flat --module=app` □`app-routing.module.ts`を編集 ```typescript import { NgModule } from '@angular/core'; //ルーティング機能を持たせることができるRouterModule, Routesをインポート import { RouterModule, Routes } from '@angular/router'; import { HeroesComponent } from './heroes/heroes.component'; const routes: Routes = [ //ルーターに向かう場所(HeroesComponent)を伝える //path:ブラウザのURLにマッチする文字列 //component:ルーターが表示すべきコンポーネント { path: 'heroes', component: HeroesComponent } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { } ``` #### **■RouterOutletを追加する** * RouterOutletは、ルーティングされたビューをどこに表示するかルーターに教える □app.component.htmlを編集 ```html <h1>{{title}}</h1> <!-- HeroesComponentへ遷移するリンクを追加 --> <nav> <a routerLink="/heroes">Heroes</a> </nav> <!-- <app-heroes>を置き換える--> <router-outlet></router-outlet> <app-messages></app-messages> ``` * `/`:アプリタイトルは表示されるが、ヒーローリストは表示されない * `/heroes`:ヒーローのマスター・詳細ビューが表示される #### **■ダッシュボードビューを追加する** □`DashboardComponent`を追加する `$ ng generate component dashboard` □`dashboard.component.html` ```html <h2>Top Heroes</h2> <div class="heroes-menu"> <a *ngFor="let hero of heroes"> {{hero.name}} </a> </div> ``` □`dashboard.component.ts`等も編集(参照:https://angular.jp/tutorial/toh-pt5) □デフォルトルートの追加(app-routing.module.ts) * 空のURLの場合、`/dashboard`にリダイレクトする設定を行う。 ```typescript const routes: Routes = [ //デフォルトルート { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, { path: 'heroes', component: HeroesComponent }, { path: 'dashboard', component: DashboardComponent }, ]; ``` #### **■詳細ページの設定** □urlを設定する(app-routing.module.ts) ```typescript import { HeroDetailComponent } from './hero-detail/hero-detail.component'; //pathを追加 { path: 'detail/:id', component: HeroDetailComponent }, ``` □各ページに、詳細ページに遷移するリンクを設定する ```html <a routerLink="/detail/{{hero.id}}"> //リンクの設定 ``` #### **■ルーティングの設定** □hero-detail.component.ts ```typescript //追加 import { ActivatedRoute } from '@angular/router'; import { Location } from '@angular/common'; import { HeroService } from '../hero.service'; constructor( private route: ActivatedRoute, private heroService: HeroService, private location: Location ) {} ngOnInit(): void { this.getHero(); } getHero(): void { //paramMap:URLから取得したパラメータの値の辞書 //id:フェッチ(取得・読み出す)するヒーローのidを返す const id = Number(this.route.snapshot.paramMap.get('id')); this.heroService.getHero(id) .subscribe(hero => this.hero = hero); } ``` * `ActivateRoute`:クエリパラメータ? * `location`:ブラウザと対話するためのAngularサービス □hero.service.ts ```typescript //追加 getHero(id: number): Observable<Hero> { // For now, assume that a hero with the specified `id` always exists. // Error handling will be added in the next step of the tutorial. const hero = HEROES.find(h => h.id === id)!; this.messageService.add(`HeroService: fetched hero id=${id}`); return of(hero); } ``` #### **■戻るボタンの設置** 詳細ビューに来たときの経路によって、 ヒーローリストまたはダッシュボード画面に戻るボタンをHeroDetailに設置する。 □hero-detail.component.html ```html <button (click)="goBack()">go back</button> ``` □hero-detail.component.ts ```typescript goBack(): void { this.location.back(); } ``` ### ⑥サーバーからデータを取得する #### **■HTTPサービスの有効化** □HttpClientをインポート(app.module.ts) ```typescript import { HttpClientModule } from '@angular/common/http'; @NgModule({ imports: [ //追加 HttpClientModule, ], }) ``` #### **■In-memory Web APIモジュールを利用する** □インストールする `$ npm install angular-in-memory-web-api --save` □サービスを作成する `$ ng generate service InMemoryData` □app.module.ts ```typescript import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api'; import { InMemoryDataService } from './in-memory-data.service'; imports: [ ... HttpClientModule, // 追加 // The HttpClientInMemoryWebApiModule module intercepts HTTP requests // and returns simulated server responses. // Remove it when a real server is ready to receive requests. HttpClientInMemoryWebApiModule.forRoot( InMemoryDataService, { dataEncapsulation: false } )], ``` ## Lifecycle Hooks :::info * **コンポーネントが変化(作成・変更・破棄)するタイミングで実行されるコールバックメソッドの総称** * 例えば、Webページを構成するコンポーネントは、表示時に生成され、フォーム入力などで状態が変更され、非表示時に破棄される、といったライフサイクルがある。 * Lifecycle Hooksを設定すると、コンポーネント自身が変化を検出したときに**自動でコールバックメソッドが実行**される。(エンジニアが実行のタイミングを設定しなくてよい) ::: 【参考サイト】 https://angular.jp/guide/lifecycle-hooks https://blog.yuhiisk.com/archive/2020/08/30/angular-lifecycle-hooks.html https://qiita.com/dairappa/items/09cc30d5c8565034ec0b https://codezine.jp/article/detail/10046 ### 1. 利用されるケース * コンポーネントの初期化時にHttpクライアントでデータを取得する。 * 親コンポーネントの初期化時に、子コンポーネントののDOM要素を取得する。 * コンポーネントの`@Input()`で受け取るデータ内容を検証する。 * 設定したイベントリスナーをコンポーネントが破棄される際に削除する。 ### 2. Lifecycle Hooksの実行順序と種類 #### ■Lifecycle Hooksの実行順序 ![Lifecycle Hooksの実行順序](https://i.imgur.com/vmH3rA5.png) #### ■Lifecycle Hooksの種類 | メソッド名 | 処理内容と実行タイミング | | -------- | -------- | | **ngOnChanges** | データバインドされた入力プロパティに値を設定/リセットする際に実行する。 | | **ngOnInit** | データバインドされた入力値を初期化した際に一度だけ実行する。 | | **ngDoCheck** | データの変更を検出するたびに実行する。 | | **ngAfterContentInit** | ng-contentを読み込んだあとに一度だけ実行する。読み込んだ外部コンテンツを操作する必要がある場合に利用する。 | | **ngAfterContentChecked** | ng-contentを読み込むたびに実行する。読み込んだ外部コンテンツを操作する必要がある場合に利用する。 | | **ngAfterViewInit** | コンポーネントのビューとその中の子ビュー、またはディレクティブを含むビューの変更を検知した時に実行する。ビューに存在するDOMや子コンポーネントの変更を監視する際に利用する。 | | **ngOnDestroy** | コンポーネントまたはディレクティブを破棄する直前に実行する。`RxJS`のSubscriptionをunsubscribeしたり、`element.removeEventListener`を実行するために利用する。 | ### 3. コンポーネントのライフサイクル ![](https://i.imgur.com/vd6TUSS.png) ### 4. メソッドを使う :::info * Lifecycle Hooksは各メソッドを実装するために**インターフェイス**が必要 ::: #### **(例)ngOnInitの場合** ```typescript= // @angular/coreからOnInitインターフェイスをインポート import { Component, OnInit } from '@angular/core'; import { LoggerService } from './logger.service'; @Component({ ... }) // コンポーネントのクラスにOnInitインターフェースを実装 export class PeekABooComponent implements OnInit { constructor(private logger: LoggerService) { } // OnInitインターフェースを実装したので // ngOnInitメソッドの定義が必要になる ngOnInit() { this.log(`OnInit`); } private log(msg:string){ this.logger.log(`${nextId++} ${msg}`); } } ```