技術情報ブログ
Angular
2021.11.17

Angularでマルチテナント対応(2)

 

はじめに

前回に引き続き、Angular でのマルチテナント対応についてご紹介致します。

前編では、URL からテナントを判別し、対応したテナント情報を取得するまでを記載致しました。

本記事では、取得したテナント情報をもとに、tailwindcss を用いたテーマカラーの変更、HTTP ヘッダへのテナント ID の付与を記載致します。

Angular でサブドメインを用いたマルチテナント対応を行いたい方に向けた記事となります。

 

tailwindcss とは

ユーティリティファーストな CSS フレームワークです。

ユーティリティファーストとは

html の class 属性に CSS プロパティと似たクラス名(ユーティリティクラス)を指定して、デザインする手法です。

例: class 属性にp-4を指定すると、padding: 1rem が実際に指定されます。

 

環境

  • Node: 14.17.0

  • npm: 6.14.13

  • Angular: 12.1.1

  • TypeScript: 4.3.5

  • tailwindcss: 2.2.4

 

テナント情報と tailwindcss を用いたテーマカラーの変更

tailwindcssでは設定を拡張し、自身で指定した名前と値の CSS クラスを設定できます。

設定を拡張する際、値に CSS カスタムプロパティを使用するよう設定し、Angular 側で取得したテナントのカラー情報から CSS カスタムプロパティを定義します。
CSS カスタムプロパティを通して、値を動的に設定する事により、テーマカラーを変更する事ができます。

 

tailwindcss の設定例

下記では primary をクラス名として、CSS カスタムプロパティの”–primary-theme-color”値を使用するよう設定を拡張しています。 詳細についてはCustomizing Colorsをご覧下さい。

module.exports = {
  theme: {
    extend: {
      colors: {
        primary: "var(--primary-theme-color)",
      },
    },
  },
  important: true,
};

 

CSS カスタムプロパティとは

再利用可能な値を定義できる、CSS の変数のようなものです。

ハイフン 2つ– –で始まるカスタムプロパティ名と、何らかの有効な CSS の値になるプロパティ値を使用することで定義できます。
var()関数の中でカスタムプロパティ名を使用することで、カスタムプロパティの値を使用することができます。

:root {
  --main-bg-color: #123456;
}
element {
  background-color: var(--main-bg-color);
}

 

実装手順

 

tailwindcss の設定を拡張

テナント情報の型(tenant-config.interface.ts)より、primaryaccentwarn の 3 色を定義し、CSS カスタムプロパティを使用するようにします。

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: "var(--primary-theme-color)",
        accent: "var(--accent-theme-color)",
        warn: "var(--warn-theme-color)",
      },
    },
  },
  important: true,
};

 

CSS カスタムプロパティにテナントのテーマカラーを設定する関数を定義

CSS カスタムプロパティに、取得したテナントのテーマカラーを設定する関数を定義します。(2 行目)

// update-theme-variables.ts
export const updateThemeVariables = (
  config: TenantConfig,
  document: Document
): void => {
  for (const [name, color] of Object.entries(config.themeColors)) {
    document.documentElement.style.setProperty(`--${name}-theme-color`, color);
  }
};

 

テナント情報を取得後、テーマカラーを設定する関数を実行

テナント情報を取得するサービスに、テナント情報を設定する関数を追加します。(33 行目)

テナント情報取得後、updateThemeVariables関数を呼び出し、テーマカラーを設定します。(35 行目)

// tenant-config.service.ts
import { DOCUMENT } from "@angular/common";
import { HttpClient } from "@angular/common/http";
import { Injectable, Inject } from "@angular/core";
import { TenantConfig } from "tenant-config.interface";
import { ConfigUrl } from "config-url";
import { updateThemeVariables } from "update-theme-variables";

@Injectable()
export class TenantConfigService {
  public config: TenantConfig | null = null;

  constructor(
    private readonly httpClient: HttpClient,
    @Inject(DOCUMENT) private readonly document: Document,
    private readonly configUrl: ConfigUrl
  ) {}

  public async load(): Promise<void> {
    let config: TenantConfig;

    try {
      config = await this.httpClient
        .get<TenantConfig>(this.configUrl.url)
        .toPromise();
    } catch {
      return;
    }

    this.set(config);
  }

  private set(config: TenantConfig): void {
    this.config = config;
    updateThemeVariables(config, this.document);
  }
}

 

ユーティリティクラスを用いて HTML の class 属性を指定

tailwindcss 設定の拡張により、text-primary, text-accent, text-warn 等のテーマカラーを使用した class 属性が利用可能となっています。
拡張した class 属性を使用して、デザインを構成します。

// sample.component.html
<p class="text-primary">test</p>
<div class="bg-accent"></div>

 

HTTP ヘッダにテナント ID を付与

Angular ではインターセプターと呼ばれる、HttpClientのリクエスト、レスポンスの処理に割り込むための機能があります。

その機能を用いて、HTTP リクエストを行う際、常にテナント ID を HTTP ヘッダに付与します。

 

実装手順

 

HTTP ヘッダにテナント ID を付与する関数を TenantService に定義

HTTP ヘッダにテナント ID を付与する関数を定義します。(14 行目)

// tenant.service.ts
import { DOCUMENT } from "@angular/common";
import { HttpHeaders } from "@angular/common/http";
import { Inject, Injectable } from "@angular/core";

@Injectable()
export class TenantService {
  constructor(@Inject(DOCUMENT) private readonly document: Document) {}

  public getTenant(): string {
    return this.getTenantForHostname(this.document.location.hostname);
  }

  public addTenantToHeaders(headers: HttpHeaders): HttpHeaders {
    return headers.append("X-Tenant-ID", this.getTenant());
  }

  private getTenantForHostname(hostname: string): string {
    return hostname.split(".")[0].toLowerCase();
  }
}

 

HTTP ヘッダを付与する、HttpInterceptor を定義

HttpInterceptorインターフェイスで定義される、intercept メソッドを実装し、HTTP リクエストの処理に割り込みます。(15 行目)

intercept メソッド内で TenantService.addTenanttoHeadersを用いて、テナント ID をヘッダに付与します。(19 行目)

import { Injectable } from "@angular/core";
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
} from "@angular/common/http";
import { Observable } from "rxjs";
import { TenantService } from "tenant.service";

@Injectable()
export class TenantInterceptor implements HttpInterceptor {
  constructor(private readonly tenantService: TenantService) {}

  public intercept(
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    const headers = this.tenantService.addTenantToHeaders(request.headers);
    const newReq = request.clone({ headers });

    return next.handle(newReq);
  }
}

 

定義した HttpInterceptor を登録

TenantModuleHttpInterceptorを登録し、HttpClient が利用できるようにします。(24 行目) 登録後、HTTP リクエストが行われる度、割り込み処理が入りテナント ID が HTTP ヘッダに付与されます。

// tenant.module.ts
import { APP_INITIALIZER, ModuleWithProviders, NgModule } from "@angular/core";
import { TenantConfigService } from "tenant-config.service";
import { ConfigUrl } from "config-url";
import { TenantService } from "tenant.service";
import { HTTP_INTERCEPTORS } from "@angular/common/http";
import { TenantInterceptor } from "tenant.interceptor";

export const initConfig =
  (tenantConfigService: TenantConfigService): (() => Promise<void>) =>
  () =>
    tenantConfigService.load();

@NgModule({
  providers: [
    TenantService,
    TenantConfigService,
    {
      provide: APP_INITIALIZER,
      useFactory: initConfig,
      deps: [TenantConfigService],
      multi: true,
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: TenantInterceptor,
      multi: true,
    },
  ],
})
export class TenantModule {
  static forRoot(templateUrl: string): ModuleWithProviders<TenantModule> {
    return {
      ngModule: TenantModule,
      providers: [
        {
          provide: ConfigUrl,
          useFactory: (tenantService: TenantService): ConfigUrl =>
            new ConfigUrl(templateUrl, tenantService),
          deps: [TenantService],
        },
      ],
    };
  }
}

 

あとがき

今回ご紹介した方法では、サブドメインをテナント ID として利用していますが、TenantServiceの処理を変更することにより、クエリストリングからテナント ID を取得する事等も可能です、試してみて下さい。

 

にTypeScriptでのフロントエンド開発を担当:木戸裕貴

木戸裕貴

私は主にTypeScriptでのフロントエンド開発を担当しております。

シェアする
記事カテゴリ
最新記事
Power Apps
2021.12.08

Power AppsアプリをPCとスマホで操作したい時の対応方針

Power Apps
2021.12.01

Dataverse for Teams・Power Apps:ドロップダウンメニューの順番を並び替えたい

Power Apps
2021.11.24

Automateのベストプラクティス・アンチパターン(1)【アクション名は変更すべき?Power】

Angular
2021.11.17

Angularでマルチテナント対応(2)

Angular
2021.11.10

Angularでマルチテナント対応(1)

人気記事ランキング
1
Power PlatformPower Apps
2020.10.02

世界に広がる「モデル駆動型アプリ」のココがスゴイ!【第1回】

2
Power PlatformPower AutomateSharePointExcel
2021.07.14

Power AutomateでExcelデータをSharePointにインポートするために考えること(第2回)

3
Power PlatformPower AutomateSharePointExcel
2021.06.23

Power AutomateでExcelデータをSharePointにインポートするために考えること(第1回)

4
Power PlatformPower AppsSharePoint
2021.07.16

Power AppsとSharePoint連携:SortByColumns関数で日本語列をソートする際の注意点

5
Power PlatformPower AutomateSharePointExcel
2021.08.11

Power AutomateでExcelデータをSharePointにインポートするために考えること(第5回)

PageTop
ページトップに戻る