技術情報ブログ
Angular
2021.11.17

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

Angularでマルチテナント対応(2)
木戸裕貴

こんにちは。アーティサン株式会社の木戸です。

はじめに

本記事は、Angular でのマルチテナント対応の後編です。前編で取得したテナント情報をもとに、tailwindcss を用いたテーマカラーの動的変更と、HttpInterceptor を活用した HTTP ヘッダへのテナント ID 付与という 2 つの実装テクニックを解説します。Angular 12・TypeScript 4・tailwindcss 2 の環境を対象としており、CSS カスタムプロパティを介したカラー切り替えから TenantModule の構成まで、実務で活用できるサンプルコードとともに紹介します。

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

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

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

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

本記事では Angular を用いたマルチテナント実装を解説しましたが、アーティサン株式会社では Microsoft クラウド技術を活用したシステム開発・導入支援も承っています。技術選定や設計についてお気軽にご相談ください。

 

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],
        },
      ],
    };
  }
}

あとがき

本記事では Angular でのマルチテナント対応の後編として、次の 2 つを実装しました。

① tailwindcss によるテーマカラーの動的切り替え

tailwindcss の設定を拡張し、CSS カスタムプロパティを通してテナントごとのテーマカラーを動的に切り替える

② HttpInterceptor によるテナント ID の自動付与

HttpInterceptor を使用し、すべての HTTP リクエストに対してテナント ID を自動付与する

どちらも TenantService・TenantModule を中心に疎結合に実装できるため、テナント数が増加しても本体コードの変更を最小限に抑えられます。

📘 前後編あわせて読む

前編の「Angular でマルチテナント対応(1)」と合わせて、サブドメインベースのマルチテナント基盤として活用してください。

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

マルチテナントアーキテクチャや Angular・TypeScript を活用したフロントエンド開発にご興味のある方は、アーティサン株式会社の Microsoft クラウド事業ページもぜひご覧ください。開発に関するご相談はお気軽にどうぞ。

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

木戸裕貴

🖊木戸裕貴さんのブログ一覧はこちら

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

Microsoftクラウド関連

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

Dataverse応用(1)!列レベルのセキュリティを設定してみよう!-特定の列だけマスクする

2026.04.22

ソリューション内で異動・退職者のPower Apps/Power Automateなどを引き継ぐには?所有者変更の手順と注意点を解説

2026.04.15

SharePoint:『リンクのコピー』は危険?権限トラブルを防ぐ推奨運用を解説

2026.04.08

技術ブログ100本書いてみたら、想像以上に得るものがあった話

2026.04.01

社内に聞く人がいない「孤独なDX」を解決!アーティサンの内製化支援サービス

AccessCSSBreakpointObserver承認動的リスト変数Power BI引き継ぎgalleryパイプラインカレンダー完全削除接続ファイルサイズ基本知識フォントカスタマイズ体験記エンティティワークフロー自動化UI/UXVisual Studio CodeAlternate Key野良アプリ対策IT エンジニア 転職OneDrive1対多InfoPathxUnitメディアクエリリマインドcollection検索個人列退職ギャラリーDevOpsCalendarモデル駆動型データフローフルリモートワークPowerAutomateブランドセンター感想フォルダ構成設定アクセシビリティPCF代替キーCoEブログ モチベーションつながり参照多対多MatTable.Net Core 3.1スマホSetコレクションMicrosoft 365グループユーザー列所有者を変更スクロールMicrosoft 365Teamsセキュリティロールrecycle binアーティサンX-SP Designテーマ作成チームサイトMicrosoft Learn Docsアジャイル開発Wordテンプレート環境構築重複チェック内製化コンサルティングネタ切れ 対策システムカスタマイザーER図Angular MaterialVSCodePCForAll複数の添付ファイルセキュリティグループSharePoint Online異動コンテナ簡易在庫管理ローコード開発ビジネスルールアクセス許可Artisanスライドショーデザイン拡張コミュニケーションサイトカスタムコネクタ準委任契約業務自動化カスタムコンポーネントGUIDITサポートAI ブログ 活用Connection Reference入門編データ構造.Net Core Test ExplorerレスポンシブUpdateContext承認フロー送信元リストLoopショートカットキー時間外非エンジニアDataverseSharePoint Framework転職Slide showMicrosoft365サイトの種類OpenAPI請負契約効率化Power Platform CLI業務キー月額定額PV数 増やす共同所有者1行テキストモデル駆動型アプリSortByColumns関数Dataverse for TeamsDynamics 365ロードマップフォームメールの送信非表示Microsoftshortcut key通知体験談JavaScriptSPFx主キー比較移行要件定義MCPサーバー総合評価型入札資料作成開発手順複合キー委任問題権限トラブル主要な所有者複数行テキストPower AppsTypeScriptitem関数入門技術form差出人アプリdesignconcat関数ファイル勉強表示サンプルCopilot Studio社内ポータル多言語化サイト構成FAQエージェントデジタルトランスフォーメーション初心者向け拡張機能データ整合性Delegationアクセス権限環境変数選択肢Power PlatformHTMLGoogle Maps初心者Itエクスポートインスタントクラウドフロー[市民開発者JSON文字制限フィルター クエリ内製化切替samplePowerAppsグループウェアMUI権限設計AIチャットボットプロポーザル方式ハウツービルドデータベース設計サブスクリプション型支援ファイルパスクラシック画面日付SharePointEF CoreMarker Clusterer中級者DXインポート自動化したクラウドフロー構築デザインフロー実行ドキュメント ライブラリ市民開発登録者X-SPNFCタグエンゲージメントMultilingualデータ移行実運用官公庁システム画像挿入プロジェクト作成ユニークキーMVP共有設定ソリューションエクスポート整数ExcelマイグレーションRANK()関数キャンバスアプリノウハウカスタマイズ委任自動化したクラウド フロー運用開発環境filter query管理システム列StyleDLPポリシー地方自治体MLJSON書式保守性デジタル化推進複数レコードPCFギャラリー一意制約技術支援情報漏洩対策権限管理データ型Power AutomateFramework CoreDynamics 365 SalesDatePicker情報技術componentVBAフローの種類選択肢列環境sortガバナンス登録日StudioTestCopilot Studiot共有リンクテンプレート化DX推進テーマカラーPDF変換業務システムURLパラメータ技術ブログ 書き方コマンドバーカスタマイズ組織変更C#Attribute directivesMicrosoft TranslatorDropdownメッセージIDダイアログエラーインスタント クラウド フロー参照列本番環境ソートerror notification更新者AICanvas自治体DXレポート化サイト複製作り方ダークモード資料自動作成キャンバスアプリ 違いメンテナンスモードエンジニア ブログ メリットSharePoint管理センター注文管理アプリattributeO/Rマッパーマーカークラスタリングライブラリviewメールコンポーネントエクセルスケジュール済みクラウド フローChatGPTライセンスmultiple itemエラー通知更新日生成系AITest Studio生成AI自治体APIPnP PowerShellページ承認Formulasプロパティフロー設計Power Apps 導入並べ替えブログ 継続 コツDLPサブグリッドvalidationazure sql databasetailwindcssビューfirst()関数dialogerrorレスポンシブ レイアウトOpenAI環境構築手順複数項目削除変更Copilotテスト事例HTTP リクエストカスタムスクリプトドキュメント管理カラーセットテンプレート活用Power Apps 比較ヘッダー非表示技術力向上権限管理ベストプラクティスDataverse テーブルローコードCase式マルチテナントアクセス制限nestTips復元responsive layoutオープンAIpipelineシェアポイントフォルダ外部DBlicenseテストスタジオ活用ワーケーション業務効率化IT管理カラーユニバーサルデザイン自動化事例モデル駆動型 とはリスト フィルタ文章力 鍛え方野良権限部署AngularHTTP Requestドロップダウンメニューノーコード入れ子新機能restoreデータ行の制限チャットGPTCI/CD便利機能ゴミ箱連携添付ファイルコントロール使い方サイトブランド化名古屋ファイル保存申請システムアプリデザインNode.jsシステム構築便利アウトプット 重要性Teams ファイル共有リレーションシップ
PageTop
ページトップに戻る