Angular besitzt die Anwendung
Alles rund um die Canvas: wer der Nutzer ist, was er tun darf und wohin das Ergebnis führt.
- Routing
- Authentifizierung
- Anwendungs-UI
- Nutzer
- Berechtigungen
- Abrechnung
- API-Schicht
- Anwendungszustand
PageKit — der selbst gehostete GrapesJS-Website-Builder, als Quellcode. Early Access sichern
Integrieren Sie den Open-Source-Visual Editor GrapesJS in Angular-Anwendungen, um Drag-and-Drop-Seiteneditoren, CMS-Editoren, Landingpage-Builder, E-Mail-Editoren und White-Label-Visual-Editing-Erlebnisse zu erstellen.
26k+
GitHub-Sterne
1.4M+
npm-Downloads / Monat
100+
Plugins auf GJS.Market
$0
Lizenzgebühr
Der Standard-GrapesJS-Build – der Editor, den du von 'GrapesJS.init()' mit dem Standard-Preset bekommst, bevor ein Plugin hinzugefügt wird. Ziehe einen Block aus dem rechten Panel, wähle ihn aus und bearbeite seine Typografie sowie Abstände im Style-Manager.
Lädt eine Drittanbieter-Seite von grapesjs.com. Es wird nichts angefordert, bis man klickt.
Das sind Live-GrapesJS-Builds, keine Angular-Demos – und genau das ist der Punkt. GrapesJS rendert in ein Container-Element, das es vollständig besitzt, sodass die Canvas, Blöcke, Style-Manager, responsive Previews, Asset Picker und Undo-History, die du unten anführst, sich identisch verhalten, sobald eine Angular-Komponente diesen Container hostet.
Angular treibt deine Anwendung an. GrapesJS ermöglicht das visuelle Bearbeitungserlebnis. Es kommt heraus, dass HTML, CSS und ein Projekt-JSON-Dokument entstehen, das dein eigenes Backend speichert.
GrapesJS ist framework-agnostisch und kann in Angular-Anwendungen integriert werden. Du initialisierst den Editor über Angular-Lebenszyklus-Hooks, behältst die Editor-Instanz unter deiner Kontrolle, verbindest GrapesJS-Ereignisse mit der Angular-Anwendungslogik und speicherst Projektdaten über dein eigenes Backend.
Es gibt keinen offiziellen Angular-Wrapper. Das GrapesJS-Projekt unterhält einen React-Wrapper und kein Angular-Äquivalent, sodass eine Angular-Integration bedeutet, eine Komponente zu schreiben – ungefähr die dreißig Zeilen im untenstehenden Beispiel – oder ein Drittanbieter-Paket nach seinen eigenen Leistungen zu bewerten.
Springen wir zum ArbeitsbeispielGrapesJS ist die visuelle Bearbeitungs-Engine. Angular ist das Anwendungsframework. Fast jedes Integrationsproblem, auf das Leute stoßen, entstehen daher, dass man erwartet, dass einer von ihnen die Arbeit des anderen erledigt, daher lohnt es sich, die Grenze zwischen ihnen explizit zu benennen.
Alles rund um die Canvas: wer der Nutzer ist, was er tun darf und wohin das Ergebnis führt.
Alles innerhalb der Canvas: was gezogen, ausgewählt, gestaltet und exportiert werden kann.
Angular ist deine Anwendung. GrapesJS ist deine visuelle Bearbeitungsmaschine.
Eine Abhängigkeit und ein Stylesheet. GrapesJS liefert sein eigenes CSS aus, und ohne es mountet der Editor ein, rendert aber als unstyletes Markup – was der häufigste Grund ist, warum eine erste Integration kaputt aussieht.
1 — Addiere die Abhängigkeit
npm install grapesjs2 — Stylesheet registrieren
// angular.json → projects.<app>.architect.build.options
{
"styles": [
"src/styles.css",
"node_modules/grapesjs/dist/css/grapes.min.css"
]
}Das Registrieren des Stylesheets in angular.json wendet es global an. Wenn der Editor auf einer faul geladenen Route lebt und du nicht überall für CSS bezahlen möchtest, importiere es stattdessen im eigenen Stylesheet der Editor-Komponente.
GrapesJS benötigt ein echtes Element zum Anhängen und misst dieses Element beim Initialisieren. In ngOnInit wurde die Vorlage noch nicht gerendert, daher ist eine @ViewChild-Referenz noch undefiniert und der Aufruf schlägt komplett fehl. ngAfterViewInit läuft, nachdem Angular die Ansicht der Komponente erstellt hat, was der erste Moment ist, in dem der Container existiert. Die gleiche Begründung erklärt die Höhenregel im untenstehenden Stylesheet: Ein Element ohne aufgelöste Höhe erzeugt einen Editor, der fehlerfrei initialisiert und nichts anzeigt.
Bevor du die Komponente schreibst
GrapesJS ersetzt den Inhalt des Elements, das du ihm übergibst. Rendere Angular-Template-Inhalte nicht innerhalb dieses Elements und binde dich nicht an etwas darunter.
Das Host-Element benötigt eine festgelegte Höhe vor der Initialisierung. Ein Container mit null Höhe montiert einen Editor, den du nicht sehen kannst.
Behalte einen einzigen Verweis auf den Editor und zerstöre ihn in ngOnDestroy. Zwei Live-Instanzen auf einer Route kämpfen um Tastenkombinationen.
Das grapesjs-Paket enthält eigene TypeScript-Definitionen, sodass Editor, Component und ProjectData ohne separates @types-Paket importierbar sind.
Drei Dateien, nichts generiert, nichts versteckt. Kopieren Sie sie in eine Angular-Anwendung und Sie haben einen funktionierenden visuellen Editor auf einer Route. Alles später auf dieser Seite – Speicher, Assets, benutzerdefinierte Komponenten, Plugins – ist eine Ergänzung zu dieser Komponente, keine Neuschreibung davon.
// 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%;
}Das ist die gesamte Integration. Die Teile, die es wert sind, noch einmal durchzulesen, sind die beiden Lebenszyklus-Hooks: die Initialisierung in ngAfterViewInit, weil der Container zuerst existieren muss, und destroy() in ngOnDestroy, weil das Entfernen des Hostelements durch Angular nicht das an das Dokument angehängte GrapesJS freigibt.
Der Editor ist das schwerste in den meisten Anwendungen, die einen enthalten. Wenn man ihn hinter eine faul geladene Route legt, bleibt er für jeden Nutzer, der ihn nie öffnet, aus dem ursprünglichen Bundle heraus.
// 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),
},
];Die Lebensdauer des Editors muss an die der Komponenten gebunden sein. Vier Teile übernehmen diese Arbeit, und jedes von ihnen versagt auf erkennbare Weise, wenn es fehlt.
Erhält eine Referenz auf das Container-Element. Abfrage anhand der Vorlagenreferenzvariable, nicht über den CSS-Selektor, damit ein Refactor des Markups die Suche nicht stillschweigend unterbrechen kann.
Der erste Hook, der nach der Ansicht der Komponente läuft, existiert. Initialisieren Sie hier. Das frühere Initialisieren ergibt eine undefinierte Elementreferenz.
Halte es in einem privaten Feld oder in einem Service, falls eine Toolbar-Komponente anderswo Befehle auslösen muss. Alles, was du danach machst, läuft über dieses eine Objekt.
Ruf editor.destroy() auf. Es entfernt das DOM des Editors, trennt die Dokumenten-Listener und verwirft den Rückgängig-Stapel. Überspringt man es, entsteht ein Leck, das die Navigation übersteht.
Initialisieren Sie GrapesJS nicht, bevor der Editor-Container existiert.
Dies ist der Fehler, der die meisten "GrapesJS funktioniert nicht in Angular"-Berichte ausmacht. In ngOnInit und in jedem Konstruktor wurde die Ansicht nicht erstellt – die Containerreferenz ist undefiniert und die Initialisierung wird geworfen. Wenn sich der Container in einem @if-Block oder einer deferred View befindet, reicht ngAfterViewInit ebenfalls nicht aus: Warte, bis dieser Branch tatsächlich gerendert ist, bevor du initialisierst.
GrapesJS verbindet eine große Anzahl von Zuhörern mit seiner Canvas und feuert sie kontinuierlich, während eine Komponente gezogen oder verkleinert wird. In einer zonenbasierten Angular-Anwendung plant jeder dieser Zuhörer einen Änderungserkennungsdurchlauf, obwohl sich in Ihrem Komponentenbaum nichts geändert hat.
// 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.Verlasse die Zone einmal bei der Initialisierung. Tritt knapp wieder ein, bei den spezifischen Ereignissen, die dein Interface abhört. Das Wrappen jedes Editor-Callbacks in NgZone.run() gibt genau das Änderungserkennungs-Churn zurück, das du aus der Zone verlassen hast, um es zu vermeiden.
Der Editor läuft im Browser. GrapesJS baut seine Canvas aus echtem DOM – es misst Elemente, erstellt ein iframe und hängt Dokumentlistener an – daher muss die Initialisierung im Browserkontext erfolgen und darf nicht während eines Serverrenderings ausgeführt werden. Routen, die lediglich veröffentlichte Ausgaben anzeigen, bleiben unbeeinträchtigt: das ist einfaches HTML und CSS und rendert auf dem Server wie alles andere.
// 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();
}
}Neuere Angular-Versionen bieten einen Render-Hook, dessen Rückrufe nur auf Browserplattformen und niemals auf dem Server laufen. Wo er verfügbar ist, gibt er dieselbe Garantie ohne Plattformprüfung und passt natürlich zu signalbasierten View-Abfragen.
// 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 });
});
}
}Überprüfe, welche dieser Varianten deine Angular-Version unterstützt, bevor du dich entscheidest. Beide Muster existieren, um dieselbe Regel durchzusetzen – der Editor initialisiert im Browser und sonst nirgendwo. angular.dev
Ein Page Builder ist nicht ein einziges Feature. Es sind neun Subsysteme, die miteinander übereinstimmen müssen, und GrapesJS liefert alle neun aus. Das ist der Überblick; die Produktentscheidungen – Preisgestaltung, Rollen, Vorlagen, Veröffentlichungsworkflow – gehören zum unten verlinkten speziellen Leitfaden.
Die editierbare Oberfläche, die in eigenem iframe gerendert wird, sodass Seitenstile nicht in dein Admin-UI gelangen können.
Das typisierte Modell hinter jedem Element: was es ist, was es akzeptiert und welche Einstellungen es offenlegt.
Die Palettenbenutzer ziehen von . Ein Block ist ein benannter Inhalt, der einem Komponententyp zugeordnet wird.
Das CSS-Bearbeitungspanel, konfigurierbar bis hin dazu, welche Eigenschaften ein bestimmter Benutzer ändern darf.
Bild- und Medienauswahl. Der dahinterliegende Upload-Endpunkt gehört dir, daher verbinden Plugins hier meist mit einem Anbieter.
Laden und speichern. GrapesJS definiert den Vertrag und ruft dein API auf – die Persistenz selbst gehört deiner Anwendung.
Benannte, aufrufbare Aktionen – rückgängig, vorschauen, exportieren und alles, was du für die Symbolleiste registrierst.
Die responsiven Breakpoints, an denen die Canvas vorschauen und gestaltet werden können.
Der eigene Chrome des Editors – Buttons, Toolbars und Regionen, alle austauschbar, wenn der Editor in einer bestehenden Shell sitzt.
Acht der neun werden mit dem Editor oder einem Plugin ausgeliefert. Das, was unvermeidlich dir gehört, ist der Speicher – der auch über deine Nutzer, deine Berechtigungen und deine Datenbank Bescheid wissen muss.
Baue einen kompletten Angular-SeitenbauerWenn der Editor von einem anderen Ort in der Anwendung erreichbar sein muss – einer Symbolleiste in der Shell, einer Seitenliste, einem Vorschaufenster – verschieben Sie die Initialisierung in einen injizierbaren Dienst und lassen Sie Komponenten mit diesem kommunizieren, anstatt direkt mit dem Editor.
Die Canvas ist ein echter iframe. Das ist absichtlich: Seitenstile dürfen nicht in deine Admin-Oberfläche gelangen und deine Anwendungsstile dürfen nicht auf die bearbeitete Seite gelangen, was genau die Isolation ist, die du willst, wenn beide von verschiedenen Leuten entworfen wurden.
Drei Artefakte, und deren Vermischung führt zu echtem Datenverlust. Projektdaten sind das editierbare Dokument; HTML und CSS sind das veröffentlichte Ergebnis. Sie sind nicht austauschbar, da der Export von Natur aus verlustbehaftet ist.
Ein JSON-Dokument, das Komponenten, Stile, Seiten und Assets enthält. Das ist das, was du speicherst, damit ein Editor die Seite genau so öffnen kann, wie sie gelassen wurde. Behandle es als Quelle der Wahrheit.
Das Markup für die Seite, wie sie ausgeliefert wird. Speichere es zusammen mit den Projektdaten, sodass beim Veröffentlichen kein Editor starten muss und eine Seite auch dann rendern kann, wenn der Editor nicht verfügbar ist.
Die Stile, die der Redakteur für diese Seite erstellt hat, angepasst an die von ihm verwalteten Regeln. Serviere es mit dem HTML – die beiden sind ein Output, aufgeteilt auf zwei Dateien.
// 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);Das Reparsen von exportiertem HTML in den Editor ist kein Ersatz für Projektdaten: Es verliert Komponententypen, traits, Seitenstruktur und alles andere, was im Markup nicht ausdrückbar war. GrapesJS ist ein Editor, kein CMS oder ein Backend – wohin die Artefakte gelangen und was danach mit ihnen passiert, ist die Entscheidung Ihrer Anwendung.
// 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
},
});Der Editor kommuniziert nie mit deiner Datenbank. Er ruft die beiden von dir registrierten Methoden auf, die HttpClient aufrufen, der dein API unter denselben Interceptoren und der gleichen Authentifizierung wie jede andere Anfrage in der Anwendung aufruft.
Speichere das editierbare Dokument
Behalten Sie die getProjectData()-Ausgabe bei, während der Datensatz eines Benutzers wieder geöffnet wird. Speichern Sie die exportierten HTML und CSS im selben Schreibabschnitt, sodass die Veröffentlichung keinen zweiten Roundtrip hat.
Batch-Writes, nicht streamen
autosave mit stepsBeforeSave verwandelt einen Schnitt in eine einzige Anfrage. Stellen Sie die Schrittzahl auf Ihr API ein, nicht auf die Demo-Standardeinstellung.
Autoload, oder explizit laden
autoload lässt den Editor beim Start abrufen. Das explizit Laden gibt dir stattdessen einen Ort, um deinen eigenen Spinner anzuzeigen und einen 403 als Anwendungsfehler statt als Editor-Fehler zu behandeln.
Verlauf serverseitig bewahren
Der Rückgängig-Stack lebt im Browser und stirbt mit dem Tab. Wenn Nutzer die Seite von gestern wiederherstellen müssen, ist das eine Versionstabelle in deiner Datenbank, die im Store geschrieben wurde.
Getrenntes Sparen und Veröffentlichen
Ein Speicherstand aktualisiert den Entwurf. Das Veröffentlichen fördert eine bestimmte Version auf die Live-Route – einen eigenständigen Endpunkt mit eigener Berechtigungskontrolle.
Der Asset Manager ist ein Picker, kein Dateidienst. Richten Sie ihn auf einen Endpunkt in Ihrer Angular-Anwendung und er wird die Dateien dort POSTEN; alles danach – Validierung, Speicherung, Größenänderung, Auslieferung – steht dir zur Verfügung, was es ermöglicht, die bereits vorhandene Medienbibliothek wiederzuverwenden.
// 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)));Nichts davon schreibt ein bestimmtes Backend vor. S3, Cloudinary, eine Laravel-Festplatte oder ein Ordner auf einem Server erfüllen alle denselben Vertrag: eine Datei akzeptieren, eine URL zurückgeben.
Das Registrieren eines Komponententyps ist der Weg, wie der Editor mehr über dein Produkt erfährt. Ein benutzerdefinierter Typ erhält ein eigenes Einstellungspanel, eigene Drag-and-Drop-Regeln und einen Block in der Palette – so arbeiten Content-Teams mit Konzepten aus deiner Domain statt mit rohen Divs.
// 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' },
});
}Typische anwendungsspezifische Komponenten
Zwei Dinge machen einen benutzerdefinierten Typ nützlich. isComponent ermöglicht es dem Editor, deinen Typ erneut zu erkennen, wenn eine gespeicherte Seite neu geparst wird, und traits wird zum Einstellungspanel, das ein nicht-technischer Nutzer tatsächlich bearbeitet. Beides lohnt sich, selbst für eine einfache Komponente geschrieben zu werden.
Die gleiche Integration liegt all diesen zugrunde. Was sich zwischen ihnen ändert, ist, welche Blöcke man registriert, welche Stile man freilegt und wo das Output veröffentlicht wird.
Ein visuelles Bearbeitungserlebnis innerhalb Ihrer Angular-Anwendung – mit eigenen Blöcken, eigenen Markenbeschränkungen und eigenem Veröffentlichungsfluss.
Baue einen kompletten Angular-SeitenbauerLassen Sie Content-Teams Seiten visuell bearbeiten, anstatt Tickets zu erstellen. Der Editor wird zu einem Weg der bereits verwendeten Admin-Anwendung.
Siehe das kopflose CMS-MusterWiederverwendbare Marketingabschnitte und -layouts, zusammengestellt von einem Marketingteam ohne Deploy für jede Textänderung.
Muster für Landingpage-BuilderBaue und verwalte E-Mail-Vorlagen visuell, wobei das MJML-Preset ein Markup erzeugt, das echte E-Mail-Clients überlebt.
GrapesJS für E-MailEin gebrandeter Editor in deinem SaaS-Produkt, bei dem der Chrome des Editors ersetzt wurde, sodass er sich wie deine Benutzeroberfläche und nicht als Drittanbieter-Tool lest.
White-label den EditorGeben Sie den Nutzern visuelle Bearbeitung, während Ihre Anwendung die Kontrolle über HTML und CSS behält, die in der Datenbank landen.
Lerne, wie man einen HTML-Drag-and-Drop-Builder bautEin Plugin ist eine Funktion, die die Editor-Instanz empfängt, daher ist hier nichts Angular-spezifisch – alles, was für GrapesJS veröffentlicht wird, funktioniert innerhalb einer Angular-Komponente. Das sind die vier Lücken, die Teams zuerst finden, nachdem der Editor gemountet ist.
Was die Nutzer ziehen. Ein blanker Editor hat fast nichts in der Palette, daher ist das das Erste, was jede Integration hinzufügt.
Durchstöbern Sie die KategorieDie Startpalette – die Blöcke, die einem neuen Editor beim ersten Durchlauf fehlen.
Tailwind-Kurse im Editor, falls deine Angular-App bereits Tailwind ausliefert.
Bootstrap 5-Komponenten als ziehbare Blöcke.
Konfigurierbare Header-Abschnitte statt handgefertigter Markup.
Persistenz, autosave und Multi-Projektmanagement – die Ebene um den Speichervertrag aus dem obigen Abschnitt.
Durchstöbern Sie die KategorieMehrere Projekte in einem Editor, mit einer Projektliste UI.
Lokale Persistenz im Browser – nützlich für Entwürfe und Offline-Bearbeitung.
Stellt nicht gespeicherte Arbeit nach einem Absturz oder einer versehentlichen Navigation wieder her.
Speicher, der von einer Directus-Instanz unterstützt wird, statt eines maßgeschneiderten Endpunkts.
Bessere Inline-Textbearbeitung und reale Formularkomponenten. Beides sind Funktionen, von denen Nutzer annehmen, dass sie eingebaut sind.
Durchstöbern Sie die KategorieErsetzt den eingebauten Rich-Text-Editor durch CKEditor 5.
Ersetzt den eingebauten Rich-Text-Editor durch TinyMCE 6.
Zusätzliche Formatierungssteuerungen auf der Standard-Rich-Text-Symbolleiste.
Formularkomponenten mit echten Eingaben, Validierungs-traits und Einreichungseinstellungen.
Medienanbieter, Exportformate und das E-Mail-Preset für die Oberflächen, in die ein Builder wächst.
Durchstöbern Sie die KategorieCloudinary als Asset-Backend, einschließlich Transformationen.
Exportiert die Seite als herunterladbare ZIP von HTML, CSS und Assets.
MJML blockiert, sodass derselbe Editor e-mail-sichere Markup-Arbeiten erzeugen kann.
Barrierefreiheit und SEO überprüfen im Editor, bevor eine Seite veröffentlicht wird.
Plugin-Kategorien
Einen visuellen Editor zu erstellen ist in Angular möglich – es ist eine bekannte Menge an Arbeit, keine unmögliche. Die Frage ist, welche Teile davon du für die Lebensdauer des Produkts besitzen möchtest.
| Funktion | Selbst bauen | GrapesJS |
|---|---|---|
| Canvas | Selbst bauen | Enthalten |
| Drag & Drop | Selbst bauen | Enthalten |
| Komponenten | Selbst bauen | Enthalten |
| Blöcke | Selbst bauen | Enthalten |
| Styling | Selbst bauen | Enthalten |
| Assets | Selbst bauen | Enthalten |
| Storage | Selbst bauen | Erweiterbar – du lieferst die Endpunkte |
| Plugins | Baue ein Ökosystem auf | Plugin-Architektur |
| Angular-Integration | Nativ in deiner App | Integration über eine Komponente |
| Lizenz | Deine eigene | BSD-3-Clause — $0 to use |
"Bauen" ist keine Kritik – jede einzelne davon ist baubar. Es ist eine Aussage darüber, wer sie danach pflegt.
Die Fakten in dieser Tabelle bestätigen 2026-09-02 erneut.
Angular gibt dir das Anwendungs-Framework. GrapesJS gibt dir die visuelle Bearbeitungs-Engine.
Nein, und es lohnt sich zu verstehen, warum die Frage aufkommt. GrapesJS veröffentlicht einen offiziellen React-Wrapper, daher erwarten Entwickler vernünftigerweise ein Angular-Äquivalent. Es gibt keinen. Das lässt drei Ansätze übrig, und für die meisten Teams ist die erste die richtige Antwort.
Schreiben Sie die Komponente selbst – die auf dieser Seite umfasst ungefähr dreißig Zeilen. Sie besitzen den Lebenszyklus, die Änderungserkennungsstrategie und den Upgrade-Weg, und zwischen Ihrer App und dem Editor API gibt es keinen Drittanbieter.
Community-Pakete existieren und können die erste Stunde verkürzen. Analysiere eines wie jede Abhängigkeit: Überprüfe, wer es pflegt, welche Angular- und GrapesJS-Versionen es deklariert und wie kürzlich es veröffentlicht wurde.
Sobald mehrere Komponenten den Editor benötigen – eine Symbolleiste, eine Seitenliste, ein Vorschaufenster – verschieben Sie die Initialisierung in einen injizierbaren Service. Es ist eine direkte Integration mit einer Naht und hält den Editor aus Ihrem Komponentenbaum heraus.
Die GrapesJS-Organisation unterhält einen React-Wrapper und kein Angular-Paket. Jede Angular-Bibliothek, die du findest, ist Drittanbieter, egal wie nützlich – behandle sie als Abhängigkeitswahl, nicht als Teil von GrapesJS.
Eine Demo ist eine Komponente. Ein Produkt besteht aus vier Ebenen, und der Editor ist nur eine davon. Das ist die Form, auf die sich die meisten Teams konzentrieren, sobald der Editor echte Nutzer hinter sich hat.
Die Shell, der Router und eine faul geladene Route, die den Editor besitzt.
Einmal konfiguriert, bei der Initialisierung: Welche Komponenten existieren, was kann gezogen werden, was kann gestaltet werden.
Die Angular-Schicht zwischen dem Editor und deinem API – und der einzige Ort, an dem die Autorisierungsentscheidungen clientseitig getroffen werden.
Wo die Artefakte wohnen und wo jede Erlaubnis wirklich durchgesetzt wird.
Nichts in diesem Diagramm ist GrapesJS-spezifisch, außer der zweiten Stufe. Das ist der Punkt: Der Editor ist eine Komponente in einer gewöhnlichen Angular-Anwendung, und der Rest ist die Architektur, die du sowieso gebaut hättest.
Sehen Sie, wie Teams dies als Produkt verpackenDer Editor ist ein großes Stück Software, das in deiner Anwendung läuft. Das sind die Bereiche, in denen das tatsächlich etwas kostet.
Erstellen Sie den Editor, wenn der Bearbeitungsweg geöffnet wird, nicht beim Start der Anwendung. Nutzer, die nie bearbeiten, sollten niemals dafür bezahlen.
Ein dynamischer Import hält GrapesJS aus dem ursprünglichen Bündel heraus, das den größten Hebel auf dieser Liste ist.
Erstellen Sie den Editor mit runOutsideAngular, damit Canvas-Zeigerereignisse nicht bei jedem Frame die Änderungserkennung auslösen.
Gib dem Editor keine vollständige Medienbibliothek bei init. Führe deine API durch und lade Assets, wie es der Picker verlangt.
Sehr tiefe oder sehr repetitive Seitenstrukturen machen Auswahl, Styling und Rückgängigmachen langsamer. Komponente bevorzugen gegenüber tief verschachtelten Verpackungen.
Verwenden Sie stepsBeforeSave, damit ein Burst von Bearbeitungen zu einer Anfrage wird und nicht mehr zu einer Anfrage pro Tastendruck.
Keines davon hat eine versprochene Zahl. Messen Sie Ihre eigene Editor-Route vor und danach – die Größe jedes Effekts hängt von Ihren Blöcken, Ihren Seiten und Ihrem API ab.
Ein visueller Editor verwandelt Nutzer in Autoren von HTML, die deine Anwendung dann bedient. Behandle alles, was aus dem Editor kommt, als Benutzereingabe, denn genau das ist es.
Die Ausgabe des Editors kann beliebige Markups enthalten, einschließlich Skripte, wenn benutzerdefinierte Codeblöcke aktiviert sind. Reinigen Sie serverseitig, bevor sie an andere als den Autor ausgeliefert werden.
Überprüfen Sie Typ und Größe auf dem Server, nicht nur in beforeUpload. Speichern Sie Uploads außerhalb des Web-Roots und servieren Sie sie von einem separaten Ursprung aus.
Lade- und Speicherrouten sind gewöhnliche API-Endpunkte. Sie benötigen die gleiche Authentifizierung wie alles andere in der Anwendung.
Das Ausblenden eines Veröffentlichen-Buttons im Angular UI ist die Präsentation. Die entscheidende Prüfung ist die am Endpunkt.
Das gespeicherte Projekt JSON kann manipuliert werden. Validiere es beim Laden, anstatt davon auszugehen, dass es das von dir geschriebene Dokument ist.
In einem Multi-Tenant-Produkt leiten Sie den Projektbesitzer aus der Sitzung auf dem Server ab – niemals aus einer ID im Anfrage-Body.
Zwölf Dinge, die eine funktionierende Demo von einer Route unterscheiden, die echte Nutzer unterscheidet, können bereitgestellt werden.
Abhängigkeit hinzugefügt und das Stylesheet wird entweder global oder in der Editor-Komponente registriert.
Lies den Abschnittgrapesjs.init() läuft in ngAfterViewInit gegen einen Container mit einer aufgelösten Höhe.
Lies den Abschnitteditor.destroy() in ngOnDestroy, verifiziert, indem man ohne Lecks weg und zurück navigiert.
Lies den AbschnittBewacht, sodass während eines Server-Renderings nichts läuft, mit einem Platzhalter an seiner Stelle.
Lies den AbschnittEin benannter Speicher ist registriert, autosave optimiert und das Projekt JSON wird zusammen mit HTML und CSS gespeichert.
Lies den AbschnittUploads zeigen auf dein Endpunkt, base64 wird aktiviert, Validierung auf beiden Seiten.
Lies den AbschnittDie eigenen Bausteine Ihres Produkts sind als Typen registriert, mit traits und Paletteneinträgen.
Lies den AbschnittBlöcke, Rich Text und Export wurden bewusst gewählt statt angesammelt.
Lies den AbschnittWer bearbeiten darf, wer veröffentlichen darf – das wird auf dem Server durchgesetzt, was sich im UI widerspiegelt.
Lies den AbschnittServerseitige Sanierung zwischen dem Speichern einer Seite und deren Bereitstellung an Besucher.
Lies den AbschnittEditor-Ausnahmen wurden an derselben Stelle gemeldet wie der Rest deiner Angular-Fehler.
Der Gerätemanager wurde auf einem Touch-Gerät trainiert, nicht nur im Desktop-Browserfenster.
Lies den AbschnittBeginnen Sie mit dem Open-Source-Visual-Editor, verbinden Sie ihn mit Ihrer Angular-Anwendung und erweitern Sie ihn mit den Plugins und Integrationen, die Ihr Produkt benötigt.
Installiere das Paket, kopiere die Komponente und lasse heute Nachmittag einen Editor auf einer Angular-Route laufen.
Leg losBlöcke, Speicher, Rich Text, Assets, Export und E-Mail – die Stücke, die du sonst selbst schreiben würdest.
Durchsuchen von PluginsScopen Sie den Editor als Feature Ihrer Anwendung: Rollen, Vorlagen, Veröffentlichung und Versionierung.
Beginnen Sie mit einem BriefingBaue die Anwendung mit Angular. Baue den visuellen Editor mit GrapesJS. Erweitere ihn mit GJS.Market-Plugins.