Skip to content

Angularで使う ​

@tuvix.js/angularはAngularコンポーネントをマイクロアプリとしてブートストラップします。デフォルトではzone.jsを使いません。また、スタンドアロンコンポーネントをサーバーでHTMLにレンダリングします。

Angular 15以降をサポートします。renderAngularToStringにはAngular 16以降と@angular/platform-serverが必要です。

インストール ​

bash
pnpm add @tuvix.js/angular @angular/core @angular/common @angular/platform-browser
# JIT components (no AOT build step) also need the compiler
pnpm add @angular/compiler
# for renderAngularToString
pnpm add @angular/platform-server

コンポーネントをマイクロアプリにする ​

ts
// src/main.ts
import { createAngularMicroApp } from '@tuvix.js/angular';
import { provideHttpClient } from '@angular/common/http';
import { AdminComponent } from './admin.component';

export default createAngularMicroApp({
  name: 'admin',
  component: AdminComponent,
  providers: [provideHttpClient()],
});
オプション型説明
namestring必須。登録したアプリ名と一致する必要があります。
componentType<C>必須。ルートコンポーネント。スタンドアロン、またはmoduleで宣言されたもの。
moduleType<unknown>componentを宣言するNgModule。そのプロバイダーはimportProvidersFromでインポートされます。
providersArray<Provider | EnvironmentProviders>環境プロバイダー。
selectorstringホスト要素のタグ。デフォルト:コンポーネントのセレクターの要素部分、またはdiv。
zonebooleanzone.jsによる変更検知を使います。デフォルトはfalse。
ssrbooleanrenderAngularToStringのマークアップを引き継ぎます。createSsrAngularMicroAppはこれを設定します。
bootstrap() => void | Promise<void>最初のマウントの前に1回実行されます。

各マウントはcreateApplicationを呼び出し、コンテナ内の専用のホスト要素にコンポーネントをブートストラップします。1つのページ上の2つのAngularアプリや、2つのコンテナにマウントされた1つのモジュールが、共有のapp-rootで衝突することはありません。

コンポーネントにコンパイル済みのɵcmpがない場合(JIT)、アダプターは@angular/platform-browserの前に@angular/compilerを読み込みます。これが役立つのは、@angular/core以外のAngularパッケージを最初に読み込むコードがアダプターである場合だけです。コンポーネントのファイルが@angular/common(NgIf、AsyncPipe、CommonModule)、@angular/forms、@angular/router、またはその他のAngularライブラリをインポートしている場合、それらのパッケージはアダプターの実行前に評価され、"JIT compilation failed"で失敗します。クライアントエントリーの最初の行にimport '@angular/compiler'を置くか、AOTでビルドしてください。

プロパティからの入力 ​

プロパティはコンポーネントの入力として宣言します。

ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-admin',
  standalone: true,
  template: `<h1>Admin for {{ userId }}</h1>`,
})
export class AdminComponent {
  @Input() userId = '';
}
  • マウントは最初の変更検知の前にComponentRef.setInputで入力を設定するため、ngOnInitからそれらを参照できます。
  • update({ props })は新しい値を設定し、変更検知を実行します。containerを指定すると、そのマウントだけを更新します。
  • 入力ではないキーはスキップされます。新しいプロパティにない入力はundefinedにリセットされます。

zone.jsを使わない変更検知 ​

アダプターはzone.jsをインポートしないため、ページ上でもサーバー上でもPromise、タイマー、DOMイベントにパッチを当てません。Angularのバージョンに応じてプロバイダーを選びます。

AngularプロバイダーDOMイベントが変更検知をトリガーするか
20+provideZonelessChangeDetection()はい
18-19provideExperimentalZonelessChangeDetection()はい
15-17NgZoneを何もしないzoneに置き換えいいえ

Angular 15-17では、クリックの後に変更検知を実行するものがありません。ハンドラー内でdetectChanges()を呼び出します。

ts
import { ChangeDetectorRef, Component, inject } from '@angular/core';

@Component({
  selector: 'app-counter',
  standalone: true,
  template: `<button (click)="add()">{{ count }}</button>`,
})
export class CounterComponent {
  private readonly cdr = inject(ChangeDetectorRef);
  count = 0;

  add(): void {
    this.count += 1;
    this.cdr.detectChanges();
  }
}

zoneベースの変更検知を使い続けるには、クライアントエントリーの先頭でzone.jsをインポートし、zone: trueを渡します。zone.jsが読み込まれていない場合、mountはrejectします。

サーバーレンダリング ​

ts
// server-only module
import { renderAngularToString } from '@tuvix.js/angular/server';
import { AboutComponent } from './about.component';

export async function renderAbout(): Promise<string> {
  return renderAngularToString(AboutComponent, { appId: 'about', props: { userId: '42' } });
}
ts
// client
import { createSsrAngularMicroApp } from '@tuvix.js/angular';
import { AboutComponent } from './about.component';

export const about = createSsrAngularMicroApp({ name: 'about', component: AboutComponent });
  • renderAngularToString(component, { props?, providers?, appId?, document?, selector? })は、コンポーネントの<style>タグに続けてホスト要素を返します。そのため、アイランドは初回描画の時点でスタイルが適用されています。zonelessで実行されます。
  • propsは、クライアントでmountが行うのと同様に、最初の変更検知の前にコンポーネントの入力を設定します。入力ではないキーはスキップされます。クライアントでも同じプロパティをアイランドに渡してください。そうしないと、ハイドレーションで異なる内容が表示されます。
  • コンポーネントが開始したHTTPリクエストやタイマーは待ちません。先にデータを読み込み、それを提供します。
  • サーバーのコードから呼び出します。ブラウザーでは''を返します。

クライアントでは、createSsrAngularMicroAppはサーバーのホスト要素にレンダリングし、Angularが自身のスタイルを追加した時点で、インライン化されたサーバーの<style>タグを削除します。tuvix:mountedイベントはhydrated: trueを報告します。これはサーバーのDOMを置き換えます。Angularの非破壊的ハイドレーションには<body>直下のマーカーが必要で、別のページ内にあるアイランドはそれを提供できないため、アダプターはprovideClientHydration()を追加しません。

イベントバス ​

ts
import { Component, OnDestroy, signal } from '@angular/core';
import { getGlobalBus } from '@tuvix.js/event-bus';

@Component({
  selector: 'app-cart-badge',
  standalone: true,
  template: `<span>{{ count() }}</span>`,
})
export class CartBadgeComponent implements OnDestroy {
  readonly count = signal(0);
  private readonly off = getGlobalBus().on<{ itemCount: number }>('cart:updated', ({ itemCount }) =>
    this.count.set(itemCount),
  );

  ngOnDestroy(): void {
    this.off();
  }
}

Angular 18以降のzonelessでは、シグナルが変更検知をスケジュールします。15-17では、setの後にChangeDetectorRef.detectChanges()を呼び出します。

アダプターが行わないこと ​

  • zone.jsや@angular/localizeは読み込みません。必要な場合はエントリーでインポートします。
  • 非破壊的なハイドレーションは行いません。上記を参照してください。
  • マウント間で状態を共有しません。各マウントは専用のルートインジェクターを持つ独立したAngularアプリケーションなので、providedIn: 'root'のサービスはマウントごとに1つ存在します。

API ​

エクスポート用途
createAngularMicroApp({ name, component, module?, providers?, selector?, zone?, ssr?, bootstrap? })Angularマイクロアプリのモジュール
createSsrAngularMicroApp(config)ssr: trueを設定した同等のもの
@tuvix.js/angular/serverのrenderAngularToString(component, options?)スタイル + ホスト要素のHTML