Angularを触っていて、同じコンポーネントでサービスを切り替えたい時ってありますよね?
一般ユーザー向けと管理者用で画面は一緒だけど、中身は変えたいとか。
サービスにisAdmin
とか持たせちゃいます?
色んな関数でif(isAdmin)
とかやっちゃいます?
実はサービスを切り替えられるんです。
Angularプロジェクトを作成する
ng new service-test --routing
画面の追加
コンポーネントの追加
- サービスからデータを取得して、表示するようにします
src/app/page1/page1.component.ts
import { Component } from '@angular/core';
import { Page1Service } from './page1.service';
@Component({
selector: 'app-page1',
template: `
<h1>type: {{type}}</h1>
<p>{{data}}</p>
`,
})
export class Page1Component {
constructor(private service: Page1Service) { }
get type() { return this.service.type; }
get data() { return this.service.getData(); }
}
サービスの追加
- 一般ユーザー向けサービスを作成します
src/app/page1/page1.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class Page1Service {
type = 'general';
getData() {
return 'これは一般ユーザー向けです';
}
}
- 次に管理者用サービスを作成します
- Page1Serviceを継承して作成します(継承しなくても変数名やメソッド名が一致していれば問題ないと思います)
- abstractクラス作るのが一番安全だと思います。
src/app/page1/page1-for-admin.service.ts
import { Injectable } from '@angular/core';
import { Page1Service } from './page1.service';
@Injectable()
export class Page1ForAdminService extends Page1Service {
type = 'admin';
getData() {
return 'これは管理者ユーザー向けです';
}
}
モジュールの定義
ここが今回の肝です!!!
- providersでuseFactoryを使って、指定のサービスを別のサービスに差し替えます
- これによりComponentでインジェクションするServiceが差し変わります
src/app/page1/page1.module.ts
import { NgModule } from '@angular/core';
import { Page1Component } from './page1.component';
import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router';
import { Page1Service } from './page1.service';
import { Page1ForAdminService } from './page1-for-admin.service';
import { Page1RoutingModule } from './page1-routing.module';
@NgModule({
declarations: [Page1Component],
imports: [Page1RoutingModule],
providers: [
{
// 置換対象のサービス
provide: Page1Service,
// 置換
useFactory: (route: ActivatedRoute) => {
const getData = (snapshot: ActivatedRouteSnapshot) => snapshot.firstChild ? getData(snapshot.firstChild) : snapshot.data;
const isAdmin = getData(route.snapshot)?.isAdmin;
if (isAdmin) {
return new Page1ForAdminService();
}
return new Page1Service();
},
// useFactoryでインジェクションするサービス
deps: [ActivatedRoute],
}
]
})
export class Page1Module { }
isAdminは後述するルーティングファイルで設定するdata内から取得します
ルーティングの設定
- Page1Module内のルーティングを定義します
src/app/page1/page1-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { Page1Component } from './page1.component';
const routes: Routes = [
// 一般ユーザー向け
{ path: '', component: Page1Component },
// 管理者向け
{ path: 'admin', component: Page1Component, data: { isAdmin: true } },
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class Page1RoutingModule { }
⇒管理者向けの方はdataにisAdminを設定します
-
/page1
アクセス時にPage1Moduleを遅延ロードするようにします
src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{ path: 'page1', loadChildren: () => import('./page1/page1.module').then(m => m.Page1Module) },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
動作確認
- http://localhost:4200/page1にアクセスします
最後に
こんな感じで簡単?にコンポーネントを共通化しつつ、サービスだけ差し替えることができます。
こうすることで、権限ごとにif文が増えたりとかしなくなるので、割とサービスがシンプルになります。
ファイル数は増えるし、Moduleが複雑になるというデメリットもあるので、使いどころの見極めは必要ですね。