技術情報ブログ
Angular
2021.11.17

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

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でのフロントエンド開発を担当しております。

シェアする
記事カテゴリ
最新記事
2024.11.20

【小ネタ】Power Automate×SharePoint:ファイルやフォルダを削除する方法(ゴミ箱・完全削除)

2024.11.06

Power Automateのベストプラクティス・アンチパターン(7)【エラー発生時、管理者へ通知する方法】

2024.10.23

【内製化支援ツール】Power Platform Advisor の紹介

2024.10.09

非エンジニア【(元)自治体職員】が自治体のDX推進リーダーに任命されたつもりで、Power Platformの活用を考えてみた

2024.09.18

Power Automateのベストプラクティス・アンチパターン(6)【Power Automate上でExcelファイルを編集する際の注意点】

モデル駆動型アプリPower AppsPower PlatformSharePointExcelPower AutomateC#attributevalidationローコードAngularAccessInfoPathMatTableAngular Materialデータ構造SortByColumns関数TypeScriptHTMLEF CoreマイグレーションFramework CoreAttribute directivesO/Rマッパーazure sql databaseCase式HTTP RequestCSSxUnit.Net Core 3.1VSCode.Net Core Test ExplorerDataverse for Teamsitem関数Google MapsMarker ClustererRANK()関数Dynamics 365 SalesMicrosoft TranslatorマーカークラスタリングライブラリtailwindcssマルチテナントドロップダウンメニューBreakpointObserverメディアクエリスマホPCレスポンシブ入門初心者中級者キャンバスアプリDatePickerDropdownviewビューアクセス制限承認リマインドSetForAllUpdateContextロードマップ技術It情報技術メッセージIDメールfirst()関数nest入れ子動的リストcollectionコレクション複数の添付ファイル承認フローformエクスポートインポートカスタマイズcomponentダイアログコンポーネントdialogTips新機能変数検索Microsoft 365グループセキュリティグループ送信元メールの送信差出人インスタントクラウドフロー自動化したクラウドフロー委任VBAエラーエクセルerror復元restorePower BI個人列ユーザー列SharePoint Onlineリスト非表示アプリ[市民開発者構築自動化したクラウド フローフローの種類インスタント クラウド フロースケジュール済みクラウド フローレスポンシブ レイアウトresponsive layoutデータ行の制限引き継ぎ退職所有者を変更異動LoopMicrosoftdesignJSONデザイン運用選択肢列参照列ChatGPTOpenAIオープンAIチャットGPTgalleryギャラリースクロールコンテナショートカットキーshortcut keyconcat関数文字制限フロー実行開発環境環境本番環境ライセンス環境構築手順pipelineCI/CDパイプラインDevOpsMicrosoft 365簡易在庫管理時間外通知ファイルフィルター クエリドキュメント ライブラリfilter querysortソートmultiple item複数項目シェアポイント便利機能カレンダーCalendarTeamsローコード開発非エンジニア体験談勉強内製化市民開発管理ガバナンスerror notificationエラー通知削除フォルダゴミ箱完全削除
PageTop
ページトップに戻る