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

Angular のサンプルで開いた「Insert Gallery」ダイアログ。Express サーバーの blog、products、team フォルダーと 4 つの画像が一覧表示されている

Angular で RichTextEditor の「Insert Gallery」ダイアログを使うには、ほかのシングルページアプリと同じ 3 つのものが必要です。アプリより前に読み込むエディターのスクリプト、適切なタイミングでエディターを作成・破棄するコンポーネント、そしてダイアログのリクエストを API に転送する開発サーバーです。このページでは、Angular 22 のスタンドアロンコンポーネントでそれをどう実現するかを、ng serve と Express のサンプルでテストした結果とともに紹介します。

index.html でエディターのスクリプトを読み込む

エディターは、グローバルな RichTextEditor を定義する従来型のブラウザー用スクリプト群です。Angular のバンドルより前の src/index.html に置きます。

HTML · 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" 配列は使っていません。その方法でのエディターの動作はテストしていません。

コンポーネント

TypeScript · src/app/rich-text-editor.ts
// 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 を読み取ります。

TypeScript · src/app/app.ts
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 のサンプルに送ります。

JSON · src/proxy.conf.json
{
  "/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 プロジェクトで以下を実行します。

Shell
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 で画像が本文に挿入されました。

ほかのプラットフォーム向けの同じガイド

React · Vue

次に読む