PageKit: el creador de sitios GrapesJS autoalojado, con el código fuente incluido. Obtener acceso anticipado

Integración GrapesJS Angular

GrapesJS Angular:Construye un editor visual con Angular

Integra el editor visual de código abierto GrapesJS en aplicaciones Angular para crear creadores de páginas de arrastre y solta, editores CMS, creadores de páginas de aterrizaje, editores de correo electrónico y experiencias de edición visual de marca blanca.

Código abiertoAutoalojadoSalida HTML y CSSExtensibleCompatible con Angular

26k+

Estrellas en GitHub

1.4M+

Descargas de npm por mes

100+

plugins en GJS.Market

$0

Tasa de licencia

Editor en directo

Prueba GrapesJS en una aplicación Angular

Abrir en una pestaña nueva

La versión estándar de GrapesJS — el editor que obtienes de 'GrapesJS.init()' con el preajuste estándar, antes de añadir cualquier plugin. Arrastra un bloque desde el panel derecho, seleccionarlo y edita su tipografía y espaciado en el gestor de estilos.

grapesjs.com/demo.htmlGratis

Carga una página de terceros desde grapesjs.com. No se solicita nada hasta que haces clic.

Estas son compilaciones en vivo de GrapesJS, no demos de Angular — y ese es el objetivo. GrapesJS renderiza en un elemento contenedor que posee completamente, así que el lienzo, bloques, gestor de estilos, previsualizaciones responsivas, selector de recursos y historial de deshacer que manejas a continuación se comportan igual una vez que un componente Angular aloja ese contenedor.

  1. Angular application
  2. GrapesJS editor
  3. HTML + CSS + project data
  4. Your backend / CMS / SaaS

Angular impulsa tu aplicación. GrapesJS impulsa la experiencia de edición visual. Lo que sale es HTML, CSS y un documento de proyecto JSON que tu propio backend almacena.

Respuesta directa

¿Se puede usar GrapesJS con Angular?

GrapesJS es independiente del framework y puede integrarse en aplicaciones Angular. Inicializas el editor a través de ganchos de ciclo de vida Angular, mantienes la instancia del editor bajo tu control, conectas eventos GrapesJS a la lógica de la aplicación Angular y persistes los datos del proyecto a través de tu propio backend.

  • GrapesJS es una biblioteca JavaScript sencilla sin binding de framework — necesita un elemento DOM y nada más.
  • Inicialízalo en ngAfterViewInit, una vez que el elemento contenedor realmente exista en el DOM.
  • Destrúyelo en ngOnDestroy para que los oyentes, los nodos DOM y la pila de deshacer se liberen junto con el componente.
  • Mantén la instancia del editor en un servicio si más de un componente necesita comunicarse con ella.
  • Los eventos del editor son eventos emisores ordinarios: conecta con señales, observables o NgZone según lo requiera tu app.
  • El almacenamiento y los recursos son tuyos: GrapesJS llama a tus endpoints, no entrega un backend.

No existe un envoltorio oficial de Angular. El proyecto GrapesJS mantiene un envoltorio de React y ningún equivalente a Angular, por lo que una integración con Angular significa escribir un componente — aproximadamente las treinta líneas del ejemplo siguiente — o evaluar un paquete de terceros por sus propios méritos.

Ir al ejemplo de trabajo
La división del trabajo

¿Qué es GrapesJS para Angular?

GrapesJS es el motor de edición visual. Angular es el framework de la aplicación. Casi todos los problemas de integración que la gente tiene surgen de esperar que uno haga el trabajo del otro, así que merece la pena ser explícito sobre la línea entre ellos.

Angular

Angular es el propietario de la aplicación

Todo lo relacionado con el lienzo: quién es el usuario, qué puede hacer y dónde va el resultado.

  • Enrutamiento
  • Autenticación
  • UI de la aplicación
  • Usuarios
  • Permisos
  • Facturación
  • Capa API
  • Estado de la aplicación
GrapesJS

GrapesJS es el propietario de la superficie de edición

Todo lo que hay dentro del lienzo: qué se puede arrastrar, seleccionar, estilizar y exportar.

  • Canvas
  • Componentes
  • Bloques
  • Gestor de estilos
  • Gestor de recursos
  • Comandos
  • Gestor de almacenamiento
  • Administrador de dispositivos

Angular es tu aplicación. GrapesJS es tu motor de edición visual.

Configuración

Instala GrapesJS en Angular

Una dependencia y una hoja de estilo. GrapesJS incluye su propio CSS, y sin él el editor se monta pero se renderiza como marcado sin estilo — que es la razón más común por la que una primera integración parece rota.

1 — Añadir la dependencia

npm install grapesjs

2 — Registrar la hoja de estilos

angular.jsonjson
// angular.json → projects.<app>.architect.build.options
{
  "styles": [
    "src/styles.css",
    "node_modules/grapesjs/dist/css/grapes.min.css"
  ]
}

Registrar la hoja de estilo en angular.json lo aplica globalmente. Si el editor vive en una ruta cargada de forma perezosa y prefieres no pagar por el CSS en todas partes, impórtalo dentro de la hoja de estilo propia del componente del editor.

¿Por qué ngAfterViewInit?

GrapesJS necesita un elemento real al que unirse, y mide ese elemento a medida que se inicializa. En ngOnInit la plantilla aún no se ha renderizado, por lo que una referencia @ViewChild sigue sin definir y la llamada falla por completo. ngAfterViewInit se ejecuta después de que Angular haya creado la vista del componente, que es el primer momento en que existe el contenedor. El mismo razonamiento explica la regla de altura en la hoja de estilos de abajo: un elemento sin altura resuelta produce un editor que inicializa sin error y no muestra nada.

Antes de escribir el componente

El contenedor no es tuyo

GrapesJS reemplaza el contenido del elemento que le entregas. No renderizes contenido de plantilla Angular dentro de ese elemento y no vincules a nada debajo de él.

Dale altura

El elemento anfitrión necesita una altura resuelta antes de la inicialización. Un contenedor de altura cero monta un editor que no puedes ver.

Una instancia por componente

Mantén una sola referencia al editor y destrúyela en ngOnDestroy. Dos instancias activas en una ruta se enfrentarán por atajos de teclado.

Se incluyen tipos

El paquete grapesjs envía sus propias definiciones TypeScript, por lo que Editor, Component y ProjectData son importables sin un paquete de @types separado.

Ejemplo de trabajo mínimo

Un editor Angular completo

Tres archivos, nada generado, nada oculto. Cópialos en una aplicación Angular y tienes un editor visual funcional en una ruta. Todo lo que aparece más adelante en esta página — almacenamiento, recursos, componentes personalizados, plugins — es una adición a este componente, no una reescritura de él.

editor.component.tsts
// editor.component.ts
import {
  AfterViewInit,
  Component,
  ElementRef,
  OnDestroy,
  ViewChild,
} from '@angular/core';
import grapesjs, { type Editor } from 'grapesjs';

@Component({
  selector: 'app-editor',
  templateUrl: './editor.component.html',
  styleUrl: './editor.component.css',
})
export class EditorComponent implements AfterViewInit, OnDestroy {
  // Resolved by the time ngAfterViewInit runs — that is the whole reason
  // initialisation lives there and not in ngOnInit.
  @ViewChild('editorHost') private host!: ElementRef<HTMLDivElement>;

  private editor?: Editor;

  ngAfterViewInit(): void {
    this.editor = grapesjs.init({
      container: this.host.nativeElement,
      height: '100%',
      width: 'auto',
      fromElement: false,
      // No persistence yet. Wire your own API before shipping — see Storage.
      storageManager: false,
      blockManager: {
        blocks: [
          {
            id: 'section',
            label: 'Section',
            category: 'Layout',
            content: '<section class="section"><h2>Headline</h2></section>',
          },
          {
            id: 'text',
            label: 'Text',
            category: 'Basic',
            content: { type: 'text', content: 'Edit me' },
          },
          { id: 'image', label: 'Image', category: 'Basic', content: { type: 'image' } },
        ],
      },
    });
  }

  ngOnDestroy(): void {
    // GrapesJS owns DOM nodes, document listeners and an undo stack. Angular
    // removes the host element; only destroy() releases the rest.
    this.editor?.destroy();
    this.editor = undefined;
  }
}
editor.component.htmlhtml
<!-- editor.component.html -->
<!-- GrapesJS takes this element over completely. Do not render Angular
     content inside it — the editor owns everything below #editorHost. -->
<div class="editor-shell">
  <div #editorHost class="editor-host"></div>
</div>
editor.component.csscss
/* editor.component.css */
.editor-shell {
  display: block;
  block-size: 100dvh;
}

/* GrapesJS measures its container at init. A host with no resolved height
   produces an editor that mounts successfully and renders nothing. */
.editor-host {
  block-size: 100%;
}

Esa es toda la integración. Las partes que merece la pena releer son los dos ganchos del ciclo de vida: la inicialización en ngAfterViewInit porque el contenedor tiene que existir primero, y destroy() en ngOnDestroy porque la eliminación de Angular del elemento anfitrión no libera lo que GrapesJS está adjunto al documento.

Carga el editor por su propia ruta

El editor es lo más pesado en la mayoría de aplicaciones que lo contienen con él. Ponerlo detrás de una ruta cargada perezosamente lo mantiene fuera del paquete inicial para todos los usuarios que nunca lo abren.

app.routes.tsts
// app.routes.ts — the editor is the heaviest route in the app. Load it last.
import type { Routes } from '@angular/router';

export const routes: Routes = [
  { path: '', loadComponent: () => import('./home/home.component').then((m) => m.HomeComponent) },
  {
    path: 'pages/:id/edit',
    loadComponent: () => import('./editor/editor.component').then((m) => m.EditorComponent),
  },
];
Ciclo de vida

Integración de GrapesJS con el ciclo de vida de Angular

La vida útil del editor debe estar vinculada a la del componente. Cuatro piezas hacen ese trabajo, y cada una falla de forma reconocible cuando falta.

@ViewChild

Obtiene una referencia al elemento contenedor. Consulta por variable de referencia de plantilla, no por selector CSS, así que una refactorización del marcado no puede romper la consulta en silencio.

ngAfterViewInit

Existe el primer gancho que se ejecuta después de la vista del componente. Inicializa aquí. Inicializar antes te da una referencia de elemento indefinida.

La instancia del editor

Manténlo en un campo privado, o en un servicio si algún componente de la barra de herramientas necesita activar comandos. Todo lo que hagas después pasa por ese único objeto.

ngOnDestroy

Llama a editor.destroy(). Elimina el DOM del editor, separa sus oyentes a nivel de documento y descarta la pila de deshacer. Saltártela es una filtración que sobrevive a la navegación.

No inicialices GrapesJS antes de que exista el contenedor del editor.

Este es el error que explica la mayoría de los informes "GrapesJS no funciona en Angular". En ngOnInit, y en cualquier constructor, la vista no se ha creado — la referencia del contenedor está indefinida y se lanza inicialización. Si el contenedor está dentro de un bloque @if o una vista diferida, ngAfterViewInit tampoco es suficiente: espera hasta que esa rama se haya renderizado antes de inicializar.

Detección de cambios

Detección de cambios en GrapesJS y Angular

GrapesJS conecta un gran número de oyentes a su lienzo y los lanza continuamente mientras un componente se arrastra o redimensiona. En una aplicación Angular basada en zonas, cada uno de esos oyentes programa un pase de detección de cambios, aunque nada en tu árbol de componentes haya cambiado.

editor.component.ts (extracto)ts
// editor.component.ts (excerpt)
import { Component, NgZone, inject, signal } from '@angular/core';

export class EditorComponent {
  private readonly zone = inject(NgZone);

  readonly selectedTag = signal('');
  readonly savedAt = signal<Date | null>(null);

  private mount(host: HTMLElement): void {
    // The canvas fires pointer events continuously while a block is being
    // dragged. Creating the editor outside the zone keeps those from
    // scheduling a change-detection pass on every frame.
    this.zone.runOutsideAngular(() => {
      const editor = grapesjs.init({ container: host, height: '100%' });

      // Re-enter only where editor state has to become Angular state.
      editor.on('component:selected', (component) => {
        this.zone.run(() => this.selectedTag.set(component.get('tagName') ?? ''));
      });

      editor.on('storage:end:store', () => {
        this.zone.run(() => this.savedAt.set(new Date()));
      });

      this.editor = editor;
    });
  }
}

// Zoneless applications (provideZonelessChangeDetection) have no zone to
// leave. Set signals from the same editor events and drop NgZone entirely.
  • Crea el editor dentro de NgZone.runOutsideAngular() para que la actividad en canvas no provoque la detección de cambios.
  • Vuelve a introducir con NgZone.run() solo en los manejadores donde el estado del editor debe convertirse en el estado Angular — una selección, una marca de tiempo de guardado, un error de validación.
  • Si nada en tu UI refleja el estado del editor, nunca necesitas volver a entrar.
  • En una aplicación sin zona (provideZonelessChangeDetection) no hay zona que salir: escribir en señales del mismo editor eventos y eliminar NgZone por completo.

Una regla general

Sal de la zona una vez, al inicializar. Reintroduce de forma estrecha, en los eventos específicos que escucha tu interfaz. Envolver cada callback de editor en NgZone.run() devuelve exactamente el cambio de detección que evitaste de la zona.

Renderizado en el lado del servidor

¿Funciona GrapesJS con Angular SSR?

El editor se ejecuta en el navegador. GrapesJS construye su lienzo a partir de DOM real — mide elementos, crea un iframe y adjunta oyentes de documentos — por lo que la inicialización debe ocurrir en un contexto de navegador y no debe ejecutarse durante el renderizado del servidor. Las rutas que simplemente muestran la salida publicada no se ven afectadas: es decir, HTML y CSS puros y se renderizan en el servidor como cualquier otra cosa.

editor.component.tsts
// editor.component.ts — browser-only initialisation
import {
  AfterViewInit,
  Component,
  ElementRef,
  OnDestroy,
  PLATFORM_ID,
  ViewChild,
  inject,
} from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import type { Editor } from 'grapesjs';

export class EditorComponent implements AfterViewInit, OnDestroy {
  @ViewChild('editorHost') private host!: ElementRef<HTMLDivElement>;

  private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
  private editor?: Editor;

  async ngAfterViewInit(): Promise<void> {
    if (!this.isBrowser) return;

    // A dynamic import keeps the editor out of the server bundle and off the
    // critical path of every route that never opens it.
    const { default: grapesjs } = await import('grapesjs');
    this.editor = grapesjs.init({ container: this.host.nativeElement });
  }

  ngOnDestroy(): void {
    this.editor?.destroy();
  }
}
  • Protege la inicialización para que solo se ejecute en el navegador — tanto la comprobación de plataforma como el gancho de renderizado de abajo hacen esto.
  • Importa la biblioteca dinámicamente para que nunca entre en el paquete del servidor.
  • Renderiza un marcador de posición del mismo tamaño durante el pase del servidor para evitar un cambio de disposición cuando aparezca el editor.
  • Las rutas publicadas que solo renderizan los HTML y CSS almacenados no necesitan ninguna protección.

La alternativa al gancho de renderizado

Las versiones recientes de Angular ofrecen un gancho de renderizado cuyas llamadas se ejecutan solo en plataformas de navegador y nunca en el servidor. Cuando está disponible, expresa la misma garantía sin verificación de plataforma, y se empareja de forma natural con consultas de vista basadas en señales.

editor.component.ts (gancho de renderizado)ts
// The same guard, expressed with the render hook instead of a platform check.
import { Component, ElementRef, afterNextRender, viewChild } from '@angular/core';
import type { Editor } from 'grapesjs';

@Component({ selector: 'app-editor', template: '<div #editorHost></div>' })
export class EditorComponent {
  private readonly host = viewChild.required<ElementRef<HTMLDivElement>>('editorHost');
  private editor?: Editor;

  constructor() {
    // afterNextRender callbacks run on browser platforms only — they never
    // execute on the server, so no isPlatformBrowser check is needed.
    afterNextRender(async () => {
      const { default: grapesjs } = await import('grapesjs');
      this.editor = grapesjs.init({ container: this.host().nativeElement });
    });
  }
}

Comprueba cuáles de estos soporta tu versión de Angular antes de elegir. Ambos patrones existen para hacer cumplir la misma regla: el editor se inicializa en el navegador y en ningún otro sitio. angular.dev

Anatomía

Crea un creador de páginas Angular con GrapesJS

Un creador de páginas no es una sola función. Son nueve subsistemas que deben coincidir entre sí, y GrapesJS incluye los nueve. Esta es la visión general; las decisiones a nivel de producto — precios, roles, plantillas, flujo de trabajo de publicación — pertenecen a la guía dedicada enlazada a continuación.

  • GrapesJS

    Canvas

    La superficie editable, renderizada en su propio iframe para que los estilos de página no puedan filtrarse en tu UI administrativo.

  • GrapesJS

    Componentes

    El modelo tipado detrás de cada elemento: qué es, qué acepta y qué ajustes expone.

  • GrapesJS

    Gestor de bloques

    La paleta de la que los usuarios arrastran. Un bloque es un fragmento de contenido con nombre asignado a un tipo de componente.

  • GrapesJS

    Gestor de estilos

    El panel de edición CSS, configurable hasta qué propiedades puede cambiar un usuario determinado.

  • Plugin

    Gestor de recursos

    Selección de imágenes y medios. El endpoint de subida detrás es tuyo, así que aquí los plugins suelen conectarlo a un proveedor.

  • Tu app Angular

    Gestor de almacenamiento

    Carga y guarda. GrapesJS define el contrato y llama a tu API — la persistencia en sí es de tu aplicación.

  • GrapesJS

    Comandos

    Acciones nombradas y llamables — deshacer, previsualizar, exportar y cualquier cosa que registres tú mismo para botones de la barra de herramientas.

  • GrapesJS

    Administrador de dispositivos

    Los puntos de interrupción responsivos del lienzo se pueden previsualizar y estilizar.

  • GrapesJS

    Paneles

    El propio Chrome del editor — botones, barras de herramientas y regiones, todos reemplazables si el editor está dentro de un shell existente.

¿Quién lo construye?GrapesJSPluginTu app Angular

Ocho de los nueve vienen con el editor o con un plugin. El que es inevitablemente tuyo es el almacenamiento — que también es el que debe conocer a tus usuarios, tus permisos y tu base de datos.

Crea un creador de páginas Angular completo
Donde se sitúa

El editor dentro de una aplicación Angular

Leído de fuera hacia dentro, esto es toda la integración. Todo lo que está encima de la instancia GrapesJS es Angular ordinario; todo lo que está debajo es el editor haciendo su propio trabajo en un iframe que nunca tienes que tocar.
  1. Angular application
  2. Editor route (lazy-loaded)
  3. EditorComponent
  4. GrapesJS instance
  5. Canvas iframe

Si el editor tiene que ser accesible desde otra parte de la aplicación — una barra de herramientas en el shell, una lista de páginas, un panel de vista previa — mueve la inicialización a un servicio inyectable y deja que los componentes se comuniquen con él en lugar de directamente con el editor.

El lienzo es un verdadero iframe. Eso es deliberado: los estilos de página no pueden filtrarse en tu interfaz de administración y los estilos de tu aplicación no pueden filtrarse en la página que se está editando, que es exactamente el aislamiento que buscas cuando ambos están diseñados por personas diferentes.

Producción

¿Qué genera GrapesJS?

Tres artefactos, y mezclarlos provoca una pérdida real de datos. Los datos del proyecto son el documento editable; HTML y CSS son el resultado publicado. No son intercambiables, porque la exportación es con pérdida por diseño.

Datos del proyecto

Un documento JSON que contiene componentes, estilos, páginas y recursos. Esto es lo que almacenas para que un editor pueda reabrir la página exactamente como la dejó. Trátala como la fuente de la verdad.

HTML

El marcado de la página tal como se va a servir. Guárdalo junto con los datos del proyecto para que la publicación nunca tenga que arrancar un editor, y para que una página pueda renderizarse aunque el editor no esté disponible.

CSS

Los estilos que el editor produjo para esa página se ajustaban a las reglas que gestiona. Sírvelo con el HTML — los dos son una sola salida dividida entre dos archivos.

output.tsts
// Three outputs, three different jobs. Do not use one where you need another.

const projectData = editor.getProjectData(); // editable state → store this
const html = editor.getHtml();               // markup         → publish this
const css = editor.getCss();                 // styles         → publish this

// Reopening a saved project restores components, styles, pages and assets.
// Re-parsing exported HTML does not — it loses everything not in the markup.
editor.loadProjectData(projectData);

Reparar HTML exportado de nuevo al editor no sustituye los datos del proyecto: pierde tipos de componentes, traits, estructura de página y cualquier otra cosa que no era expresable en el marcado. GrapesJS es un editor, no un CMS ni un backend — dónde van los artefactos y qué les ocurre después, es decisión de tu aplicación.

Persistencia

Guarda los proyectos GrapesJS en tu aplicación Angular

GrapesJS define el contrato; tú proporcionas los puntos finales. Registra un almacenamiento nombrado con dos métodos asíncronos y el editor los llamará — bajo demanda o automáticamente a medida que se acumulan las ediciones. Todo lo relacionado con autenticación, tenencia y permisos permanece donde debe estar, en tus servicios Angular y en tu API.
  1. Angular
  2. GrapesJS Storage
  3. HttpClient → REST API
  4. Your backend
  5. Database
project-storage.service.tsts
// project-storage.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
import type { Editor, ProjectData } from 'grapesjs';

@Injectable({ providedIn: 'root' })
export class ProjectStorage {
  private readonly http = inject(HttpClient);

  /** Registers a named storage the editor can then autosave into. */
  register(editor: Editor, projectId: string): void {
    editor.Storage.add('gjs-api', {
      load: () =>
        firstValueFrom(this.http.get<ProjectData>(`/api/projects/${projectId}`)),

      store: async (data) => {
        await firstValueFrom(
          this.http.put(`/api/projects/${projectId}`, {
            project: data,          // what the editor reopens
            html: editor.getHtml(), // what the site renders
            css: editor.getCss(),
          })
        );
      },
    });
  }
}
editor.component.ts (extracto)ts
// Point the editor at the storage you just registered.
this.storage.register(editor, projectId);

grapesjs.init({
  container: host,
  storageManager: {
    type: 'gjs-api',      // the name passed to Storage.add()
    autosave: true,
    autoload: true,
    stepsBeforeSave: 5,   // batch edits instead of a request per keystroke
  },
});

El editor nunca se comunica con tu base de datos. Llama a los dos métodos que registraste, que llaman a HttpClient, que llama a tu API bajo los mismos interceptores y autenticación que todas las demás solicitudes de la aplicación.

Lista de comprobación de persistencia

Cinco decisiones que tomar antes del primer salvamento

  1. 1
    Proyecto JSON

    Almacenar el documento editable

    Persiste la salida getProjectData() como el registro que se reabre un usuario. Guarda el HTML y CSS exportados en la misma escritura para que la publicación no tenga un segundo viaje de ida y vuelta.

  2. 2
    Guardado automático

    Escribe por lotes, no los transmitas

    el autosave con stepsBeforeSave convierte una ráfaga de ediciones en una sola petición. Ajusta el recuento de pasos a tu API, no a la demo por defecto.

  3. 3
    Carga

    Autoload, o carga explícitamente

    autoload permite que el editor obtenga al inicio. Cargar explícitamente te da un lugar para mostrar tu propio spinner y manejar un 403 como un error de aplicación en lugar de uno de editor.

  4. 4
    Versiones

    Conserva historial en el servidor

    La pila de deshacer permanece en el navegador y muere con la pestaña. Si los usuarios necesitan restaurar la página de ayer, eso es una tabla de versiones en tu base de datos, escrita en la tienda.

  5. 5
    Publicar

    Ahorro separado de la publicación

    Una partida guardada actualiza el borrador. La publicación promueve una versión específica a la ruta en vivo — un punto final distinto, con su propia comprobación de permisos.

Medios de comunicación

Gestionar imágenes y recursos

El gestor de recursos es un selector, no un servicio de archivos. Apúntalo a un punto final en tu aplicación Angular y PUBLICARÁ los archivos allí; todo lo que viene después de eso — validación, almacenamiento, redimensionamiento, entrega — es tuyo para implementar, lo que hace posible reutilizar la biblioteca multimedia que ya tienes.

editor.component.ts (extracto)ts
// Assets go to your endpoint, under your auth — GrapesJS only POSTs files.
grapesjs.init({
  container: host,
  assetManager: {
    upload: `/api/projects/${projectId}/assets`,
    uploadName: 'files',
    multiUpload: true,
    credentials: 'include',
    headers: { 'X-Requested-With': 'XMLHttpRequest' },
    // Off by default is the safer choice: base64 images end up inside saved
    // markup and make every page row grow without limit.
    embedAsBase64: false,
    // Returning false aborts the upload before the request leaves the browser.
    beforeUpload: (files: File[]) =>
      files.every((file) => file.size <= MAX_UPLOAD_BYTES && ALLOWED_TYPES.has(file.type)),
  },
});

editor.on('asset:upload:start', () => this.uploading.set(true));
editor.on('asset:upload:end', () => this.uploading.set(false));
editor.on('asset:upload:error', (error) => this.uploadError.set(String(error)));
  • Las subidas van a tu endpoint con tus cabeceras y credenciales, así que se aplica la misma autenticación que el resto de la app.
  • Valida antes de que la solicitud salga del navegador y vuelva a entrar en el servidor — la comprobación del lado del cliente es de conveniencia, no de control.
  • Devuelve las URLs almacenadas desde tu endpoint; el editor introduce esas URLs en la página, así que deben ser las que pretendes servir.
  • Desactiva la incrustación en base64 a menos que quieras imágenes en línea en el marcado guardado, donde hacen que cada página almacenada crezca sin límite.
  • Sirve recursos desde un CDN o un almacén de objetos y mantén el editor apuntando a la URL pública, no al origen de tu aplicación.
  • Carga grandes librerías a través de un endpoint paginado en lugar de entregar miles de recursos al editor al inicializar.

Nada de esto prescribe un backend concreto. S3, Cloudinary, un disco Laravel o una carpeta en un servidor cumplen todos el mismo contrato: aceptar un archivo, devolver una URL.

Ampliación del editor

Crear componentes personalizados

Registrar un tipo de componente es la forma en que el editor aprende sobre tu producto. Un tipo personalizado tiene su propio panel de configuración, sus propias reglas de arrastrar y soltar y un bloque en la paleta — así que los equipos de contenido trabajan con conceptos de tu dominio en lugar de divs en bruto.

pricing-table.type.tsts
// pricing-table.type.ts — an application-specific building block.
import type { Editor } from 'grapesjs';

export function registerPricingTable(editor: Editor): void {
  editor.Components.addType('pricing-table', {
    // Lets the editor recognise the type again when a saved page is reparsed.
    isComponent: (el) => el.classList?.contains('pricing-table'),
    model: {
      defaults: {
        tagName: 'section',
        attributes: { class: 'pricing-table' },
        droppable: false,
        // Traits become the right-hand settings panel for this component.
        traits: [
          { type: 'text', name: 'plan', label: 'Plan name' },
          { type: 'number', name: 'price', label: 'Price' },
          { type: 'checkbox', name: 'featured', label: 'Highlight' },
        ],
        components: `
          <h3 data-gjs-type="text">Team</h3>
          <p data-gjs-type="text">$29 / month</p>
        `,
      },
    },
  });

  // A block is how the component reaches the user: one entry in the palette.
  editor.Blocks.add('pricing-table', {
    label: 'Pricing table',
    category: 'Commerce',
    content: { type: 'pricing-table' },
  });
}

Componentes típicos específicos de la aplicación

  • Sección Hero
  • Tabla de precios
  • Tarjeta de producto
  • Formulario
  • Navegación
  • Testimonio
  • Cuadrícula de características
  • Cualquier cosa específica para tu SaaS

Dos cosas hacen útil un tipo personalizado. isComponent permite que el editor reconozca tu tipo de nuevo cuando se vuelve a analizar una página guardada, y traits se convierte en el panel de ajustes que un usuario no técnico realmente edita. Ambos merecen la pena escribirlos incluso para un componente simple.

Resultados

¿Qué puedes construir?

La misma integración sustenta todo esto. Lo que cambia entre ellos es qué bloques registras, qué estilos expones y dónde se publica la salida.

Extiéndelo

Extiende GrapesJS con plugins

Un plugin es una función que recibe la instancia del editor, así que nada aquí es específico de Angular — cualquier cosa publicada para GrapesJS funciona dentro de un componente Angular. Estos son los cuatro huecos que los equipos encuentran primero después de que el editor se monta.

Construye o adopta

¿Construir el editor tú mismo o usar GrapesJS?

Construir un editor visual es posible en Angular — es una cantidad de trabajo conocida, no imposible. La cuestión es qué partes quieres poseer durante toda la vida del producto.

FunciónConstrúyelo túGrapesJS
CanvasLo construyes túIncluido
Arrastrar y soltarLo construyes túIncluido
ComponentesLo construyes túIncluido
BloquesLo construyes túIncluido
EstilosLo construyes túIncluido
RecursosLo construyes túIncluido
AlmacenamientoLo construyes túExtensible — tú suministras los endpoints
PluginsConstruir un ecosistemaArquitectura de plugins
Integración de AngularNativo de tu appIntegrar mediante un componente
LicenciaTuyaBSD-3-Clause — $0 to use

"Construir" no es una crítica — todas estas son construibles. Es una declaración de quién la mantiene después.
Los hechos de esta tabla han vuelto a verificar 2026-09-02.

Angular te da el marco de la aplicación. GrapesJS te da el motor de edición visual.

Elección de integración

¿Necesito un wrapper Angular para GrapesJS?

No, y merece la pena entender por qué surge la pregunta. GrapesJS publica un wrapper oficial de React, así que los desarrolladores esperan razonablemente un equivalente a Angular. No hay uno. Eso deja tres enfoques, y para la mayoría de los equipos el primero es la respuesta correcta.

Recomendado

Integración directa

Escribe tú mismo el componente — el de esta página tiene aproximadamente treinta líneas. Tú eres el propietario del ciclo de vida, la estrategia de detección de cambios y la ruta de actualización, y no hay un tercero entre tu aplicación y el editor API.

  • No hay dependencias adicionales que se mantengan al día con las versiones de Angular
  • Acceso completo a todas las opciones de editor y eventos
  • Las mejoras a GrapesJS aterrizas inmediatamente
Terceros

Un envoltorio comunitario

Existen paquetes comunitarios que pueden acortar la primera hora. Evalúa uno como harías con cualquier dependencia: comprueba quién lo mantiene, qué versiones de Angular y GrapesJS declara y cuándo se publicó.

  • No mantenida por el proyecto GrapesJS
  • Añade una restricción de versión tanto en Angular como en GrapesJS
  • Vale la pena compararlo con la versión central actual antes de adoptarlo
  • ngx-grapesjs 21.0.0 · 2026-01-15 · peer grapesjs ^0.22.4
Para aplicaciones más grandes

Tu propio envolvente de servicio

Una vez que varios componentes necesitan el editor — una barra de herramientas, una lista de páginas, un panel de vista previa — traslada la inicialización a un servicio inyectable. Es una integración directa con una costura, y mantiene el editor fuera de tu árbol de componentes.

  • Un lugar que posee init y destruye
  • El estado del editor alcanza componentes como señales u observables
  • Se puede probar sin montar el editor

La organización GrapesJS mantiene un envoltorio React y ningún paquete Angular. Cualquier biblioteca Angular que encuentres es de terceros, por muy útil que sea — trátala como una opción de dependencia, no como parte de GrapesJS.

Arquitectura

Arquitectura de producción para GrapesJS + Angular

Una demo es un componente. Un producto tiene cuatro capas, y el editor es solo una de ellas. Esta es la forma en la que convergen la mayoría de los equipos una vez que el editor tiene usuarios reales detrás.

  1. Aplicación Angular

    La carcasa, el router y una ruta cargada de forma perezosa que posee el editor.

    • Shell de la aplicación
    • Router
    • Ruta del editor (carga diferida)
  2. Instancia GrapesJS

    Configurado una vez, al inicializar: qué componentes existen, cuáles se pueden arrastrar, qué se pueden estilizar.

    • Componentes
    • Bloques
    • Estilos
    • Recursos
    • Comandos
  3. Servicios de aplicación

    La capa Angular entre el editor y tu API — y el único lugar que las decisiones de autorización se toman en el lado del cliente.

    • Auth
    • Cliente API
    • Permisos
    • Publicación
  4. Backend

    Donde viven los artefactos y donde cada permiso se hace cumplir de verdad.

    • Proyectos
    • Recursos
    • Usuarios
    • Versiones

Nada en este diagrama es específico de GrapesJS excepto el segundo nivel. Ese es el punto: el editor es un componente en una aplicación Angular ordinaria, y el resto es la arquitectura que habrías construido de todas formas.

Mira cómo los equipos empaquetan esto como producto
Rendimiento

Consideraciones de rendimiento

El editor es un gran software que se ejecuta dentro de tu aplicación. Estos son los lugares donde eso realmente cuesta algo.

Inicializar solo cuando sea necesario

Crea el editor cuando se abra la ruta de edición, no al inicio de la aplicación. Los usuarios que nunca editan nunca deberían pagar por ello.

Carga perezosa en la ruta

Una importación dinámica mantiene a GrapesJS fuera del paquete inicial, que es la palanca más grande de esta lista.

Mantente fuera de la zona

Crea el editor con runOutsideAngular para que los eventos de puntero canvas no activen la detección de cambios en cada fotograma.

Enlazar la biblioteca de recursos

No le des al editor una biblioteca media completa en init. Pagina por tu API y carga los assets según los solicite el selector.

Observa el árbol de componentes

Las estructuras de página muy profundas o muy repetitivas hacen que la selección, el estilo y la desorganización sean más lentos. Prefiero componentes a envoltorios profundamente anidados.

Partidas de guardado por lotes

Usa stepsBeforeSave para que una ráfaga de ediciones se convierta en una sola petición en lugar de una petición por pulsación de tecla.

Ninguno de estos viene con un número prometido adjunto. Mide tu propia ruta de editor antes y después: el tamaño de cada efecto depende de tus bloques, tus páginas y tu API.

Seguridad

Consideraciones de seguridad

Un editor visual convierte a los usuarios en autores de HTML que tu aplicación luego sirve. Trata todo lo que sale del editor como entrada del usuario, porque eso es exactamente lo que es.

Sanitise publicó HTML

La salida del editor puede contener marcado arbitrario, incluidos scripts si se habilitan bloques de código personalizados. Limpia el lado servidor antes de que se sirva a alguien que no sea su autor.

Validar los recursos subidos

Comprueba el tipo y tamaño del servidor, no solo en beforeUpload. Almacena las subidas fuera de la raíz web y sírvelas desde un origen separado.

Autenticar los extremos de almacenamiento

Las rutas de carga y guardado son extremos API ordinarios. Necesitan la misma autenticación que todo lo demás en la aplicación.

Hacer cumplir permisos en el lado del servidor

Ocultar un botón de publicación en Angular UI es la presentación. La comprobación que importa es la del endpoint.

Nunca confíes en los datos del proyecto

El proyecto JSON almacenado puede ser manipulado. Valida el proceso al cargarlo en lugar de asumir que es el documento que escribiste.

Inquilinos de alcance explícitamente

En un producto multi-inquilino, se deriva el propietario del proyecto a partir de la sesión en el servidor — nunca a partir de un id en el cuerpo de la solicitud.

Antes de que envíes

Lista de verificación de producción Angular + GrapesJS

Doce cosas que separan una demo funcional de una ruta que se puede ofrecer a los usuarios reales.

  1. Instalar GrapesJS

    Dependencia añadida y la hoja de estilo registrada, globalmente o en el componente del editor.

    Lee la sección
  2. Inicializar después de la creación de la vista

    grapesjs.init() se ejecuta en ngAfterViewInit, contra un contenedor con una altura resuelta.

    Lee la sección
  3. Destruye el editor correctamente

    editor.destroy() en ngOnDestroy, verificado navegando de vuelta y vuelta sin filtraciones.

    Lee la sección
  4. Gestionar la inicialización solo del navegador

    Protegido para que nada funcione durante un render del servidor, con un marcador en su lugar.

    Lee la sección
  5. Conectar almacenamiento

    Un almacenamiento nombrado se registró, ajustó el autosave y el proyecto JSON se almacenó junto a HTML y CSS.

    Lee la sección
  6. Configurar recursos

    Las subidas apuntan a tu endpoint, el embedding de base64 se apaga, validación en ambos lados.

    Lee la sección
  7. Añadir componentes personalizados

    Los propios bloques de construcción de tu producto se registraron como tipos, con entradas de traits y paleta.

    Lee la sección
  8. Añade los plugins que necesites

    Bloques, texto enriquecido y exportación elegidos deliberadamente en lugar de acumulados.

    Lee la sección
  9. Permisos de implementación

    Quién puede editar, quién puede publicar — lo que se impone en el servidor, reflejado en el UI.

    Lee la sección
  10. Sanitize contenido publicado

    Sanitización en el servidor entre guardar una página y servirla a los visitantes.

    Lee la sección
  11. Errores del editor de monitores

    Las excepciones del editor se reportan en el mismo lugar que el resto de tus errores Angular.

  12. Edición responsiva a pruebas

    El administrador de dispositivos se ejercía en un dispositivo táctil, no solo en la ventana del navegador de escritorio.

    Lee la sección
FAQ

GrapesJS y Angular: preguntas frecuentes

Siguiente paso

Construye tu editor visual Angular con GrapesJS

Empieza con el editor visual de código abierto, conéctalo a tu aplicación Angular y amplíala con los plugins e integraciones que tu producto necesite.

Desarrollador

Empieza

Instala el paquete, copia el componente y ten un editor funcionando en una ruta Angular esta tarde.

Empieza
Ampliación

Explorar plugins

Bloques, almacenamiento, texto enriquecido, recursos, exportación y correo electrónico — las piezas que de otro modo estarías escribiendo tú mismo.

Explorar plugins
Equipo

Planifica el producto

Define el ámbito del editor como una característica de tu aplicación: roles, plantillas, publicación y versión.

Empieza un resumen

Construye la aplicación con Angular. Construye el editor visual con GrapesJS. Extiende la aplicación con plugins GJS.Market.