Use Visual Editor with Angular

Learn how to use Visual Editor with an Angular-based website.

# Configure Visual Editor for an Angular project

Define ssgName: angular in your Visual Editor configuration file.

By default, Visual Editor will launch the Angular development server by running:

npm run config --if-present && ./node_modules/.bin/ng serve  --port {PORT} --disable-host-check

If you have a config script defined in your package.json file, it will run. This allows you to perform pre-bundling tasks such as copying needed environment variables into your environment.ts file.

For example, if you're using an API-based CMS with your Visual Editor project, you should either configure npm run config to run a script that copies the current CMS settings and keys into environment.ts (example) or adopt an alternative solution.

If you need to override how your site's development server runs, define devCommand in your Visual Editor configuration file.

# Content reload

There is no out-of-the-box mechanism to gracefully refresh any page in an Angular web app. So, to implement content reload you will need to have a listener to the stackbitObjectsChanged window event (reference).

Here's a technique you can use to encapsulate the implementation details inside your service classes, so that consumer components are automatically refreshed on content changes:

First, add a service class (e.g. StackbitService) that listens on the stackbitObjectsChanged window event and allows other services to subscribe to these events via an RxJS Subject object. For example:

import { Injectable } from "@angular/core";
import { Subject } from "rxjs";

export class StackbitEvent {
  changedContentTypes: string[];
  changedObjectIds: [];
  currentPageObjectId: string;
  currentUrl: string;
  visibleObjectIds: [];
}

@Injectable()
export class StackbitService {
  public contentChanged = new Subject<StackbitEvent>();

  constructor() {
    window.addEventListener(
      "stackbitObjectsChanged",
      (event: any) => {
        this.contentChanged.next({
          changedContentTypes: event.detail.changedContentTypes,
          changedObjectIds: event.detail.changedObjectIds
          // ...copy rest of properties
        });

        event.preventDefault();
      },
      { passive: false }
    );
  }
}

(source)

Then, in your existing services that provide data to components, wrap the return value of your public methods with a BehaviorSubject. This will allow pushing updates to clients.

import { Injectable } from "@angular/core";
import { BehaviorSubject } from "rxjs";
import { StackbitEvent, StackbitService } from "./stackbit.service";
// ...

@Injectable()
export class ContentfulService {
  constructor(private stackbitService: StackbitService) {
    // ...
  }

  getProduct(slug: string): BehaviorSubject<Promise<Entry<any>>> {
    const productSubject = new BehaviorSubject(
      this.getProductFromContentful(slug)
    );
    this.stackbitService.contentChanged.subscribe({
      next: (event: StackbitEvent) => {
        productSubject.next(this.getProductFromContentful(slug));
      }
    });
    return productSubject;
  }

  // ...
}

(source)

# Example

Angular + Contentful Starter — based on Contentful's "Product Catalogue" example (GitHub), with added Visual Editor support and updated to the latest version of Angular (v14 at the time of writing).