Visual Editor
Subscribe to Localess Visual Editor live-editing events with the Angular SDK for Localess.
Visual Editor Integration
The Localess Visual Editor enables live in-browser content editing. Set enableSync: true in provideLocaless() to automatically inject the sync script.
<ll-document> already wires up live sync automatically. To handle sync events yourself elsewhere, inject LocalessSyncService and use onDocument(), onChange() or on() — each already covers the enabled() check (browser + Visual Editor iframe) and the ready() wait (avoiding a race where the listener is attached before the sync script has loaded). onDocument(documentId, callback) delivers the live edits of one document and ignores edits to any other document on the page, such as a shared header:
import { Component, effect, inject, input, linkedSignal } from '@angular/core';
import { Content, ContentData, LocalessSyncService } from '@localess/angular';
@Component({
selector: 'app-slug',
standalone: true,
templateUrl: './slug.component.html',
})
export class SlugComponent {
private readonly sync = inject(LocalessSyncService);
content = input.required<Content>();
liveContent = linkedSignal<ContentData | undefined>(() => this.content().data);
constructor() {
effect(onCleanup => onCleanup(this.sync.onDocument(this.content().id, data => this.liveContent.set(data))));
}
}Render liveContent() instead of content().data to give authors a live preview. The effect re-subscribes when content() changes, and onCleanup removes the previous subscription — and the last one when the component is destroyed. A subscriber that attaches after the document was already edited is called once straight away with the latest edit, so a component that mounts late still shows what the editor shows.
onChange(callback) is shorthand for on(['input', 'change'], callback) across all documents: the input event fires on every keystroke, change fires when blocks are added, removed, duplicated or reordered — and once when the preview connects — and callback is narrowed to that variant.
For other event types (save, publish, unpublish, pong, enterSchema, hoverSchema, leaveSchema), use on(event, callback):
this.sync.on(['save', 'publish'], event => console.info(`Content ${event.type}d`));To read the latest event as a signal instead, call localessSyncEvent(event) — the signal is undefined until the first matching event arrives:
import { localessSyncEvent } from '@localess/angular';
export class PageComponent {
readonly saved = localessSyncEvent('save');
}All of these are no-ops if sync isn't enabled or usable in the current context — no need to check enabled() yourself.
Cleanup
Every LocalessSyncService subscription method returns a function that removes the subscription, even before the sync script has loaded. Subscriptions made in an injection context (a constructor or field initializer) are removed automatically when that component, directive or service is destroyed. Anywhere else — ngOnInit, an event handler — pass a DestroyRef as the last argument, or call the returned function yourself:
private readonly destroyRef = inject(DestroyRef);
ngOnInit(): void {
this.sync.onChange(event => this.liveContent.set(event.data), this.destroyRef);
}An effect() callback is not an injection context, which is why the example above hands the returned function to onCleanup. localessSyncEvent() must itself be called in an injection context, and is removed when that context is destroyed.