らいふうっどの閑話休題

興味のあることをゆる~く書いていく

実践 Angular: Standalone Components をざっくりキャッチアップしてみた Part2

実践 Angular: Standalone Components をざっくりキャッチアップしてみた Part2

lacolaco さん監修の実践 Angular: Standalone Components をざっくりキャッチアップしてみます。 zenn.dev

Who is lacolaco? / lacolaco さんて、どんな人?
  • Google Developers Expert for Angular
  • Angularコントリビューター
  • Angular日本ユーザー会代表
  • jsprimer.net 著者

Zenn プロフィールから引用

前回

lifewood.hatenablog.com

■ 2 種類のインジェクターがあるそうです。


■ 新規に service を作って理解してみる

$ cd src/app/list
$ ng g s list # list.servce.ts の作成
$ ng g i list # list.servce.ts 用の interface の作成
$ cd ../../../
$ tree
.
├── LICENSE
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── src
│   ├── app
│   │   ├── app.component.css
│   │   ├── app.component.html
│   │   ├── app.component.spec.ts
│   │   ├── app.component.ts
│   │   └── list
│   │       ├── list-item
│   │       ├── list.component.css
│   │       ├── list.component.html
│   │       ├── list.component.spec.ts
│   │       ├── list.component.ts
│   │       ├── list.service.spec.ts # 新規作成
│   │       ├── list.service.ts # 新規作成
│   │       └── list.ts # 新規作成
│   ├── assets
│   ├── favicon.ico
│   ├── index.html
│   ├── main.ts
│   └── styles.css
├── tree.txt
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json

  • list.ts

export interface List {
  name: string;
}

  • list.service.ts

import { Injectable } from '@angular/core';
import { List } from './list';

@Injectable({
  providedIn: 'root',
})
export class ListService {
  constructor() {}

  get(): List[] {
    // 単純な List[] を返すだけ
    return [{ name: 'hoge' }, { name: 'fuga' }];
  }
}


■ service を component, html に反映します。

  • list.component.ts

import { Component, OnInit, inject } from '@angular/core';
import { CommonModule } from '@angular/common';

import { Observable, filter, of } from 'rxjs';

import { ListItemComponent } from './list-item/list-item.component';
import { ListService } from './list.service';
import { List } from './list';

@Component({
  selector: 'app-list',
  standalone: true,
  imports: [CommonModule, ListItemComponent],
  templateUrl: './list.component.html',
  styleUrls: ['./list.component.css'],
+  // ListService を依存注入(DI)
+  providers: [{ provide: ListService, useClass: ListService }],
})
export class ListComponent implements OnInit {

  list: List[] = [];

-  // これまでの実装
-  constructor(private service: ListService) {}

+  // スタンドアロンコンポーネント用の依存注入(DI)
+  readonly service = inject(ListService);
+  constructor() {}

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

  • list.component.html

- <app-list-item><span>hoge</span></app-list-item>
- <app-list-item><span>fuga</span></app-list-item>

+ <app-list-item *ngFor="let item of list">
+   <span>{{ item.name }}</span>
+ </app-list-item>


■ HttpClient を DI してみます。

  • プロバイド関数 に関する箇所を読んでみます。
  • provideHttpClient() を利用します。

  • src/assets/jsonlist.json を作成します。

[{ "name": "hoge" }, { "name": "fuga" }]
  • main.ts

import { bootstrapApplication } from '@angular/platform-browser';
+ import { provideHttpClient } from '@angular/common/http';

import { AppComponent } from './app/app.component';

- bootstrapApplication(AppComponent).catch((error) => console.error(error));
+ bootstrapApplication(AppComponent, 
+ {
+   providers: [provideHttpClient()],
+ }
+ ).catch((error) => console.error(error));

  • list.service.ts を HttpClient 使った実装にします。

import { Injectable, inject } from '@angular/core';
+ import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { List } from './list';

@Injectable({
  providedIn: 'root',
})
export class ListService {

-  // これまでの実装
-  constructor(private http: HttpClient) {}

+ // 依存注入(DI)します。
+  readonly http = inject(HttpClient);
+  constructor() {}

-  get(): List[] {
-   return [{ name: 'hoge' }, { name: 'fuga' }];
-  }

+  // HttpClient 実装に変更します。
+  get(): Observable<Array<List>> {
+    return this.http.get<Array<List>>('./assets/json/list.json');
+  }
}

  • list.component.ts

import { Component, OnInit, inject } from '@angular/core';
import { CommonModule } from '@angular/common';

import { Observable, filter, of } from 'rxjs';

import { ListItemComponent } from './list-item/list-item.component';
import { ListService } from './list.service';
import { List } from './list';

@Component({
  selector: 'app-list',
  standalone: true,
  imports: [CommonModule, ListItemComponent],
  templateUrl: './list.component.html',
  styleUrls: ['./list.component.css'],
  providers: [{ provide: ListService, useClass: ListService }],
})
export class ListComponent implements OnInit {
- list: List[] = [];
+ // 戻り値の型が Observable になったので、コンポーネントのプロパティの型も変更
+ list$: Observable<List[]> = of([]);

  readonly service = inject(ListService);

  constructor() {}

  ngOnInit(): void {
-    this.list = this.service.get();
+   // 戻り値の型が Observable になったので、コンポーネントのプロパティの型も変更
+   this.list$ = this.service
+     .get()
+     .pipe(filter((list) => !!list && list.length > 0));
  }
}

  • list.component.html

- <app-list-item *ngFor="let item of list">
+ <!-- データバインドの型が Observable になったので、async を実装
+ <app-list-item *ngFor="let item of list$ | async">
  <span>{{ item.name }}</span>
 </app-list-item>

まとめ
  • provideHttpClient() 関数を定義するのと依存注入(DI)の実装の仕方を覚えるだけで基本的には良いようです。
  • ルーティング のキャッチアップは次回に続きます。

関連記事
参考文献