Añadir una galería de imágenes a RichTextEditor en Angular

El cuadro de diálogo Insert Gallery de RichTextEditor necesita en Angular las mismas tres cosas que en cualquier aplicación de una sola página: los scripts del editor cargados antes que la aplicación, un componente que cree y destruya el editor en el momento adecuado, y un servidor de desarrollo que reenvíe las solicitudes del cuadro de diálogo a tu API. Esta página muestra cómo queda en un componente standalone de Angular 22, probado con el ejemplo de Express y ng serve.
Carga los scripts del editor en index.html
El editor es un conjunto de scripts clásicos para el navegador que definen una variable global RichTextEditor. Ponlos en src/index.html, antes del bundle de Angular:
<!-- 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>Los archivos están en public/richtexteditor/. En un proyecto nuevo de Angular, angular.json ya copia todo lo que hay en public/ a la compilación como recursos estáticos, así que no hace falta cambiar nada ahí. El ejemplo mantiene el editor como archivos separados bajo /richtexteditor/, tal como vienen en la descarga. No usa el array "scripts" de angular.json, que agrupa los scripts en un solo archivo. No probamos el editor de esa forma.
El componente
// 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 RichTextEditorle informa a TypeScript de la variable global, con solo los tres métodos que usa el componente.viewChild.requiredda eldivde la plantilla.ngAfterViewInites el primer hook en el que existe, así que el editor se crea ahí.ngOnDestroyllama adestroy(), para que el editor desaparezca con el componente, por ejemplo al cambiar de ruta o cuando un@ifpasa a false. El ejemplo tiene una sola página, así que nuestra comprobación en el navegador no ejercitó este hook.- Las entradas signal (
input.required,input) llevan las opciones y el HTML inicial. Se leen una sola vez, al crear el editor.
El proyecto se creó con ng new --minimal --zoneless. El editor funcionó sin zone.js: gestiona sus propios eventos del DOM, y nada en el componente depende de la detección de cambios. La aplicación pasa las opciones y lee el HTML a través de una referencia de plantilla:
const options = {
toolbar: 'full',
galleryEndpoint: '/rte/gallery', // forwarded to Express by src/proxy.conf.json
maxUploadFileSize: 4000000,
};El botón Show HTML del ejemplo llama a editor.getHTML() y mostró <p>Hello from Angular.</p>.
Reenvía /rte/ y /uploads/ durante el desarrollo
ng serve sirve la aplicación en el puerto 5113. El archivo de proxy envía las rutas de la galería y de carga, y las imágenes, al ejemplo de Express en el puerto 5101:
{
"/rte/": { "target": "http://localhost:5101", "secure": false },
"/uploads/": { "target": "http://localhost:5101", "secure": false }
}El ejemplo lo define una vez en angular.json, dentro de projects.frontend-angular.architect.serve.options, como "proxyConfig": "src/proxy.conf.json". También lo ejecutamos como ng serve --proxy-config src/proxy.conf.json, con el mismo resultado. El servidor de desarrollo de Angular está construido sobre Vite. Angular convierte en expresiones regulares las claves que contienen caracteres glob, como /api/**, y pasa las claves simples a Vite sin cambios. Vite las compara por prefijo, así que mantén la barra final. Con "/rte/", /rte-files.js no se reenvió: ng serve sirvió la copia propia de la aplicación desde public/, sin encabezado X-Powered-By: Express.
El proxy es solo para ng serve. Una compilación de producción no tiene proxy: sírvela junto con la API desde un mismo origen.
Qué muestra el cuadro de diálogo
La imagen del principio es el cuadro de diálogo en esta aplicación Angular, con las carpetas y las imágenes del servidor Express descrito en la guía de la galería para Node.js. Como en cualquier framework, la indicación de la galería publicada enumera JFIF, BMP y AVIF, que el servidor rechaza. Una carga rechazada muestra el mensaje propio del cuadro de diálogo, "Upload failed for <name>.".
Pruébalo
Descarga el proyecto de ejemplo de esta guía y el ejemplo de Express, e inicia primero Express. Después, en el proyecto de Angular:
npm install
npm start
➜ Local: http://localhost:5113/
curl "http://localhost:5113/rte/gallery?folder="
{"currentFolder":"","currentFolderDisplay":"/","parentFolder":null,"folders":[...],"images":[...]}npm start copia primero los archivos del editor en public/. Define RTE_DIR con la carpeta de tu editor, o coloca tú mismo la carpeta en public/richtexteditor. Abre http://localhost:5113 y haz clic en Insert Gallery. Ejecutamos toda la secuencia en Chromium con Angular 22.2: la lista coincidía con el servidor, New folder y Upload funcionaron a través del proxy, e Insert puso la imagen en el texto.