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
PageKit: el creador de sitios GrapesJS autoalojado, con el código fuente incluido. Obtener acceso anticipado
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.
26k+
Estrellas en GitHub
1.4M+
Descargas de npm por mes
100+
plugins en GJS.Market
$0
Tasa de licencia
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.
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.
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.
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.
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 trabajoGrapesJS 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.
Todo lo relacionado con el lienzo: quién es el usuario, qué puede hacer y dónde va el resultado.
Todo lo que hay dentro del lienzo: qué se puede arrastrar, seleccionar, estilizar y exportar.
Angular es tu aplicación. GrapesJS es tu motor de edición visual.
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 grapesjs2 — Registrar la hoja de estilos
// 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.
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
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.
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.
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.
El paquete grapesjs envía sus propias definiciones TypeScript, por lo que Editor, Component y ProjectData son importables sin un paquete de @types separado.
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.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.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.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.
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.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),
},
];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.
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.
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.
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.
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.
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 (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.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.
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.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();
}
}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.
// 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
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.
La superficie editable, renderizada en su propio iframe para que los estilos de página no puedan filtrarse en tu UI administrativo.
El modelo tipado detrás de cada elemento: qué es, qué acepta y qué ajustes expone.
La paleta de la que los usuarios arrastran. Un bloque es un fragmento de contenido con nombre asignado a un tipo de componente.
El panel de edición CSS, configurable hasta qué propiedades puede cambiar un usuario determinado.
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.
Carga y guarda. GrapesJS define el contrato y llama a tu API — la persistencia en sí es de tu aplicación.
Acciones nombradas y llamables — deshacer, previsualizar, exportar y cualquier cosa que registres tú mismo para botones de la barra de herramientas.
Los puntos de interrupción responsivos del lienzo se pueden previsualizar y estilizar.
El propio Chrome del editor — botones, barras de herramientas y regiones, todos reemplazables si el editor está dentro de un shell existente.
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 completoSi 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.
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.
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.
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.
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.
// 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.
// 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(),
})
);
},
});
}
}// 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.
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.
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.
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.
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.
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.
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.
// 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)));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.
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.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
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.
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.
Una experiencia visual de edición de página dentro de tu aplicación Angular, con tus propios bloques, tus propias restricciones de marca y tu propio flujo de publicación.
Crea un creador de páginas Angular completoDeja que los equipos de contenido editen las páginas visualmente en lugar de enviar tickets. El editor se convierte en una vía de la aplicación de administración que ya usan.
Véase el patrón CMS sin cabezaSecciones y diseños de marketing reutilizables, montados por un equipo de marketing sin necesidad de despliegue para cada cambio de texto.
Patrones para creadores de páginas de aterrizajeCrea y gestiona las plantillas de correo electrónico visualmente, con el preset MJML generando marcado que sobrevive a los clientes de correo reales.
GrapesJS para correo electrónicoUn editor de marca dentro de tu producto SaaS, con el Chrome del editor reemplazado para que se lea como tu interfaz en lugar de una herramienta de terceros.
Etiqueta blanca al editorOfrece a los usuarios edición visual mientras tu aplicación mantiene el control sobre el HTML y CSS que acaban en la base de datos.
Aprende a construir un generador de arrastre y solta HTMLUn 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.
Lo que arrastran los usuarios. Un editor básico casi no tiene nada en la paleta, así que esto es lo primero que añade cada integración.
Explora la categoríaLa paleta inicial — los bloques que un nuevo editor no tiene en la primera ejecución.
Tailwind asignaturas en el editor, si tu app de Angular ya incluye Tailwind.
Bootstrap 5 componentes como bloques arrastrables.
Secciones de cabecera configurables en lugar de marcado hecho a mano.
Persistencia, autosave y gestión multi-proyecto — la capa alrededor del contrato de almacenamiento de la sección anterior.
Explora la categoríaVarios proyectos en un solo editor, con una lista de proyectos UI.
Persistencia local en el navegador — útil para borradores y edición offline.
Recupera trabajo no guardado tras un fallo o una navegación accidental.
Almacenamiento respaldado por una instancia Directus en lugar de un endpoint personalizado.
Mejor edición de texto en línea y componentes reales de formulario. Ambas son capacidades que los usuarios asumen que están integradas.
Explora la categoríaSustituye el editor de texto enriquecido integrado por CKEditor 5.
Sustituye el editor de texto enriquecido integrado por TinyMCE 6.
Controles de formato extra en la barra de herramientas de texto enriquecido predeterminada.
Componentes de formulario con entradas reales, traits de validación y ajustes de envío.
Proveedores multimedia, formatos de exportación y el preajuste de correo electrónico, para las superficies en las que crece un constructor.
Explora la categoríaCloudinary como backend de recursos, incluyendo transformaciones.
Exporta la página como un código postal descargable de HTML, CSS y recursos.
MJML bloquea para que el mismo editor pueda producir marcado seguro para correo electrónico.
La accesibilidad y el SEO se revisan dentro del editor antes de publicar una página.
Categorías de plugins
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ón | Constrúyelo tú | GrapesJS |
|---|---|---|
| Canvas | Lo construyes tú | Incluido |
| Arrastrar y soltar | Lo construyes tú | Incluido |
| Componentes | Lo construyes tú | Incluido |
| Bloques | Lo construyes tú | Incluido |
| Estilos | Lo construyes tú | Incluido |
| Recursos | Lo construyes tú | Incluido |
| Almacenamiento | Lo construyes tú | Extensible — tú suministras los endpoints |
| Plugins | Construir un ecosistema | Arquitectura de plugins |
| Integración de Angular | Nativo de tu app | Integrar mediante un componente |
| Licencia | Tuya | BSD-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.
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.
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.
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ó.
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.
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.
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.
La carcasa, el router y una ruta cargada de forma perezosa que posee el editor.
Configurado una vez, al inicializar: qué componentes existen, cuáles se pueden arrastrar, qué se pueden estilizar.
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.
Donde viven los artefactos y donde cada permiso se hace cumplir de verdad.
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 productoEl editor es un gran software que se ejecuta dentro de tu aplicación. Estos son los lugares donde eso realmente cuesta algo.
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.
Una importación dinámica mantiene a GrapesJS fuera del paquete inicial, que es la palanca más grande de esta lista.
Crea el editor con runOutsideAngular para que los eventos de puntero canvas no activen la detección de cambios en cada fotograma.
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.
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.
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.
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.
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.
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.
Las rutas de carga y guardado son extremos API ordinarios. Necesitan la misma autenticación que todo lo demás en la aplicación.
Ocultar un botón de publicación en Angular UI es la presentación. La comprobación que importa es la del endpoint.
El proyecto JSON almacenado puede ser manipulado. Valida el proceso al cargarlo en lugar de asumir que es el documento que escribiste.
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.
Doce cosas que separan una demo funcional de una ruta que se puede ofrecer a los usuarios reales.
Dependencia añadida y la hoja de estilo registrada, globalmente o en el componente del editor.
Lee la seccióngrapesjs.init() se ejecuta en ngAfterViewInit, contra un contenedor con una altura resuelta.
Lee la seccióneditor.destroy() en ngOnDestroy, verificado navegando de vuelta y vuelta sin filtraciones.
Lee la secciónProtegido para que nada funcione durante un render del servidor, con un marcador en su lugar.
Lee la secciónUn almacenamiento nombrado se registró, ajustó el autosave y el proyecto JSON se almacenó junto a HTML y CSS.
Lee la secciónLas subidas apuntan a tu endpoint, el embedding de base64 se apaga, validación en ambos lados.
Lee la secciónLos propios bloques de construcción de tu producto se registraron como tipos, con entradas de traits y paleta.
Lee la secciónBloques, texto enriquecido y exportación elegidos deliberadamente en lugar de acumulados.
Lee la secciónQuién puede editar, quién puede publicar — lo que se impone en el servidor, reflejado en el UI.
Lee la secciónSanitización en el servidor entre guardar una página y servirla a los visitantes.
Lee la secciónLas excepciones del editor se reportan en el mismo lugar que el resto de tus errores Angular.
El administrador de dispositivos se ejercía en un dispositivo táctil, no solo en la ventana del navegador de escritorio.
Lee la secciónEmpieza 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.
Instala el paquete, copia el componente y ten un editor funcionando en una ruta Angular esta tarde.
EmpiezaBloques, almacenamiento, texto enriquecido, recursos, exportación y correo electrónico — las piezas que de otro modo estarías escribiendo tú mismo.
Explorar pluginsDefine el ámbito del editor como una característica de tu aplicación: roles, plantillas, publicación y versión.
Empieza un resumenConstruye la aplicación con Angular. Construye el editor visual con GrapesJS. Extiende la aplicación con plugins GJS.Market.