Angular で RichTextEditor に画像ギャラリーを追加する

Angular で RichTextEditor の「Insert Gallery」ダイアログを使うには、ほかのシングルページアプリと同じ 3 つのものが必要です。アプリより前に読み込むエディターのスクリプト、適切なタイミングでエディターを作成・破棄するコンポーネント、そしてダイアログのリクエストを API に転送する開発サーバーです。このページでは、Angular 22 のスタンドアロンコンポーネントでそれをどう実現するかを、ng serve と Express のサンプルでテストした結果とともに紹介します。
index.html でエディターのスクリプトを読み込む
エディターは、グローバルな RichTextEditor を定義する従来型のブラウザー用スクリプト群です。Angular のバンドルより前の src/index.html に置きます。
<!-- Plain browser scripts from public/, loaded before the Angular bundle. -->
<link rel="stylesheet" href="/richtexteditor/rte_theme_default.css">
<script src="/richtexteditor/rte.js"></script>
<script src="/richtexteditor/plugins/all_plugins.js"></script>
<script src="/rte-files.js"></script>ファイルは public/richtexteditor/ に置きます。新しい Angular プロジェクトでは、angular.json がすでに public/ 内のすべてを静的アセットとしてビルドにコピーするので、そこを変更する必要はありません。サンプルでは、ダウンロードに含まれる形のまま、エディターを /richtexteditor/ 以下の個別のファイルとして置いています。スクリプトを 1 つのファイルにバンドルする angular.json の "scripts" 配列は使っていません。その方法でのエディターの動作はテストしていません。
コンポーネント
// RichTextEditor is the global from the scripts in index.html.
declare const RichTextEditor: new (element: HTMLElement, options: object) => EditorInstance;
type EditorInstance = { setHTMLCode(html: string): void; getHTMLCode(): string; destroy(): void };
@Component({
selector: 'app-rich-text-editor',
template: `<div #container></div>`,
})
export class RichTextEditorComponent implements AfterViewInit, OnDestroy {
readonly options = input.required<object>();
readonly initialHtml = input('');
private readonly container = viewChild.required<ElementRef<HTMLDivElement>>('container');
private editor?: EditorInstance;
ngAfterViewInit(): void {
this.editor = new RichTextEditor(this.container().nativeElement, this.options());
this.editor.setHTMLCode(this.initialHtml());
}
ngOnDestroy(): void {
this.editor?.destroy();
this.editor = undefined;
}
getHTML(): string {
return this.editor?.getHTMLCode() ?? '';
}
}declare const RichTextEditorは、コンポーネントが使う 3 つのメソッドだけを持つグローバル変数として、TypeScript に存在を知らせます。viewChild.requiredは、テンプレートのdivを取得します。それが存在する最初のフックがngAfterViewInitなので、エディターはそこで作成します。ngOnDestroyはdestroy()を呼び出すため、ルートの変更時や@ifが false になったときなどに、エディターはコンポーネントと一緒に破棄されます。サンプルは 1 ページだけなので、ブラウザーでの確認ではこのフックは実行されませんでした。- シグナル入力(
input.required、input)で、オプションと初期 HTML を受け取ります。これらは作成時に 1 回だけ読み取られます。
このプロジェクトは ng new --minimal --zoneless で作成しました。エディターは zone.js なしで動作しました。エディターは自身の DOM イベントを自分で処理し、コンポーネントには変更検知に依存する部分がないためです。アプリはオプションを渡し、テンプレート参照を通じて HTML を読み取ります。
const options = {
toolbar: 'full',
galleryEndpoint: '/rte/gallery', // forwarded to Express by src/proxy.conf.json
maxUploadFileSize: 4000000,
};サンプルの「Show HTML」ボタンは editor.getHTML() を呼び出し、<p>Hello from Angular.</p> と出力しました。
開発中に /rte/ と /uploads/ を転送する
ng serve はアプリをポート 5113 で配信します。プロキシファイルは、ギャラリーとアップロードのルート、そして画像を、ポート 5101 で動く Express のサンプルに送ります。
{
"/rte/": { "target": "http://localhost:5101", "secure": false },
"/uploads/": { "target": "http://localhost:5101", "secure": false }
}サンプルでは、angular.json の projects.frontend-angular.architect.serve.options に "proxyConfig": "src/proxy.conf.json" として一度だけ設定しています。ng serve --proxy-config src/proxy.conf.json としても実行してみましたが、結果は同じでした。Angular の開発サーバーは Vite をベースにしています。Angular は、/api/** のようにグロブ文字を含むキーを正規表現に変換し、単純なキーはそのまま Vite に渡します。Vite はそれらをプレフィックスで照合するので、末尾のスラッシュは残してください。"/rte/" では /rte-files.js は転送されず、ng serve が public/ にあるアプリ自身のコピーを、X-Powered-By: Express ヘッダーなしで配信しました。
プロキシは ng serve 専用です。本番ビルドにはプロキシがないため、ビルドしたアプリと API を 1 つのオリジンから配信してください。
ダイアログに表示されるもの
ページ上部の画像は、この Angular アプリで開いたダイアログです。Node.js のギャラリーガイドで説明している Express サーバーのフォルダーと画像が一覧表示されています。ほかのフレームワークと同様に、リリース版のギャラリーのヒントには JFIF、BMP、AVIF が含まれていますが、サーバーはこれらを拒否します。拒否されたアップロードについては、ダイアログ独自のメッセージ「Upload failed for <name>.」が表示されます。
テストする
このガイドのサンプルプロジェクトと Express のサンプルをダウンロードし、まず Express を起動します。次に、Angular プロジェクトで以下を実行します。
npm install
npm start
➜ Local: http://localhost:5113/
curl "http://localhost:5113/rte/gallery?folder="
{"currentFolder":"","currentFolderDisplay":"/","parentFolder":null,"folders":[...],"images":[...]}npm start は、最初にエディターのファイルを public/ にコピーします。RTE_DIR にエディターのフォルダーを設定するか、そのフォルダーを自分で public/richtexteditor に置いてください。http://localhost:5113 を開き、Insert Gallery をクリックします。当社では、Chromium 上で Angular 22.2 を使って一連の手順を実行しました。一覧はサーバーの内容と一致し、New folder と Upload はプロキシを通じて動作し、Insert で画像が本文に挿入されました。