Angularは15以降から使うべき８つの理由（始めよう、スタンドアロンコンポーネント）

※段階的に、Angular19基準の説明に変えています。

日本ではVueとReactの人気が高く、なかなか市民権を得られないAngularだったのですが、Angularも14になってからReact、Vue、新勢力のSvelteなどに対抗しうる画期的な機能が搭載され、昨今のTypeScript隆盛に合わせ、アメリカではかなり盛り返しを見せている（Stack Overflowの質問記事は18%増、JS2024の評価では数%のシェア回復、満足度で劇的な改善）ようです。

それが**スタンドアロンコンポーネント**という、今までのAngularの常識をくつがえすような革命的機能（Vueのcomposition APIやReactの関数コンポーネント化ぐらいの飛躍的進化）です。そのスタンドアロンコンポーネントはどういうものかというと、VueやReactのように単一コンポーネントで独立した働きを持たせることができるようになる機能なので、それまでの汎用モジュール（app.module.ts）を使用しなくてもアプリケーションを作成でき、しかも簡単に実装可能です。

スタンドアロンコンポーネントはAngular14では評価版、Angular15で安定版となっています。基本的なスタンドアロンコンポーネントの部分において、そこまでの仕様変更はなかったのですが、Angular15からdependency injection(依存性注入)がスタンドアロン対応に刷新され、サービス周りが大幅に改善されました。

![ang_banda.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272191/104e5bff-d36d-7893-d81b-0b0190fb16bf.jpeg)


ところがQiitaには、このスタンドアロンコンポーネントに特化した記事がなかったので、この機会に作成してみました。

前半が情報収集結果と動作テストによるレポートとAngular15による変更点、後半が実践的開発を元にした解説（Angular15ベース）となります。なお、現時点ではAngular15をベースにした記述になっていますが、Angular19の時点では、基本的なモジュールが標準実装されているほか、コンフィグファイル、ルーティングファイルなどファイルの処理分担が簡明になっています。また、Angular17からは分岐やループのテンプレート構文を簡潔な埋め込み表記（公式でビルトインコントロールフローと名付けられている）を利用できます。

そして、Angular18からは標準でのアプリケーション起動において**Viteとesbuildの併用**となっているため、今までと比較にならない速度で高速起動できます（**平均して6～7秒**ぐらい）。

# スタンドアロンコンポーネントの利点
海外サイト風にスタンドアロンコンポーネントの何が優れているのか利点をまとめてみました。

## 1：汎用モジュールに依存しないので軽量化を実現できる
なによりこれが最大の利点でしょう。汎用モジュールに依存しないということは**アプリケーションを大幅に軽量化**できるということで、海外サイトの分析によれば、**メモリ消費量は60％以上軽減**されています。つまり、AngularでもVueやReactのように小規模プロジェクトにも適したプロジェクトやアプリケーションを構築できるようになります。

- [Angular Standalone Components by examples](https://lean-forge.at/blog/angular-standalone-component-example)

## 2：作業の分担やアプリケーションの切換が簡単になる
今まではどうしても汎用モジュールの設定ファイル、app.module.tsにあらゆる設定を記述しなければいけなかったため、アプリケーションを切り換えたり、コンポーネント作成作業を分担させたりするのはけっこう面倒でした。その上、Angular起動時に他の汎用モジュールと干渉したりもしました。ですが、スタンドアロンコンポーネントの場合、main.tsで一通りの情報をインポートし、制御できるようになっています。すなわち、**簡単に開発環境やアプリケーションを切り換えられる**ようになっています。

例ではAppComponentインポート先を切り換えてブラウザで再ロードするだけで、別のアプリケーションとなります。

```ts:main.ts
import { enableProdMode,importProvidersFrom} from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
//import { AppComponent } from './project-one/app.component'; //ルートコンポーネント
import { AppComponent } from './project-two/app.component'; //切り替え対象のルートコンポーネント
import { environment } from './environments/environment';
import {RouterModule} from '@angular/router';
if (environment.production) {
  enableProdMode();
}

// StandaloneのBootstrap
bootstrapApplication(AppComponent,{
   providers:[]
}).catch(err => console.error(err));
```

※後のバージョンではconfigファイルも別ファイルに独立しています。

## 3：CommonModule一つで最低限の動作を実現できる
スタンドアロンコンポーネントを用いれば、**CommonModuleが標準実装**されます。そしてこのCommonModuleはngModule、FormsModule、BrowserModuleといったコンポーネント作成に最低限必要なモジュールを一通りスートにしてくれているので、逐一それらを呼び出す必要がなく、簡単にテンプレート上でngディレクティブやフォーム部品を使用できるようになります。

また、Angular13から試験実装されたリアクティブフォームがAngular14で正規実装となったので、それらを活用するのにも便利です。

## 4：使用したいモジュールだけを簡単にカスタマイズできる
汎用モジュールの設定ファイルapp.module.tsは必須でなくなりましたが、CommonModule以外にもRouterModuleやFormsModuleに代わるReactiveFormsModuleは場合によっては必須になります。また、ngModuleデコレータも廃止になっていないので、これを利用してアプリケーション起動に**必要なモジュールだけを集約したライブラリを作成**することも可能になります。しかも、従来のようなモジュール側の宣言とコンポーネント側のセレクタ指定が不要なので、簡単に作成、紐づけできます。

```ts:custom.module.ts
import {NgModule} from '@angular/core';
import { CommonModule } from '@angular/common';
import {RouterModule } from '@angular/router'
import {ReactiveFormsModule} from '@angular/forms'

@NgModule({
    //アプリケーション起動に必要なモジュール
	imports:[
    CommonModule, RouterModule, ReactiveFormsModule,
	],
    //外部で制御させたいモジュール
    exports:[
    CommonModule, RouterModule, ReactiveFormsModule,
    ],
})
export class CustomModule { } //必要なモジュールだけ集めて外部にエクスポートできる
```

使用したいコンポーネントでは、ライブラリだけインポートすれば、冗長な記述を省略できるので、従来の記述より、シンプルに表記できます。

```ts:sample.component.ts
import { Component } from '@angular/core';
//import { CommonModule } from '@angular/common'　//CommonModuleの呼出は不要
import { ActivatedRoute,Router } from '@angular/router' //RouterModuleの記述は不要
import { FormGroup,FormControl} from '@angular/forms' //ReactiveFormModuleの記述は不要
import { CustomModule } from '../custom.module' //先程設定したモジュールが内包される

@Component({
  standalone: true,
  imports: [CustomModule], //逐一、他のモジュールを記述する必要がなくなる
})
```

このライブラリで一括制御できるのはモジュールのほかコンポーネント、属性ディレクティブ、カスタムパイプも可能です（後述しますが、サービスは別の方法を用います）。

## 5： 任意のコンポーネントに従属コンポーネントを設定できる
今までだとapp.module.tsに使用したい全コンポーネントを記述する必要があり、親子コンポーネント同士の従属関係が今一つ不明瞭な部分がありましたが、スタンドアロンコンポーネントはVueやReactなどのように**任意のコンポーネント自体に従属のコンポーネントを持たせることができる**ので、コンポーネントの親子関係がより明瞭になります。

```ts:standalone-parent.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import {StandaloneChildComponent } from './standalone-child.component'; //子コンポーネント　

@Component({
  standalone: true,
  imports: [CommonModule,StandaloneChildComponent], //ここに従属コンポーネントを記述
  //従属関係にある子コンポーネント
  template: `
            <ul>
              <app-standalone-child></app-standalone-child>
            </ul>
            `
})
```

※importsプロパティに従属コンポーネントを記述するのを忘れないでください。またAngular17からはタグの記述を簡素化できます（上記の場合、`<app-standalone-child />`と記述可能）。

## 6：　ルーティング制御を分離、簡略化できる
app.module.tsが必須ではなくなったことで、**main.tsでルーティング情報を制御**させることができ、独立したファイルで管理することもできます。また、各コンポーネントにRouterModuleをエクスポートする必要もありません。

```ts:route-sample.ts
import { Routes } from "@angular/router";
//各種コンポーネント
import {TopComponent} from '../top.component';
import {RouteOneComponent} from '../route-one.component';
import {RouteTwoComponent} from '../route-two.component';
import {RouteThreeComponent} from '../route-three.component';
export const Route: Routes = [
	{path: '',component: TopComponent},
	{path: 'one',component: RouteOneComponent},
	{path: 'two',component: RouteTwoComponent,pathMatch: 'full'},
	{path: 'three',component: RouteThreeComponent,pathMatch: 'full'},
]
```

あとは、この設定ファイルをmain.tsだけに読み込ませれば、ルーティング設定は完了です。

```ts:main.ts
import { enableProdMode,importProvidersFrom} from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './project-one/app.component';
import { environment } from './environments/environment';
import {RouterModule,Routes} from '@angular/router';
import {Route} from './app/routes/route-sample' //ルーティングの設定ファイル
if (environment.production) {
  enableProdMode();
}

// StandaloneのBootstrap
bootstrapApplication(AppComponent,{
   providers:[importProvidersFrom(RouterModule.forRoot(Route))]
}).catch(err => console.error(err));
```

※後のバージョンでは、ルーティングファイルは別途で用意されており、app.routes.tsにルーティング内容を記述し、app.config.tsに紐づける形となっています。

## 7：従属コンポーネントが存在しない場合はセレクタを省略できる
地味ですが、これもありがたい機能です。今まではapp.module.tsで宣言(declarationに設定)してから呼び出していたために、対象のコンポーネントを紐づけるためセレクタ名指定が必須となっていましたが、スタンドアロンコンポーネントではテンプレート上に従属関係を持たないコンポーネントの場合は**セレクタ記述を省略**することができます。

さらに宣言が不要ということは、**属性ディレクティブやカスタムパイプなどもimportプロパティに設定するだけでダイレクトに制御**できたりします。

```ts:standoalone-sub.componet.ts
import { CommonModule  } from '@angular/common';
import { Component } from '@angular/core';
import { MessageDirective } from "../directives/message.directive"; //nameプロパティはmessage
import { TickerPipe } from "../pipes/ticker.pipe"; //nameプロパティはticker

@Component({
  //selector: app-standalone-sub 従属関係がないコンポーネントは独立できるのでセレクタ指定不要
  standalone: true,
  template: `<p message>{{"従属コンポーネントがない場合はセレクタ不要"| ticker }}</p>`,
  imports: [CommonModule,MessageDirective,TickerPipe], //必要なコンポーネント類はそのまま使える
})
```

## 8： クラスコンポーネント上での変数を柔軟に処理できる
ngModuleに依存しなくてもよいので、ngOnInitも必須でなくなりました。したがってコンストラクタ上で購読された変数を処理できます。そして、クラスコンポーネントがngOnInitで制御されなくなったため、**ローカル上に変数を設定**して、それをダイレクトにテンプレート上に返すこともできます（ngOnInitはあっても動くので、DOM呼出後に制御したい場合で使い分けるといいでしょう）。

プロバイダを用いたサービスの分配、インジェクタを用いた受け取りなども不要になり、直接サービスをコンストラクタ上で購読できます（ただ、この部分はAngular15で改善され、現在は非推奨の技術ですので、追記部分の「依存性注入」を参照してください）。

```ts:sample.ts
@component{
    standalone: true,
    template: `<p>{{ subview }}{{ localview }}</p>`
}
export class SampleComponent{
    subview: string
    constructor(private svs:CustomService){
       this.svs.sub.subscribe((v)=>{ this.subview = v}) //コンストラクタ上でサービスを購読できる
    }
    localview = "これをダイレクトに表示できる" //ローカル上に返す場合はそのままダイレクトに記述可能
}
```

このようにスタンドアロンコンポーネントは利点ばかりです(欠点もないことはなく、たとえばAngular14の時点ではサービスの分配などができないので、直接各種スタンドアロンコンポーネント毎にインポートする必要がありました)。

スタンドアロンコンポーネントは任意に使用が可能なので、スタンドアロン化したくない場合は従来のコンポーネントを併存させることもできるので、部分的で弾力的な改良、改修も可能となります。

# Angular15での改良点
Angular15ではprovideRouterというルーティングにおいて高速化を図れるメソッドが導入されています。また、これを使用すればルーティング制御において幾つかのライブラリも不要になるので、非常に記述がすっきりします。

```ts:main.ts
import { enableProdMode} from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser'
import { AppComponent } from './app/todo-views/app.component'
import { environment } from './environments/environment'
import {provideRouter} from '@angular/router'
import {Route} from './app/routes/app-todo'
if (environment.production) {
  enableProdMode();
}
// StandaloneのBootstrap
bootstrapApplication(AppComponent,{
   providers:[provideRouter(Route)]
}).catch(err => console.error(err));
```

## サービス周りの改善（依存性注入）
このprovideRouterを導入したのは理由があります。それはAngular14で保留扱いとなっていたサービス周りを改善するためです（Angularでいうサービスとは、ざっくばらんにいえばデータを扱うためのロジックを構築するクラスファイルのことです）。Angular15からはdependency injection（依存性注入）がスタンドアロンコンポーネントに対応したので、こっちからこそがスタンドアロンコンポーネントの本領発揮といえそうです（今まではコンストラクタからサービスを分配してきましたが、Angularとしてはあまり推奨されていない方法でもあるようです。そこでAngular15からはproviderによるサービス注入に対し、injectによるデータ受け取り《これをAngularでは**購読**と呼んでいる》が推奨されています）。

このサービス注入はルーティングファイル、アプリケーション、各種コンポーネントなどいずれからでも注入可能で、そこから新たに登場した**inject**メソッドを使って簡単にサービスを受け取ることができます。

※このinjectはデータをやりとりする際にデータを受け取るメソッドで、Vue3のinjectとほぼ同じ働きと見ていいでしょう（ただし、Vue3と違って直書きのトークンは通りません）。

### アプリケーション全体にサービスを注入する
共通のコンポーネントでサービスを受け取りたい場合、アプリケーションファイル（main.tsまたはapp.config.ts）を以下のように追記し、サービスを注入します（プロパティを省略した場合はサービスクラスそのものが分配されます）。

```ts:main.ts
import {HogeService} from './app/services/hoge-service' //インポートするサービス

// StandaloneのBootstrap
bootstrapApplication(AppComponent,{
   providers:[
    [HogeService], //ここを追記
    provideRouter(Route),
   ],
}).catch(err => console.error(err));
```

### その他の方法でサービスを注入する場合
#### ルーティングファイルから注入する
- ルーティングファイルの場合は任意のコンポーネントに対し、以下のように追記します。なので、サービスを渡したいコンポーネントとそうでないコンポーネントをはっきり分けることができます。

```ts:routes.ts
	{path: 'hoge/:id',component: HogeComponent,pathMatch: 'full',
    providers:[HogeService]}
```

#### デコレータから注入する
単一のデコレータから注入したい場合は以下のようにします。コンポーネント、ディレクティブ、カスタムパイプなどにも適用できます。

```ts:hoge.component.ts
@Component({
  standalone: true,
  imports: [CustomModule],
  templateUrl: './hoge.component.html',
  styleUrls: ['./hoge.component.css'],
  providers:[HogeService], //ここに記述する（importsではない）
})
```

## サービスを受け取る
サービスを受け取る場合ですが、Angular15からはinjectメソッドを使ってダイレクトに受け取ることができます（今までのようにprovidersプロパティへの追記不要）。ただし、その**サービスの受け皿はngOnitに限られている**ようで、スタンドアロンコンポーネントの強みであるローカルに展開しようとするとエラーが起きます。

```ts:detail-todo.component.ts
import { Component,OnInit,inject } from '@angular/core';
import {HogeService} from '../services/hoge-service'

export class HogeComponent implements OnInit{
  svs = inject(HogeService) //先程のサービスを受け取る
  }
  //サービスはngOnitでないと展開できない
  ngOnInit(){
    console.log(this.svs)
  }
}
```

## トークンを利用する
ダイレクトにサービスを分配したりすると効率も悪く、なにより安全性に大きな問題があります。なので、このサービス注入に対してトークン（受け渡し用パスワードのようなもの）を発行して、データを分配するのが一般的な方法です。

### トークンを発行して、サービスを注入する
トークンの発行には**InjectionToken**クラスから発行できます。トークンは型定義ファイルにでも書いておくといいでしょう。その際には必ずInjectionTokenをインポートしておいてください。

```ts:types_todos.ts
import { InjectionToken } from '@angular/core'
export const token = new InjectionToken<any>("hoge") //hogeは任意のパスワード
```

## トークンとサービスを紐付ける
main.tsでは以下のように記述しておく必要があります。provideプロパティにはトークンを設定することでトークンとサービスを紐付けることができます。また、任意の値を注入する場合はuseValueを使用するのですが、今回はサービスクラスそのものを注入するため**useClass**を使用します。

```ts:main.ts
import {HogeService} from './app/services/hoge-service' //分配したいサービスクラス
import {token} from './app/types/types_hoge' //受け取るためのトークン

bootstrapApplication(AppComponent,{
   providers:[
    [{provide:token,useClass:HogeService}],
    provideRouter(Route),
   ]
   //providers:[]
}).catch(err => console.error(err));
```

### サービスの受け取り側
受け取り側はトークンだけで注入されたサービスを受け取ることができるので、Vue3のように、自在に値を受け取ることができるようになります。なので、ここではトークンだけインポートしておきましょう。

※inject('hoge')と決め打ちでパスワードを書いてもVue3のように受け取ることはできません。受け渡しに用いるのは変数そのものというよりインジェクショントークンというオブジェクトとなっているからです。

```ts:detail-todo.component.ts
import {token} from '../types/hoge_todo' //トークンのみをインポート

@Component({
  standalone: true,
  imports: [CustomModule],
  templateUrl: './hoge.component.html',
  styleUrls: ['./hoge.component.css'],
})
export class DetailTodoComponent implements OnInit{
  svs = inject(token) //先程のhogeを引数に用いることでサービスを受け取ることができる
  ngOnInit(){
     console.log(this.svs)
  }
}
```

### 任意の値を注入したい場合
任意の値を注入したい場合はuseValueを使うのですが、このuseValueは単体では使えません。provideと同じようにトークンを用意しておけば、任意の値を受け渡すことができます。JSONデータなどを受け取る場合もこの方法を用います。

また、injectメソッドに型定義を入れておくこともできます。

```ts:hoge.component.ts
import {token,token2} from '../types/types_hoge'

@Component({
  standalone: true,
  imports: [CustomModule],
  templateUrl: './hoge.component.html',
  styleUrls: ['./hoge.component.css'],
  providers:[{provide: token2,useValue:'任意の値をここに記述'}]
})
export class HogeComponent implements OnInit{
  svs = inject<any>(token)
  val = inject<string>(token2) //useValueの値が展開される
  ngOnInit(){
      console.log(this.val) //任意の値をここに記述と表示される
  }
```

この辺りはAngular8から導入された依存性注入の基本とほとんど変わっていません（式の計算結果を受け渡すuseFactoryなども使えます）。

# スタンドアロンコンポーネントを作成
では、実際にスタンドアロンコンポーネントを作成してみましょう。

## 新規に作成する
スタンドアロンコンポーネントの作成はngコマンドから簡単に作れます。gはgenerate、cはcomponentの省略形です。もし、任意のディレクトリも作成する場合は--flatを外してください。

※Angular19からはstandaloneが標準となっているので、--standalone指定は不要です。

```
#ng g c hoge-fuga --standalone --flat 
```

このようにすれば、以下のようなコンポーネントが自動作成されます。このCommonModuleが従来のNgModuleの代わりとなり、FormsModule、BrowserModuleなども兼ねてくれています。

```ts:hoge-fuga.component.ts
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-hoge-fuga',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './hoge-fuga.component.html',
  styleUrls: ['./hoge-fuga.component.css']
})
export class HogeFugaComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

}
```

ただ、前述したようにスタンドアロンコンポーネントは必ずしもngOnInitを必要としないので、以下のように取り払っても大丈夫です。また、従属関係のコンポーネントが存在しない場合はセレクタプロパティを削除しても問題ありません（これもAngular19ではngOnitは任意設定に変わっています）。

```ts:hoge-fuga.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common'; 

@Component({
  selector: 'app-hoge-fuga', //場合によっては削除できる
  standalone: true,
  imports: [CommonModule],
  templateUrl: './hoge-fuga.component.html',
  styleUrls: ['./hoge-fuga.component.css']
})
//ngOninitを取り払う
export class HogeFugaComponent{
  constructor() { }
}
```

### 更に軽量化したスタンドアロンコンポーネントを作成する
次のように--minimalオプションを付与してスタンドアロンコンポーネントを作成すると更に軽量化できます（1プロジェクトあたり200KB程度で最低限のアプリケーションができるようになる）。

```
#ng g c hoge-piyo --standalone  --minimal --inline-template --inline-style --routing=false --style=css
```

※--inline-templateを指定するとhoge.component.htmlは作成されず、デコレータ内にtemplateプロパティが作成されます。--inline-styleも同様です。

## 既存のコンポーネントをスタンドアロンに変更する
既存のコンポーネントをスタンドアロンに変更することもできます。変更点に注釈を入れてみました。

一番大事なフラグが、①に示しているstandaloneプロパティで、これをtrueにすることで、このコンポーネントはスタンドアロンと宣言できます(**Angular19からはスタンドアロンが標準実装となった**ので、この宣言は不要になっています)。

また、それまで必須だったBrowserModuleも全部**CommonModule**の中に集約されることになるので②と③の記述は必須です。

```ts:before.component.ts
import { CommonModule  } from '@angular/common'; //②CommonModuleをインポートする
import { Component } from '@angular/core';
@Component({
  //selector: 'app-before',
  standalone: true, //①スタンドアロンコンポーネントの宣言
  styleUrls: ['./m.component.css'],
  template: `<p>従来のコンポーネントをスタンドアローンへ</p>`,
  imports:[CommonModule], //③使用モジュールを記述
})
//implements onInitとしなくても、データを転送できる
export class BeforeComponent{
  constructor()
  {
}
```

# 実践してみた
では、今から簡易なTodoアプリを作っていこうと思います。元々の開発環境はAngular14ですが、後にAngular15にアップデートして再構築した後に動作確認しています。その後Angular19にて、再度動作確認しています。

※ここでは、RxJSやルーターなどはほとんど説明を入れていません。自作記事で恐縮ではありますが、そこから参考にしていただければと思います。

https://qiita.com/BRSF/items/f8c38429bc92e9b504e3

※また、TypeScriptは頻繁に仕様変更が起きるので、あまり検証していません（勉強不足なのもありますが）。

## main.tsを設定する
スタンドアロンコンポーネントでは前述した通りapp.module.tsが不要です。そしてmain.tsが重要な役割を担うことになり、ここにルーティングも制御していきます。また任意のルーティングファイルを作っておけば、簡単に接続したいアプリケーションを切り替えられるので、分業も非常に楽になります。

それから今回はサービスも全体に分配したいので、main.tsから設定しておくといいでしょう。

※Angular15から採用されたprovideRouterはAngular/router内にあります。間違わないようにしましょう。Angular19ではプロジェクト部分のmain.tsは極力触らないようにできており、app.config.tsにて基本的なアプリケーション設定を行います（単一プロジェクトに対し、複数のアプリケーションを導入しやすくなっている）。

```ts:main.ts
import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { environment } from './environments/environment';
import {RouterModule,provideRouter} from '@angular/router'; //追記する
import {TodoService} from './app/services/todo-service'; //注入したいサービス
import {token} from './app/todo-types/types_todo'; //サービス注入用のトークン
import {Route} from './app/routes/app-todo' //ルーティング情報を外部ファイル化する
if (environment.production) {
  enableProdMode();
}

// StandaloneのBootstrap
bootstrapApplication(AppComponent,{
    [{provide:token,useClass:TodoService}], //分配したいサービス
    provideRouter(Route), //ルーティング設定
}).catch(err => console.error(err));
```

ルーティング情報の設定ファイルはこのように作ります。Angular19ではapp.routes.tsという独立ファイルとなっているので、よりルーティング設定をしやすくなっています。

```ts:app-todo.ts
import { Routes } from "@angular/router";
//各種コンポーネント
import {TodoComponent} from '../todo-views/todo.component';
import {AddTodoComponent} from '../todo-views/add-todo.component';
import {EditTodoComponent} from '../todo-views/edit-todo.component';
import {DetailTodoComponent} from '../todo-views/detail-todo.component';
export const Route: Routes = [
	{path: '',component: TodoComponent},
	{path: 'add',component: AddTodoComponent},
	{path: 'edit/:id',component: EditTodoComponent,pathMatch: 'full'},
	{path: 'detail/:id',component: DetailTodoComponent,pathMatch: 'full'},
]
```

次からはプロジェクトにTodoアプリのファイルを作成していきますが、その前にもうひとつ準備をしておきましょう。

## ライブラリで制御する
スタンドアロンコンポーネントはNgModuleが不要になると前述しましたが、逆にSPAに必要なモジュールだけをインポートだけして、ライブラリとして各種コンポーネントに送り出すこともできます（宣言が不要）。その際にインポート先のコンポーネントで使用したいモジュールを必ずexportsで記述してください。これを忘れると外部のコンポーネントで各種モジュールを使用できません。

※各種コンポーネントをライブラリに記述することも可能ですが、従属関係が曖昧になるので今回はここにコンポーネントを設定しません。

```ts:custom.module.ts
import {NgModule} from '@angular/core';
import { CommonModule } from '@angular/common';
import {RouterModule } from '@angular/router' //ルーティング制御に必要
import {ReactiveFormsModule} from '@angular/forms' //リアクティブフォーム制御に必要

@NgModule({
	imports:[
    CommonModule, RouterModule, ReactiveFormsModule,
	],
    //外部で制御させたいモジュール
    exports:[
    CommonModule, RouterModule, ReactiveFormsModule,
    ],
})
export class CustomModule { }
```

これで使用したいコンポーネントでは、ライブラリだけインポートすれば、冗長な記述を省略できます。これで準備ができたので、今からTodoアプリを作成していきます。

## Todoアプリを作る
Todoアプリのディレクトリ構造はこうなっています。●はコンポーネントの一連ファイル(コンポーネント、テンプレート、スタイル、スペック)です。ちなみに、大本になったアプリは以下の記事（Vueで作成）のもので、これを簡略化して更にAngularに書き換えています。

https://qiita.com/azukiazusa/items/205ae0cc5378419b1337

```
■app
    ■routes
        -app-todo.ts
    ■services
        -todo-service.ts
    ■todo-components
        -●todo-item //Todo各種の子コンポーネント
        -●todos //Todoリストの親コンポーネント
    ■todo-types
        -types-todo.ts //型定義とトークンを格納したファイル
    ■todo-views
        -●add-todo //新規登録
        -●app //ルートコンポーネント
        -●detail-todo //詳細
        -●edit-todo //編集
        -●todo //トップ画面
custom.module.ts #ライブラリ
main.ts

```

### ルートコンポーネント
全ての基準となるルートコンポーネントは以下のようになります。注意点としてテンプレートにルーティング先を示すrouter-outletタグを用いているので、RouterModuleも使用されています（先程作成したライブラリによって機能が内包されている）。

またルートコンポーネントはmain.tsに紐づいているのでセレクタ名指定は必須です。

```ts:app.component.ts
import { Component } from '@angular/core';
import { CustomModule } from '../custom.module' //先程設定したモジュールが内包される

@Component({
  selector: 'app-root', //main.tsに紐づいている
  standalone: true,
  imports: [CustomModule], //ここに追記する
  template: `<router-outlet></router-outlet>`,
  styleUrls: ['./app.component.css'],
})
export class AppComponent{
}
```

Angular17からは次のような**単一でのテンプレートタグ(`<hoge />`)が有効となっている**ので、記述もずっとスリムになります。

```ts:app.component.ts
import { Component } from '@angular/core';
import { CustomModule } from '../custom.module' //先程設定したモジュールが内包される

@Component({
  selector: 'app-root', //main.tsに紐づいている
  standalone: true,
  imports: [CustomModule], //ここに追記する
  template: `<router-outlet />`, //Angular17から可能な記述
  styleUrls: ['./app.component.css'],
})
export class AppComponent{
}
```

### トップページ
トップページは以下ようになります。ここではTodosコンポーネントへの紐づけが必要なので、Todosコンポーネントをインポートしておくようにしておきます。これも、今までだと全てモジュールから制御しないといけませんでしたが、**スタンドアロンコンポーネントによって、独立してコンポーネントを呼び出すことができる**ようになっています。したがって、コンポーネント同士の従属関係が非常に明白です。

また、この画面では新規登録画面への遷移用ボタンが実装されています。なので、ここも本来はRouterModuleが必要となります（これをインポートしておかないと画面遷移できません）。

```ts:todo.component.ts
import { Component } from '@angular/core';
import { CustomModule } from '../custom.module';
import { TodosComponent } from '../todo-components/todos.component' //Todo制御用のコンポーネント

@Component({
  standalone: true,
  imports: [CustomModule,TodosComponent], //必要なモジュールとコンポーネント
  template:`
    <h2>TODO一覧</h2>
      <app-todos></app-todos><!-- Todosコンポーネントに紐づけ -->
    <a routerLink="./add"><button>新規登録</button></a>
  `,
  styleUrls: ['./todo.component.css']
})
export class TodoComponent{}
```

### 型の定義ファイル
型の定義ファイルは以下のようになっています。ただし、Angularの仕様の都合上、メソッド制御の一部においてanyも使用しています。また、サービス注入用のトークンもここで発行しておきます。

※TSの型定義はバージョンで随時変わっています。

```ts:types_todo.ts
import { InjectionToken } from '@angular/core'
export const token = new InjectionToken<any>("token")

import { Observable } from 'rxjs'
//ベースとなる型（|でor条件を作成できる）
export type Status = 'waiting'|'working'|'completed'|'pending'

export type Data = {
  id?: number
  title?: string,
  description?: string,
  str_status?: Status, //上で定義した任意のパラメータ
}

//アレンジされた型
//任意のオブジェクトから独自の型定義を作成したい場合（ここではDataオブジェクトのうち、title、description、sutatusの3つのプロパティを用いたParams型を作成している）
export type Params = Pick<Data,'title'|'description'|'str_status'>
export interface Todos{
  todos: DATA[]
}

//注入されたサービス用
export interface Services{
  sub: Observable<any>,
  addTodo: (data:Data)=> void,
  updTodo: (data:Data)=> void,
  delTodo:(data:Data)=> void,
  todoReducer: (mode:string,data:Data)=> void 
}
```

## Todo一覧制御
Todo一覧の作成はtodosコンポーネントによって制御されます。また、このコンポーネントは各Todoを制御する子コンポーネントと親子関係に当たるのですが、これもスタンドアロンコンポーネントによって、コンポーネントの紐付けを簡明にしています（Vue、React、Svelteのように関係がわかりやすくなっている）。一方、このTodo一覧はトップページのtodoコンポーネントの被従属関係に当たるので、セレクタによる従属指定が必須です。

また、サービスから呼び出されるTodoデータはトークンを用いて取得し、ngOninitメソッド上で処理できます。そして、サービス上で登録、修正、削除の各種処理を実行することで、更新後のデータを検知し、それを購読できるようになっています。

```ts:todos.component.ts
import { Component, OnInit,inject } from '@angular/core'
import { CustomModule } from '../modules/custom.module'
import {TodoItemComponent } from './todo-item.component' //子コンポーネント　
import { Todos,Services,token } from '../todo-types/types_todo'
@Component({
  selector: 'app-todos', //従属する親コンポーネントを紐づけるために必須
  standalone: true,
  imports: [CustomModule,TodoItemComponent],
  template: `
            <ul>
              <app-todo-item
                *ngFor="let todo of todos"
                [todo]="todo"
              ></app-todo-item>
            </ul>
            `
  ,
  styleUrls: ['../todo-components/todos.component.css']
})
export class TodosComponent implements OnInit{
  svs = inject<Services>(token)
  todos: Todos
  ngOnInit(){
    this.svs.sub.subscribe(v => this.todos = v) //更新を検知した値を購読
  }
}
```

※Angular17からはビルトインコントロールフローによる `@for`を用いた記述が可能です。その場合、**trackプロパティが必須**となります（Vueの:keyのようなもの）。ループ対象が存在しない場合は`@empty`以下に結果を返します。

```ts:todos.component.ts
@Component({
  selector: 'app-todos', //従属する親コンポーネントを紐づけるために必須
  standalone: true,
  imports: [CustomModule,TodoItemComponent],
  template: `
            <ul>
              @for(todo of todos; track todo.id){
                <app-todo-item [todo]="todo" />
              }@empty{
                 <p>データがありません</p>
              }
            </ul>
            `
  ,
  styleUrls: ['../todo-components/todos.component.css']
})
```

## 各Todoを制御する
各Todoの制御は以下のようになっています。ここは親コンポーネントから値を受け取っているので、サービスの購読をしていません。また、ここもtodosコンポーネントの子コンポーネントなので、セレクタ指定が必須となります。

```ts:todo-item.component.ts
import { Component, Input,inject } from '@angular/core';
import { Services,Data,token } from '../todo-types/types_todo'
import {Router } from '@angular/router'
import { CustomModule } from '../modules/custom.module'

@Component({
  selector: 'app-todo-item',
  standalone: true,
  imports: [CustomModule],
  templateUrl: './todo-item.component.html',
  styleUrls: ['./todo-item.component.css']
})
export class TodoItemComponent{
  svs = inject<Services>(token)
  @Input() todo: Data
  constructor(
    private rtr: Router,
  ) {}
  postcall(mode,data){
    this.svs.todoReducer(mode,data)
  }
  getId(mode,id){
    this.rtr.navigate([`${mode}/${id}`])
  }
}
```

```html:todo-item.component.html
<!-- 各Todo子コンポーネント -->
<div class="card">
  <div>
    <a routerLink="/" (click)="getId('detail',todo.id)">
      <span class="title">{{todo.title}}</span>
    </a>
    <span class="status">{{ todo.str_status }}</span>
  </div>
  <div class="action">
    <a routerLink="/" (click)="getId('edit',todo.id)"><button>修正</button></a>
    <button (click)="postcall('DEL_TODO',todo.id)">削除</button>
  </div>
</div>
```

## サービスファイル
サービスファイルはそこまで変化はないと思います。今回のケースはObservableが便利です。ただ、削除はspliceで制御しないと更新を検知できない（filterは非破壊的なので、制御しても変数の値は更新されない）ので注意しましょう。

※今回はsubscribeしているだけなので問題は発生しませんが、もしpipeで中継処理を行う場合はObservableだと期待どおりの動きをしなくなる場合があります。その場合はBehavior Subjectで定期的にnextメソッドを使用するといいでしょう。

```ts:todo-service.ts
import { Injectable } from '@angular/core'
import { Observable } from 'rxjs'
import { Data } from '../todo-types/types_todo'

const todos = <any>[]

@Injectable({
  providedIn: 'root'
})


export class TodoService{
  sub: Observable<any>
  constructor(){
    this.sub = new Observable((ob)=>{
      ob.next(todos)
    })
  }
  //データ登録
  addTodo = (data)=>{
    todos.push(data)
  }
  //データ更新
  updTodo = (data)=>{
    const idx = todos.findIndex((item)=>item.id == data.id)
    todos[idx] = {...todos[idx],...data}
  }
  //データ削除
  delTodo = (id)=>{
    const idx = todos.findIndex((item)=>item.id == id)
    todos.splice(idx,1)
  }

  todoReducer =(mode,data)=>{
    switch(mode){
      case "ADD_TODO": this.addTodo(data)
      break
      case "UPD_TODO": this.updTodo(data)
      break
      case "DEL_TODO": this.delTodo(data)
      break
    }
  }
}
```

## CRUDを制御する（リアクティブフォーム）
では、ここからAngular13で試験的に実装され、14で晴れて正規の実装となった**リアクティブフォーム**の説明に入っていきます。このリアクティブフォームは簡潔に言えば**フォーム構造を事前に設計する機能**で、Angular上でフォーム制御をわかりやすくグループ化し、事前に値を設定したり、入力後の値をグループ毎に取得できたりします。また、13だとTypeScript制御でかなり困った不備も発生していましたが、14ではそれが改善されています。

ここで、スタンドアロンコンポーネントの際の記述に注意点があります。スタンドアロンコンポーネントの場合、ReactiveFormModuleというモジュールを取得しておきます（今回の例ではカスタムコンポーネントに内包済）。また、FormGroup、FormControlの各種オブジェクトをインポートしておく必要があります（スタンドアロンコンポーネントの場合はFormsModuleはCommonModuleに含まれているので、指定不要）。

ちなみに、この各種CRUD用コンポーネントはそれぞれ独立している（従属関係を持たない）ので、セレクタ指定は不要です（あってもなくても動きます）。

それから登録画面はinjectでサービスを受け取っていますが、使用しているのがsendDataメソッドだけなのでOnInitは使用しなくても大丈夫です。

### Todoの新規登録

```ts:add-todo.component.ts
import { Component,inject } from '@angular/core';
import { CustomModule } from '../modules/custom.module';
import {Router } from '@angular/router'
import { Data,Todos,Status,Services,token } from '../todo-types/types_todo'
import {FormGroup,FormControl} from '@angular/forms'

@Component({
  standalone: true,
  imports: [CustomModule],
  templateUrl: './add-todo.component.html',
  styleUrls: ['./add-todo.component.css']
})
export class AddTodoComponent{
  svs = inject<Services>(token)
  data: Data
  constructor(
    private rtr:Router,
  ) {}
  //ダイレクトに変数を代入することもできる
  date = new Date()
  setid = this.date.getTime()
  todoForm = new FormGroup({
    id: new FormControl<number>(this.setid),
    title: new FormControl<string>(''),
    description: new FormControl<string>(''),
    str_status: new FormControl<Status>('waiting')
  })
  sendData(mode:string){
    const data = this.todoForm.value
    this.svs.todoReducer(mode,data)
    this.rtr.navigate(['/'])
  }
}
```

### テンプレートの記述
テンプレートも今までのような双方向バインディングはお役御免となります。その代わりに**formGroup**というプロパティと**formControlName**というプロパティが制御に必須となり、formControlNameプロパティ上の名称がformGroupに記載されたグループ名の所属フォーム名となります。そして、これらは先程の**コンポーネントファイルで制御したフォーム設計に合致させておく**必要があります。

具体的にいえば、フォームのグループ名todoFormに属する各種プロパティがtitle、description、そしてstr_statusとなります。

ただし、IDのようにreadonlyにする場合はプロパティバインディングを用います。

※何故かはわかりませんが、コンポーネントはパスカルケース(FormHoge)なのに対し、プロパティはキャメルケース(formHoge)で記述するようになっています。注意しましょう。

```html:add-todo.component.html
<h2>TODOの作成</h2>
<form [formGroup]="todoForm" (ngSubmit)="sendData('ADD_TODO')">
<div>
  <label for="title"> ID</label>
  <input type="text" id="title" [value]="setid" readonly />
</div>
<div>
  <label for="title"> タイトル</label>
  <input type="text" id="title" formControlName="title" />
</div>
<div>
  <label for="description"></label>
  <textarea id="description" formControlName="description" ></textarea>
  </div>
  <div>
  <label for="status">ステータス</label>
  <select id="status"  formControlName="str_status">
    <option value="waiting">waiting</option>
    <option value="working">working</option>
    <option value="completed">completed</option>
    <option value="pending">pending</option>
  </select>
</div>
<button type="submit">作成する</button>
</form>
```

### 入力したフォームの値を転送する
フォームの値転送はformタグに埋め込まれたngSubmitディレクティブに記述されたsendDataに転送されます。sendDataは以下のように記述しており、フォームグループのプロトタイプにあるvalueプロパティに格納されます。あとはこれを制御用のサービスファイルに送るだけです。

```ts:add-todo.component.ts
  sendData(mode){
    const data = this.todoForm.value //フォームに入力された値
    const id = this.setid //idの値
     this.svs.todoReducer(mode,{...data,id}) //サービス上のメソッドへ転送
    this.rtr.navigate(['/']) //TOP画面へ遷移
  }
```

### 修正画面
では、修正画面の場合はどうやって制御するのでしょうか。修正画面の場合は、該当の値をフォームに一旦返さないといけません、そこで、役立つメソッドが**patchValue**で、対象フォームのプロパティに対して、更新処理を行ってくれます。

※setValueというメソッドもありますが、こちらは全プロパティを設定する必要があります。今回はidは一旦、テンプレートに返さなくてもよいので、却ってsetValueを使用すると型不一致のエラーとなります。

また、今回は一旦サービスからデータを受け取る必要があるので、ngOnintを受け皿としてそこでpatchValueをかけています。その際の型はid以外のプロパティを抽出したParamsとなります。

```ts:edit.component.ts
import { Component,OnInit,inject } from '@angular/core'; //OnInitを忘れない
import { CustomModule } from '../modules/custom.module';
import { ActivatedRoute,Router } from '@angular/router'
import { Data,Params,Status,Todos,Services,token } from '../todo-types/types_todo'
import { FormGroup,FormControl} from '@angular/forms'

@Component({
  standalone: true,
  imports: [CustomModule],
  templateUrl: './edit-todo.component.html',
  styleUrls: ['./edit-todo.component.css']
})
export class EditTodoComponent implements OnInit{
  svs = inject<Services>(token)
  todos: Todos
  data: Data
  id: number
  todoForm = new FormGroup({
    id: new FormControl<number>(null),
    title: new FormControl<string>(''),
    description: new FormControl<string>(''),
    str_status: new FormControl<Status>('waiting')
  })
  constructor(
    private rt: ActivatedRoute,
    private rtr:Router,
  ) {}
  ngOnInit(){
    this.svs.sub.subscribe((v)=>{ this.todos = v})
    this.id = Number(this.rt.snapshot.params.id)
    this.data = this.todos.find((item:Data)=>item.id === this.id)
    this.todoForm.patchValue(<Params>this.data)
  }
  //転送されたデータ
  sendData(mode: string){
    this.todoForm.patchValue({
      id: this.data.id
    })
    const data = this.todoForm.value
    this.svs.todoReducer(mode,data)
    this.rtr.navigate(['/'])
  }
  backto(){
    this.rtr.navigate(['/'])
  }
}
```

そして値の更新の際に、忘れずにidに対してもpatchValueをかけておきましょう。valueで入力されたフォームの値を取得するのはidを返してからの作業になります。

ちなみに、TypeScriptの利点として、値を代入しなくても型を定義できるので、次のような記述方法でも大丈夫です。フォームに返す値を逐一確認したい場合は前述の方法で、そこまでのフォームが存在しない場合は以下の方法でいいでしょう。

```ts
    this.todoForm.patchValue(<Params>this.data)
```

### 編集画面のテンプレート
編集用のテンプレートは登録用とそこまで変わりません。編集用だからといっても、フォームの値はリアクティブフォームが制御してくれるので、プロパティバインディングも必要なのはformGroupだけで済みます。

```html:edit-component.html
<h2>TODOの編集</h2>
<form [formGroup]="todoForm" (ngSubmit)="sendData('UPD_TODO')">
<div>
  <label for="title"> タイトル</label>
  <input type="text" id="title" formControlName="title" />
</div>
<div>
  <label for="description"></label>
  <textarea id="description" formControlName="description" ></textarea>
  </div>
  <div>
  <label for="status">ステータス</label>
  <select id="status"  formControlName="str_status">
    <option value="waiting">waiting</option>
    <option value="working">working</option>
    <option value="completed">completed</option>
    <option value="pending">pending</option>
  </select>
</div>
<button type="submit">更新する</button>
</form>
<button (click)="backto()">戻る</button>
```

## 詳細画面（属性ディレクティブとカスタムパイプ）
詳細画面制御のコンポーネントはフォーム制御は不要なので、記述もシンプルです。ただし、ここでは属性ディレクティブとカスタムパイプを使用しているのですが、それぞれスタンドアロン宣言が必要になります。

そして、これらも同様にimportするだけで使用可能になります。

```ts:detail-todo.component.ts
import { Component,OnInit,inject } from '@angular/core';
import { CustomModule } from '../custom.module';
import {ActivatedRoute,Router } from '@angular/router'
import { Data,Todos,Services,token } from '../todo-types/types_todo'
import { TableDirective } from '../directives/table.directive' //属性ディレクティブ
import { SetBrPipe } from '../pipes/set.br.pipe' //カスタムパイプ

@Component({
  standalone: true,
  imports: [CustomModule,SetBrPipe,TableDirective], //使用するコンポーネント類を記述する
  templateUrl: './detail-todo.component.html',
  styleUrls: ['./detail-todo.component.css']
})
export class DetailTodoComponent implement OnInit{
  svs = inject<Services>(token)
  todos: Todos
  id: number
  data: Data
  constructor(
    private rt: ActivatedRoute,
    private rtr:Router,
  ) {}
  ngOnInit(){
    this.svs.sub.subscribe((v)=>{ this.todos = v})
    const id = Number(this.rt.snapshot.params.id)
    this.data = this.todos.find((item)=>item.id === id)
  }
  //ルートコンポーネントに戻る
  backto(){
    this.rtr.navigate(['/'])
  }
}
```

### 詳細画面のテンプレート
詳細画面はフォームとは異なり、ダイレクトに変数を返しています。

テーブルタグには属性ディレクティブ（bs_tableが任意のプロパティ）でBootstrapを制御しています。また、テキストエリアの改行が反映されるように、カスタムパイプで改行コードを置換した上でプロパティバインディングの[innerHTML]を使用しています。

```html:detail-todo.component.html
	<h2>TODOの詳細</h2>
  <ng-template *ngIf="data.title ==''; then t else f"></ng-template>
  <ng-template #t>
	<div>ID:{{ data.id }}のTODOが見つかりませんでした</div> 
	</ng-template>
  <ng-template #f>
    <table bs_table>
      <thead>
        <tr>
        <th>タイトル</th>
        <th>説明</th>
        <th>ステータス</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>{{ data.title }}</td>
          <td [innerHTML]="data.description | setbr"></td>
          <td>{{ data.str_status }}</td>
        </tr>
      </tbody>
    </table>
  </ng-template>
  <button (click)="backto()">戻る</button>
``` 

### スタンドアロンコンポーネントで属性ディレクティブを使用する
属性ディレクティブは、スタンドアロンコンポーネント上ではスタンドアロン化したものしか利用できません。ですが、スタンドアロン化することで、上記のようにコンポーネント上でimportするだけで使用できるので、スタンドアロンの方がずっと利便性が高いです。

※ちなみに、スタンドアロンコンポーネントはBootstrap標準実装です。

```ts:table.directive.ts
import { Directive,HostBinding } from '@angular/core';

@Directive({
  selector: '[bs_table]', //タグに付与するプロパティの名称
  standalone: true
})
export class TableDirective {
  @HostBinding("class") //class属性にバインド
  elementClass = "table table-bordered border-primary" //使用したいBootstrapのプロパティ
}
```

#### 属性ディレクティブ作成のコマンド
属性ディレクティブを使用するときも同様にstandalone宣言が必要です。dはディレクティブの省略形です。また--flatを付与しないと余分なdirectivesフォルダが作成されることになります。

```
# cd directives
# ng g d 任意のディレクティブ名 --flat --standalone 
```

### スタンドアロンコンポーネントでカスタムパイプを使用する
スタンドアロンコンポーネントでは、RxJSにおいてかなり大きな挙動変化があります。それはObservableがリアルタイムに起動しなくなるというもので、普通にsubscribeするだけなら問題ないのですが、中継処理用のpipeがうまく機能しません（読み込み時しか起動しない。BehaviorSubjectなら問題ない）。

その代替案としてカスタムパイプの使用が推奨されています。スタンドアロンコンポーネントはカスタムパイプに対してもスタンドアロン化で使用し、しかもコンポーネントと同じようにインポートするだけです。

ここではテキストエリアに入力された値に対し、改行コードをbrタグに変換する処理をカスタムパイプ化しています。

```ts:set.br.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
  name: 'setbr',
  standalone: true
})
export class SetBrPipe implements PipeTransform {
  transform( str : string){
    return str.replace(/\r?\n/g, '<br>') //テキストエリアの改行コードをbrタグに変換
  }
}
```

#### カスタムパイプ作成のコマンド
カスタムパイプを作成するときも同様にstandalone宣言が必要です。pはカスタムパイプの省略形です。また--flatを付与しないと、余分なpipesフォルダが作成されることになります。

```
# cd pipes
# ng g p 任意のパイプ名 --flat --standalone 
```

### 属性ディレクティブやカスタムパイプをライブラリに組み込む
前述したようにライブラリはディレクティブ、パイプ類も組み込むことができるので、利用頻度の高いディレクティブやパイプは一元管理した方がいい場合もあります。

```ts:custom.module.ts
import {NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {RouterModule,Router } from '@angular/router'
import {ReactiveFormsModule } from '@angular/forms'
import {ButtonDirective} from '../directives/button.directive' //ボタンデザイン制御のディレクティブ

@NgModule({
	imports:[
    CommonModule,
    RouterModule,
    ReactiveFormsModule,
    ButtonDirective, //追加
	],
  exports: [
    CommonModule,
    RouterModule,
    ReactiveFormsModule,
    ButtonDirective, //追加
  ],
})
export class CustomModule {}
```

あとは使用したいコンポーネントでインポートするだけです。

## 補足
### 遅延ロード
AngularでNextやNuxtのような遅延ロード（リンクされた時だけ読み込みを行うローディング方法）もできます。それには以下のように記述するだけです。コールバック関数を記述するだけでも大丈夫です。修正画面と詳細画面の制御コンポーネントを遅延ロードに書き換えてみました。

**loadComponent: ()=> コンポーネントを呼び出す式またはコールバック関数**

```ts:app-todo.ts
import { Routes } from "@angular/router";
//各種コンポーネント
import {TodoComponent} from '../todo-views/todo.component';
import {AddTodoComponent} from '../todo-views/add-todo.component';
const lazy_EditTodoComponent = import('../todo-views/edit-todo.component').then(c => c.EditTodoComponent)
const lazy_DetailTodoComponent = import('../todo-views/detail-todo.component').then(c => c.DetailTodoComponent)
export const Route: Routes = [
	{path: '',component: TodoComponent},
	{path: 'add',component: AddTodoComponent},
	{path: 'edit/:id',
    loadComponent: ()=> lazy_EditTodoComponent, //コールバック関数にしてもよい
    pathMatch: 'full'},
	{path: 'detail/:id',
    loadComponent: ()=> lazy_DetailTodoComponent, //コールバック関数にしてもよい
    pathMatch: 'full'},
]
```

一般的には以下のように式を記述することが多いですが、これだと使用コンポーネントが散逸してしまって、個人的に分かりづらいと思ったからです。

```ts:app-todo.ts
loadComponent: import('../todo-views/detail-todo.component').then(c => c.DetailTodoComponent),
```

※loadChildrenを用いるとルーティングファイルごと子コンポーネント化させて、遅延ロードすることもできたりします。その際はパスが一致していることが条件となります。

# Signal
Angular16からはもう一つの革新的な機能としてSignalが登場しました。このsignalを活用すれば、プリミティブな変数やリアクティブ処理に対して単一コンポーネントでも柔軟に対応できるようになっており、ストレージ管理メインのRxJSと併用すれば、効率よく処理が可能となります。

ちなみにSignalといえばSolidJSで大きく注目されるようになった機能ですが、Solidのものとは大きく働きが異なり、どちらかというとVue2以前にあったsetメソッドに近いです。

## signalでリスト件数を表示させる
signalを用いてapp.todos.component.tsに対し、リスト件数を表示させてみます。この件数表示はRxJSから購読したデータを元に、個数を再計算するものですが、今までだとpipeなどの処理が必要でした。ですが、signalによって、単一のコンポーネント上で再計算処理が可能となっています。

signalで返された変数はコールバック関数となっています。したがって、テンプレートで表示場合は関数のまま記述する必要があります。

※通常の変数との差違がわかりづらい場合は$などの約物を付与しておくといいでしょう。

### set
setメソッドは、値を設定するメソッドです。それこそVue2以前にあったVue.setのようなもので、signalで監視対象とした値に対し、ダイレクトに値を代入したい場合に用いると効果的です。今回はtodosの件数を代入しています。

```ts:todos.component.ts
import { Component,OnInit,inject,signal } from '@angular/core';
import {TodoitemComponent } from '../todoitem/todoitem.component';
import { Todos,Services,token } from '../../types/types';
@Component({
  selector: 'app-todolist',
  imports: [TodoitemComponent],
  template: `
			<h2>一覧リスト({{ $cnt() }}件)</h2>
			@for(todo of todos; track todo.id){
			<h4>{{ todo?.id }}</h4>
              <app-todoitem [todo]="todo"
              />
			}@empty{
				<p>リストに値はありません</p>
			}
			`
  ,
  styleUrl: './todolist.component.css'
})
export class TodolistComponent implements OnInit{
  svs = inject<Services>(token);
  todos = [];
  $cnt = signal(0);
  ngOnInit(){
    this.svs.sub.subscribe(v => this.todos = v) //更新を検知した値を購読
	this.$cnt.set(this.todos.length); //TODOの件数を取得し、setで代入する

  }
}
```

signalで合計値cntをリアクティブにし、RxJSのObservableで取得したtodosの件数をsetメソッドで設定しておくだけです。

### update
setに対し、updateというメソッドもあります。こっちは元の値を展開して用いるので、更になにか計算や加工したい場合に使います。今回の場合だと、0件と1件以上で表示を切り替えたい場合、また、カウントをリセットしたくない場合などに用いると効果的です。

```ts:todos.component.ts
import { Component,OnInit,inject,signal } from '@angular/core';
import {TodoitemComponent } from '../todoitem/todoitem.component';
import { Todos,Services,token } from '../../types/types';
@Component({
  selector: 'app-todolist',
  imports: [TodoitemComponent],
  template: `
			<h2>一覧リスト({{ mes }})</h2>
			@for(todo of todos; track todo.id){
			<h4>{{ todo?.id }}</h4>
              <app-todoitem [todo]="todo"
              />
			}@empty{
				<p>リストに値はありません</p>
			}
			`
  ,
  styleUrl: './todolist.component.css'
})
export class TodolistComponent implements OnInit{
  svs = inject<Services>(token);
  todos = [];
  $cnt = signal(0);
  mes = ""
  ngOnInit(){
    this.svs.sub.subscribe(v => this.todos = v) //更新を検知した値を購読
	if(this.todos.length > 0){
		this.$cnt.update((c)=> c + 1 )
		this.mes =`(${this.$cnt()}回更新しました)`; //1回以上の場合のみメッセージを表示させる
	} 
  }
}
```

### computed
**computed**はVueの算出プロパティのように、signalで監視対象とした値に対し、setまたはupdateによる更新を検知し、再計算処理を行うものです。今回のように、メッセージを柔軟に編集したい場合は以下の記述が便利です。**computedで制御した場合は戻り値も関数になっている**ので注意。

```ts:todos.component.ts
import { Component,OnInit,inject,signal,computed } from '@angular/core'; //computedを追記
import {TodoitemComponent } from '../todoitem/todoitem.component';
import { Todos,Services,token } from '../../types/types';
@Component({
  selector: 'app-todolist',
  imports: [TodoitemComponent],
  template: `
			<h2>一覧リスト{{ $mes() }}</h2>
			@for(todo of todos; track todo.id){
			<h4>{{ todo?.id }}</h4>
              <app-todoitem [todo]="todo"
              />
			}@empty{
				<p>リストに値はありません</p>
			}
			`
  ,
  styleUrl: './todolist.component.css'
})
export class TodolistComponent implements OnInit{
  svs = inject<Services>(token);
  todos = [];
  $cnt = signal(0);
  $mes = computed(()=>`(${this.$cnt()}件)`); //件数の変更に対処する
  ngOnInit(){
    this.svs.sub.subscribe(v => this.todos = v) //更新を検知した値を購読
	if(this.todos.length > 0){
		this.$cnt.update((c)=> c + 1 )
	} 
  }
}
```

### effect
computedに対して、**effect**というものもあります。これは`constructor上で用いる`もので、VueのライフサイクルフックonMountedのように、起動時に初期値や体裁を設定したい時に用いたりすると効果的です(起動後も監視された値に反応します)。また、**effectは戻り値がありません**。したがって先ほどと違い、mesはプリミティブな変数となっています。

```ts:todos.component.ts
import { Component,OnInit,inject,signal,effect } from '@angular/core';
import {TodoitemComponent } from '../todoitem/todoitem.component';
import { Todos,Services,token } from '../../types/types';
@Component({
  selector: 'app-todolist',
  imports: [TodoitemComponent],
  template: `
			<h2>一覧リスト{{ mes }}</h2>
			@for(todo of todos; track todo.id){
			<h4>{{ todo?.id }}</h4>
              <app-todoitem [todo]="todo"
              />
			}@empty{
				<p>リストに値はありません</p>
			}
			`
  ,
  styleUrl: './todolist.component.css'
})
export class TodolistComponent implements OnInit{
  svs = inject<Services>(token);
  todos = [];
  $cnt = signal(0);
  mes = "";
  //コンストラクタを設定し、中で用いる
  constructor(){
	effect(()=>{ this.mes = `(${this.$cnt()}件)`; }); //表示メッセージの体裁を設定
  }
  ngOnInit(){
    this.svs.sub.subscribe(v => this.todos = v); //更新を検知した値を購読
	this.$cnt.set(this.todos.length ); //件数の設定
  }
}
```