Angular+SpringBootでWebアプリを実装することが多く、どちらもDIコンテナを備えてますが、
・Angular -> 実クラスを指定してDI
・SpringBoot -> Interfaceを指定してDI
として通常実装しています。
ただ、依存性の注入。という意味では利用側はInterfaceだけを意識して、実装クラスの解決をDIコンテナに任せたく、AngularでもInterfaceを指定してDIができないものかと思っていました。
Angular界隈では常識なのかもしれませんが、私は知らなかったのでChatGPTに聞いてみました。
(今更流行りにのって。。。)
ChatGPTとの会話
いやこいつ天才か!?
すごすぎるだろ。。。知りたかったことが2,3分で知れました。。。
ただし、ChatGPTは嘘を教えることもあるので、実際にやってみます
実装
インタフェース
export interface ServiceInterface {
show: () => void;
}
実装クラス(prod)
import { Injectable } from '@angular/core';
import { ServiceInterface } from './service.interface';
@Injectable({
providedIn: 'root',
})
export class ProdService implements ServiceInterface {
constructor() {}
show() {
console.log('This is ProdService.');
}
}
実装クラス(test)
import { Injectable } from '@angular/core';
import { ServiceInterface } from './service.interface';
@Injectable({
providedIn: 'root',
})
export class TestService implements ServiceInterface {
constructor() {}
show() {
console.log('This is TestService.');
}
}
よし、これであとはapp.module.tsのprovidersにfactoryを設定して、利用クラス側でDIすればOK。
prodで動かした場合はprod.service.ts、それ以外はtest.service.tsがDIされて動く想定。
provideにinterfaceは指定できないな。。。ChatGPTに嘘つかれたか。。。
調べてみた感じやり口としては二通りありそう。
1.Interfaceを継承するabstract classを実装して、abstract classをprovideに指定する
2.Interfaceに紐づくInjectionTokenを生成し、それをprovideに指定する
好みですが、2の方法で対処してみます。
import { environment } from 'src/environments/environment';
import { ProdService } from './service/prod.service';
import { TestService } from './service/test.service';
import { ServiceInterface } from './service/service.interface';
// environmentでインスタンス化するクラスを変えていく
const myFactory = () => {
return environment.production ? new ProdService() : new TestService();
};
// ServiceInterfaceに紐づくInjectionTokenを生成する
export const SERVICE_TOKEN = new InjectionToken<ServiceInterface>(
'service.interface'
);
@NgModule({
declarations: [AppComponent, CustomTooltipComponent, CustomTooltipDirective],
imports: [BrowserModule, AppRoutingModule, OverlayModule],
providers: [
{
// 生成したInjectionTokenをprovideに指定する
provide: SERVICE_TOKEN,
useFactory: myFactory,
},
],
bootstrap: [AppComponent],
})
export class AppModule {}
あとは利用クラスを書くのみ
import { Component, Inject } from '@angular/core';
import { SERVICE_TOKEN } from './app.module';
import { ServiceInterface } from './service/service.interface';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
title = 'angular-sample';
// DIする際に@InjectでInjectionTokenを指定する
constructor(@Inject(SERVICE_TOKEN) sv: ServiceInterface) {
sv.show();
}
}
@Inject
でInjectionToken指定するのが、個人的には微妙ですね。。。
なんとかならんのかな。その場合はabstract class方式で実装するのが良いのでしょうか。
いざ、稼働確認
ng serve --configutaion production
ng serve
おおおおおおおおおおおおおおおおおおおお、すげええええええ!!
紆余曲折ありましたが、やりたいことはできました。ChatGPTすごい!!!
使ってみて思いましたが、AIに仕事奪われるとか、もうおれらの価値なんて、、、、ってネガティブな感じではなく、ChatGPTを利用しまくってもっともっと成長するぜって気になりました。
とにかく、情報の収集効率がほんとに良くて、案Aと案Bどっちが良いかな?みたいな相談にも使えたりするのでChatGPTからの回答自体が非常に勉強になることが多いです。
ただ、今回みたいにガセ情報もあるので、もう一段踏み込んで考えたり、ググって裏どりするのが重要ですね。
また、相談系もこちらの誘導尋問的なこともできるし、ChatGPTが忖度した回答してくるときもあるので、案Aと案Bのメリットデメリットを聞くとか、公平性を意識した質問が良いんですかね。
以上です。