らいふうっどの閑話休題

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

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

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

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

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

Zenn プロフィールから引用

前回

lifewood.hatenablog.com

■ memo

ルーターのセットアップ準備

■ config, route module の作成

$ cd ~/stand-alone-components-sample/src/app
# config module生成(configファイルのディレクトリ移動、ファイル名変更、移動元ディレクトリ削除)
$ npx ng g m config && mv ./config/config.module.ts ./app.config.ts && rm -rf ./config
# route module生成(routeファイルのディレクトリ移動、ファイル名変更、移動元ディレクトリ削除)
$ npx ng g m route && mv ./route/route.module.ts ./app.route.ts && rm -rf ./route

  • app.config.ts

// この段階では、ファイル作成と class 名変更のみ
// この後の作業で書き換えます
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

@NgModule({
  declarations: [],
  imports: [
    CommonModule
  ]
})
export class ConfigModule { } 

  • app.route.ts

// この段階では、ファイル作成と class 名変更のみ
// この後の作業で書き換えます
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

@NgModule({
  declarations: [],
  imports: [CommonModule],
})
export class RoutesModule {}


■ ページ遷移用のコンポーネントを作成します

# 親コンポーネント生成
$ npx ng g c parent --standalone
CREATE src/app/parent/parent.component.css (0 bytes)
CREATE src/app/parent/parent.component.html (21 bytes)
CREATE src/app/parent/parent.component.spec.ts (594 bytes)
CREATE src/app/parent/parent.component.ts (297 bytes)
# 子コンポーネント生成
$ npx ng g c child --standalone
CREATE src/app/child/child.component.css (0 bytes)
CREATE src/app/child/child.component.html (20 bytes)
CREATE src/app/child/child.component.spec.ts (587 bytes)
CREATE src/app/child/child.component.ts (293 bytes)
# 孫コンポーネント生成
$ npx ng g c grand-child --standalone
CREATE src/app/grand-child/grand-child.component.css (0 bytes)
CREATE src/app/grand-child/grand-child.component.html (26 bytes)
CREATE src/app/grand-child/grand-child.component.spec.ts (623 bytes)
CREATE src/app/grand-child/grand-child.component.ts (316 bytes)


■ 上記手順のディレクトリ構成

$ 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
│   │   ├── app.config.ts # ルーティング環境設定
│   │   ├── app.routes.ts # ルーティング
│   │   ├── child # 子コンポーネント
│   │   │   ├── child.component.css
│   │   │   ├── child.component.html
│   │   │   ├── child.component.spec.ts
│   │   │   └── child.component.ts
│   │   ├── grand-child # 孫コンポーネント
│   │   │   ├── grand-child.component.css
│   │   │   ├── grand-child.component.html
│   │   │   ├── grand-child.component.spec.ts
│   │   │   └── grand-child.component.ts
│   │   ├── list
│   │   └── parent # 親コンポーネント
│   │       ├── parent.component.css
│   │       ├── parent.component.html
│   │       ├── parent.component.spec.ts
│   │       └── parent.component.ts
│   ├── assets
│   ├── favicon.ico
│   ├── index.html
│   ├── main.ts
│   └── styles.css
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json

ルーターの実装

ルーター関連の実装

  • app.config.ts

import { ApplicationConfig } from '@angular/platform-browser';
// main.ts のインポート文をこちらに移植
import { provideHttpClient } from '@angular/common/http';

import {
  provideRouter,
  withDebugTracing,
  withHashLocation,
  withInMemoryScrolling,
} from '@angular/router';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(),  // main.ts の providers セクションのコードをこちらに移植
    provideRouter(
      routes,
      // 複数のオプションを同時に指定する
      withHashLocation(),
      withDebugTracing(),
      withInMemoryScrolling()
    ),
  ],
};

  • app.route.ts

import { Routes } from '@angular/router';
import { ParentComponent } from './parent/parent.component';

export const routes: Routes = [
  {
    path: '',
    // 通常の読み込み
    component: ParentComponent,
  },
  {
    path: 'parent',
    // 単一の遅延読み込み
    loadComponent: () =>
      import('./parent/parent.component').then((m) => m.ParentComponent),
  },
];

  • main.ts

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

import { AppComponent } from './app/app.component';
+ import { appConfig } from './app/app.config';

// providers: [provideHttpClient()], は、app.config.ts へ移動
// ApplicationConfig で、関連する DI を一纏めにする為

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


■ 画面関係の実装変更

  • app.component.ts

import 'zone.js/dist/zone.js';
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
+ import { RouterOutlet } from '@angular/router';

import { ListComponent } from './list/list.component';

@Component({
  selector: 'app-root',
  standalone: true,
-  imports: [CommonModule, ListComponent],
+  imports: [CommonModule, RouterOutlet, ListComponent],
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  title = 'stand alone components sample';
}

  • app.component.html

 <h1>{{ title }}</h1>

- <app-list></app-list>
+ <router-outlet></router-outlet>

  • parent.component.html

 <p>parent works!</p>

+ <app-list></app-list>

  • parent.component.ts

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

+ import { ListComponent } from '../list/list.component';

@Component({
  selector: 'app-parent',
  standalone: true,
-  imports: [CommonModule],
+  imports: [CommonModule, ListComponent],
  templateUrl: './parent.component.html',
  styleUrls: ['./parent.component.css'],
})
export class ParentComponent {}


■ ポイント

  • main.ts の providers は、ApplicationConfig する時は、そちらで実装する。
  • ApplicationConfig を使用すると bootstrapApplication の実装が簡素化される
階層ルーティング

■ 子・孫コンポーネント用ルーティングファイル作成

$ cd src/app
# 親 route module生成(routeファイルのディレクトリ移動、ファイル名変更、移動元ディレクトリ削除)
$ npx ng g m parent.routes && mv ./parent.routes/parent.routes.module.ts ./parent.routes.ts && rm -rf ./parent.routes
CREATE src/app/parent.routes/parent.routes.module.ts (198 bytes)
# 子 route module生成(routeファイルのディレクトリ移動、ファイル名変更、移動元ディレクトリ削除)
$ npx ng g m child.routes && mv ./child.routes/child.routes.module.ts ./child.routes.ts && rm -rf ./child.routes
CREATE src/app/child.routes/child.routes.module.ts (197 bytes)
$ ../..
$ 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
│   │   ├── app.config.ts
│   │   ├── app.routes.ts
│   │   ├── child
│   │   ├── child.routes.ts # 子コンポーネント用ルーティング
│   │   ├── grand-child
│   │   ├── list
│   │   ├── parent
│   │   └── parent.routes.ts # 親コンポーネント用ルーティング
│   ├── assets
│   ├── favicon.ico
│   ├── index.html
│   ├── main.ts
│   └── styles.css
├── tree.txt
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json


■ 階層ルーティングを実装

  • app.routes.ts

import { Routes } from '@angular/router';
- import { ParentComponent } from './parent/parent.component';

- export const routes: Routes = [
-   {
-     path: '',
-     // 通常の読み込み
-     component: ParentComponent,
-   },
-   {
-     path: 'parent',
-     // 単一の遅延読み込み
-     loadComponent: () =>
-       import('./parent/parent.component').then((m) => m.ParentComponent),
-   },
- ];
// 下の階層の routes.ts をロード
+ export const routes: Routes = [
+   {
+     path: '',
+     loadChildren: () => import('./parent.routes').then((m) => m.parentRoutes),
+   },
+ ];

  • parent.routes.ts

import { Routes } from '@angular/router';

export const parentRoutes: Routes = [
  {
    path: '',
    // 自身(ParentComponent)のコンポーネント をロード
    loadComponent: () =>
      import('./parent/parent.component').then((m) => m.ParentComponent),
    // 下の階層の routes.ts をロード
    children: [
      {
        path: '',
        loadChildren: () =>
          import('./child.routes').then((mod) => mod.childRoutes),
      },
    ],
  },
];

  • child.routes.ts

import { Routes } from '@angular/router';

export const childRoutes: Routes = [
  {
    path: '',
    // 自身(ChildComponent)のコンポーネント をロード
    loadComponent: () =>
      import('./child/child.component').then((m) => m.ChildComponent),
    // 下の階層のコンポーネント をロード
    children: [
      {
        path: '',
        loadComponent: () =>
          import('./grand-child/grand-child.component').then(
            (m) => m.GrandChildComponent
          ),
      },
    ],
  },
];


■ parent コンポーネントとスタイル実装

  • parent.component.ts

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
+ import { RouterOutlet } from '@angular/router';

import { ListComponent } from '../list/list.component';

@Component({
  selector: 'app-parent',
  standalone: true,
-  imports: [CommonModule, ListComponent],
+  imports: [CommonModule, RouterOutlet, ListComponent],
  templateUrl: './parent.component.html',
  styleUrls: ['./parent.component.css'],
})
export class ParentComponent {}

  • parent.component.css

div {
  border: 2px solid red;
  margin: 5px;
  padding: 5px;
}

  • parent.component.html

<div>
  <p>parent works!</p>

  <app-list></app-list>

  <router-outlet></router-outlet>
</div>


■ child コンポーネントとスタイル実装

  • child.component.ts

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
+ import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-child',
  standalone: true,
-  imports: [CommonModule],
+  imports: [CommonModule, RouterOutlet],
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css'],
})
export class ChildComponent {}

  • child.component.css

div {
  border: 2px solid blue;
  margin: 5px;
  padding: 5px;
}

  • child.component.html

<div>
  <p>child works!</p>

  <router-outlet></router-outlet>
</div>


■ grand-child コンポーネントとスタイル実装

  • grand-child.component.css

div {
  border: 2px solid orange;
  margin: 5px;
  padding: 5px;
}

  • grand-child.component.html

<div>
  <p>grand-child works!</p>
</div>


■ 実行結果イメージ

まとめ
  • main.ts の providers は、ApplicationConfig する時は、そちらで実装する。
  • ApplicationConfig を使用すると bootstrapApplication の実装が簡素化される
  • ディレクティブの合成 のキャッチアップは次回に続きます。

関連記事
参考文献