🎄

ReactコンポーネントをAngularで使う

2024/12/26に公開

はじめまして、@onicode3です。
AngularコンポーネントからReactコンポーネントを使う方法を紹介します。

Angular から React コンポーネントを使う実装方法

1. React パッケージのインストール

Reactをインストールします。
今回はテストの為、angularからreact-selectを表示してみます。

npm i react react-dom react-select
npm i -D @types/react @types/react-dom

2. tsconfig.json の設定

tsconfig.json に jsx の設定を追加します。

tsconfig.json
{
  "jsx": "react-jsx"
}

3. ライブサイクルフックでReactコンポーネントをレンダリングする

Angularのライフサイクルフックを使って、Reactコンポーネントをレンダリングします。

  • afterNextRender(一回限りの初期化): コンテナとなるdomを取得し、Reactコンポーネントをレンダリングします。
  • afterRender(すべての変更検出時): Reactコンポーネントを再レンダリングして、Angular側の変更を反映します。
  • inject(DestroyRef).onDestroy(): Angularコンポーネントが破棄された時にReactコンポーネントもアンマウントします。

これで完成です。

const containerElementName = '__REACT_COMPONENT_REF__';

@Component({
    selector: 'app-angular-react-select',
    imports: [],
    template: `
    <div #${containerElementName}></div>
  `,
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AngularReactSelectComponent {
    // ~~~
    private reactRoot!: Root;
    
    constructor() {
        inject(DestroyRef).onDestroy(() => {
            this.reactRoot.unmount();
        });
        afterNextRender({
            write: () => {
                this.reactRoot = createRoot(this.reactRootContainer().nativeElement);
                this.render();
            },
        });
        afterRender({
            write: () => {
                this.render();
            },
        });
    }
    // ~~~
}

完成したコード

今回はreact-selectをAngularから表示しています。

全体 ソースコード
angular-react-select.component.tsx
import {
    afterNextRender,
    afterRender,
    ChangeDetectionStrategy,
    Component,
    DestroyRef,
    ElementRef,
    inject,
    signal,
    viewChild,
} from '@angular/core';
import { StrictMode } from 'react';
import { createRoot, Root } from 'react-dom/client';
import Select, { Options, SingleValue } from 'react-select';

const containerElementName = '__REACT_COMPONENT_REF__';

export interface SelectOptionValue {
    value: string;
    label: string;
}
const options = [
    { value: 'chocolate', label: 'Chocolate' },
    { value: 'strawberry', label: 'Strawberry' },
    { value: 'vanilla', label: 'Vanilla' },
];

@Component({
    selector: 'app-angular-react-select',
    imports: [],
    template: `
    <p>angular-react-select works!</p>
    <button (click)="addClick()">Add Option</button>
    <div #${containerElementName}></div>
    <div>{{ selectValue()?.label }}</div>
  `,
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AngularReactSelectComponent {
    private readonly reactRootContainer =
        viewChild.required<ElementRef<HTMLDivElement>>(containerElementName);

    public options = signal<Options<SelectOptionValue>>(options);

    public selectValue = signal<SelectOptionValue | null>(options[0]);

    private reactRoot!: Root;

    constructor() {
        inject(DestroyRef).onDestroy(() => {
            this.reactRoot.unmount();
        });
        afterNextRender({
            write: () => {
                this.reactRoot = createRoot(this.reactRootContainer().nativeElement);
                this.render();
            },
        });
        afterRender({
            write: () => {
                this.render();
            },
        });
    }

    protected addClick = () => {
        this.options.update((options) => {
            return [...options, { label: 'Hello', value: 'hello' }];
        });
    };

    private readonly handleChange = (value: SingleValue<SelectOptionValue>) => {
        this.selectValue.set(value);
    };

    private readonly render = () => {
        this.reactRoot.render(
            <StrictMode>
                <Select
                    defaultValue={this.selectValue()}
                    options={this.options()}
                    onChange={this.handleChange}
                />
            </StrictMode>
        );
    };
}

Demo

Discussion