How to Integrate Your Angular App with dotCMS Using the JavaScript SDK

How to Integrate Your Angular App with dotCMS Using the JavaScript SDK
Author image

Kevin Davila

Senior Software Engineer

Share this article on:

The dotCMS Universal Visual Editor (aka UVEis a framework-agnostic editor that enables full in-context editing for headless websites, single-page applications (SPAs), and other remote web applications that use dotCMS as a content source.

If you're developing applications with Angular, you can integrate UVE to provide a seamless visual editing experience, leveraging the robust architectures of both Angular and dotCMS. By implementing it, you enable users to interact with the interface more dynamically, allowing for instant updates and better content management within your application.

In this guide, we will walk through the integration of UVE into an Angular project from scratch, ensuring a clean and efficient implementation. We'll cover everything from installation to creating a functional component that enables in-context visual editing of your content.

Note: The dotCMS Angular library is compatible and supported starting from Angular 17.


Prerequisites

Before integrating UVE into your Angular project, ensure that your development environment is properly set up. You'll need:

What we’re building

In this tutorial, we will build a simple React App that’s fully editable with dotCMS Universal Visual Editor, using dotCMS JavaScript SDK for Angular. Let's see the final result of the tutorial:

80

Note: If you get stuck at any point in this tutorial, you can find the full code in this GitHub repository: https://github.com/KevinDavilaDotCMS/dotcms-angular-integration-example. You can also follow along step by step by checking out the commit history here: Commit History.

dotCMS Basic knowledge

dotCMS has a flexible page structure consisting of the following hierarchy:

  • Rows → Columns → Containers → Content (e.g., news, events, blog posts).

This structure enables easy content organization within dotCMS pages.

Example Diagram:

image.png

Learn more about page structure here.


dotCMS Page API

The dotCMS Page API provides everything we need to build custom pages in a headless architecture. The response includes details about:

  • Layout

  • Containers

  • Page


Getting your dotCMS Environment ready

Create Our Example Page

Let’s create the page we’ll use in this tutorial. Follow these steps to create the page in dotCMS:

  1. Browse to Site > Pages.

  2. Click on Create Page and select Page Type.

  3. Name the page example and select the System Template.

  4. Publish the page.

80

Universal Visual Editor Configuration

Let’s start by configuring the Universal Visual Editor (UVE) in dotCMS for headless pages. This step is needed to use UVE to edit pages built in any JavaScript framework and served by any external hosting service. Next, let’s set up the Universal Visual Editor:

  1. Sign in your dotCMS instance

  2. Browse to Settings -> Apps

  3. Select the built-in integration for UVE - Universal Visual Editor.

  4. Select the site that will be feeding the destination pages.

80

In the configuration section, add your JSON object configuration. For this example, we’ll use a simple configuration to work with a localhost environment:

{
  "config": [
    {
      "pattern": ".*",
      "url": "http://localhost:4200/"
    }
  ]
}

Where:

  • pattern: a regular expression (RegEx) that identifies to dotCMS which URLs should be associated with the Angular application

  • url: the location of our Angular app

Note: if you want to know more about Universal Visual Editor configuration and all you can do with it, check out our documentation: Universal Visual Editor Configuration for Headless Pages.


Building our Angular + dotCMS Page

To keep our project structured and maintainable, we will organize it as follows:

🗂️ src/

├── 🗂️ environments/

└── 🗂️ app/

    ├── 🗂️ content-types/

    ├── 🗂️ layout/

    ├── 🗂️ pages/

    ├── app.component.html

    ├── app.component.ts

    └── app.component.scss

The first step in setting up our environment is ensuring we have the environment files.

If they are missing, you have two options:

  1. Create them manually within the src directory.

  2. Use the Angular CLI schematic, which will generate the environments folder along with two files:

  • environment.ts

  • environment.development.ts

ng generate environments

By running the provided command in your project's root directory, you'll have these files set up automatically.

These files will store environment variables related to dotCMS, ensuring a clean and centralized configuration. Next, we add this to the environments and fill it with our information

export const environment = {
    dotcmsURL: 'YOU_DOTCMS_HOST',
    authToken: 'YOUR_TOKEN',
    siteId: 'YOUR_DOTCMS_SITE_ID'
};

We will also create a component inside the pages/ directory, which will be responsible for routing our application and rendering content based on the desired path.

To achieve this, run the following command:

ng generate component pages --inline-template=true --inline-style=true --skip-tests

This will generate a new file: pages.component.ts

Next, we need to set this component as our main route. To do this, we will navigate to app.routes and configure it accordingly:

/* app.routes.ts */

import { Routes } from '@angular/router';
import { PagesComponent } from './pages/pages.component';

export const routes: Routes = [
    {
        path: '',
        component: PagesComponent
    },
    {
        path: '**',
        component: PagesComponent
    }
];

And then modify the AppComponent

/* app.component.ts */

import { Component } from '@angular/core';
import {  RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  imports: [RouterOutlet],
  styleUrl: './app.component.scss'
})
export class AppComponent { }

/* app.component.html */

<router-outlet />

With this setup, whenever we access any route in our application, the PagesComponent will be rendered.

image5.gif

Fetching a Page from dotCMS

The next step is to install the dotCMS client library SDK, part of our Javascript SDK. This library provides the necessary methods to interact seamlessly with dotCMS APIs, allowing us to retrieve and manage content efficiently.

npm i @dotcms/client

After installing the SDK library, we need to configure the client with our specific values. To do this, we must add the following configuration inside the app.config.ts file.

export const DOTCMS_CLIENT_TOKEN = new InjectionToken<DotCmsClient>('DOTCMS_CLIENT');

const DOTCMS_CLIENT_CONFIG: ClientConfig = {
  dotcmsUrl: environment.dotcmsURL,
  authToken: environment.authToken,
  siteId: environment.siteId,
};

const client = DotCmsClient.init(DOTCMS_CLIENT_CONFIG);

export const appConfig: ApplicationConfig = {
  providers: [
    ...existing configs,
    {
      provide: DOTCMS_CLIENT_TOKEN,
      useValue: client
    },
  ],
};

Where:

  • dotcmsURL: The URL of your dotCMS instance.

  • authToken: The JWT token of the dotCMS user you are using. If you don't have one, you can generate it by following these instructions: Creating an API Token in the UI.

  • siteId: The ID of the site from which you want to retrieve information. If left empty, the client will use the default site. Learn more here: Multi-Site Management in dotCMS.


Fetching the Page Asset

Now that our environment is fully set up, we are ready to retrieve content from our dotCMS instance!

We can use the client.page.get() method to fetch the content of any page within our dotCMS instance. This method accepts an object of type PageApiOptions as a parameter.

For this example, we will use the path provided in the URL along with the default language_id.

/* pages.component.ts */

import { Component, inject } from '@angular/core';
import { DOTCMS_CLIENT_TOKEN } from '../app.config';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-pages',
  imports: [],
  template: `
    <p>
      pages works!
    </p>
  `,
  styles: ``
})
export class PagesComponent {
  #dotcmsClient = inject(DOTCMS_CLIENT_TOKEN);
  #route = inject(ActivatedRoute);

  ngOnInit() {
    this.#route.url.subscribe((urlSegments) => {
      const pathname = urlSegments.join('/');

      this.#fetchPageAsset(pathname);
  });
  }

  #fetchPageAsset(pathname: string) {
    this.#dotcmsClient.page.get({ path: pathname, language_id: 1 })
      .then((page) => {
        console.log("DotCMS Page Asset: ",page);
      })
      .catch((error) => {
        console.error("Error fetching page asset: ",error);
      });
  }
}

If we check the console, we will see the response containing all the details of our Page Asset.

image4.gif

With just a few lines of code, we can retrieve all the necessary information from our dotCMS instance 🚀


Rendering Our Page with @dotcms/angular

To render our page, we will use the Angular SDK library provided by dotCMS. This library allows us to render a page using the Page Asset along with a component mapping system (which we will cover in more detail later).

Step 1: Install the Library

Before proceeding, we need to install the Angular library for dotCMS.

npm i @dotcms/angular

Now we can use the <dotcms-layout /> component to render our page. This component requires the following inputs:

  • pageAsset: The page object we receive from the client.page.get() function. It represents our page.

  • components: A key-value object where the key is the name of an element in our page, and the value is the component that will be rendered. This allows us to dynamically render different components based on each page element.

For now, we will pass an empty object to components.

/* pages.component.ts */

import { Component, inject, signal } from '@angular/core';
import { DotcmsLayoutComponent, DotCMSPageAsset, DotCMSPageComponent } from '@dotcms/angular';
import { DOTCMS_CLIENT_TOKEN } from '../app.config';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-pages',
  imports: [DotcmsLayoutComponent],
  template: `
    @if (page(); as page) {
      <div class="flex flex-col min-h-screen">
          <main class="container m-auto">
            <dotcms-layout 
              [pageAsset]="page"
              [components]="components()"
            />
          </main>
        </div>
    }
  `,
  styles: ``
})
export class PagesComponent {
  #dotcmsClient = inject(DOTCMS_CLIENT_TOKEN);
  #route = inject(ActivatedRoute);

  page = signal<DotCMSPageAsset | null>(null);

  components = signal<DotCMSPageComponent>({})

  ngOnInit() {
    this.#route.url.subscribe((urlSegments) => {
      const pathname = urlSegments.join('/');

      this.#fetchPageAsset(pathname);
  });
  }

  #fetchPageAsset(pathname: string) {
    this.#dotcmsClient.page.get({ path: pathname, language_id: 1 })
      .then((page) => {
        this.page.set(page as DotCMSPageAsset);
      })
      .catch((error) => {
        console.error("Error fetching page asset: ",error);
      });
  }
}

With these changes, if we go to UVE and view our page, it will look like this:

image6.png

This is expected because our page doesn't have any content yet, just an empty container. To add content, we will insert a contentlet of type "Banner." You can drag it from the Content Palette.

image2.gif

At this point, our page will display this message: "No component for Banner."

This happens because the <dotcms-layout /> component is trying to render a component for the Banner element we added. However, since we haven’t provided one yet, it falls back to a default message.

Now, let’s specify which component should be rendered for the Banner. To do this, we will create a new component inside the content-types folder.

We can generate this component using the Angular CLI.

ng generate component content-types/banner --inline-template=true --inline-style=true --skip-tests

And the content of this component will be:

/* banner.component.ts */
import { Component, input, OnInit } from '@angular/core';
import { DotCMSContentlet } from '@dotcms/angular';

@Component({
  selector: 'app-banner',
  imports: [],
  template: `
    <p>
      banner works!
    </p>
  `,
  styles: ``
})
export class BannerComponent implements OnInit {
  contentlet = input.required<DotCMSContentlet>();

  ngOnInit() {
    console.log("Banner Contentlet: ",this.contentlet());
  }
}

Now, we will go to pages.component.ts and add our new component to the components map.

/* pages.component.ts */

import { Component, inject, signal } from '@angular/core';
import { DotcmsLayoutComponent, DotCMSPageAsset } from '@dotcms/angular';
import { DOTCMS_CLIENT_TOKEN } from '../app.config';
import { DotCMSPageComponent } from '@dotcms/angular';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-pages',
  imports: [DotcmsLayoutComponent],
  template: `
    @if (page(); as page) {
      <div class="flex flex-col min-h-screen">
          <main class="container m-auto">
            <dotcms-layout 
              [pageAsset]="page"
              [components]="components()"
            />
          </main>
        </div>
    }
  `,
  styles: ``
})
export class PagesComponent {
  #dotcmsClient = inject(DOTCMS_CLIENT_TOKEN);
  #route = inject(ActivatedRoute);

  page = signal<DotCMSPageAsset | null>(null);

  components = signal<DotCMSPageComponent>({
    Banner: import('../content-types/banner/banner.component').then(m => m.BannerComponent)
  })

  ngOnInit() {
    this.#route.url.subscribe((urlSegments) => {
      const pathname = urlSegments.join('/');

      this.#fetchPageAsset(pathname);
  });
  }

  #fetchPageAsset(pathname: string) {
    this.#dotcmsClient.page.get({ path: pathname, language_id: 1 })
      .then((page) => {
        this.page.set(page as DotCMSPageAsset);
      })
      .catch((error) => {
        console.error("Error fetching page asset: ",error);
      });
  }
}

If we check the console inside UVE, we will see something like this:

image7.png

Internally, the <dotcms-layout /> component passes the Banner data as an input to our component. Because of this, our component needs an input of type dotCMSContentlet.

This input represents our Banner, and within our component, we can manipulate it however we like.

For this example, we are using Tailwind CSS to render a simple banner.

Additionally, we parse the image included in the Banner to use it on our website. Since the contentlet is a signal, we can take advantage of it by using a computed property.

/* banner.component.html */

import { Component, computed, input, OnInit } from '@angular/core';
import { DotCMSContentlet } from '@dotcms/angular';
import { environment } from '../../../environments/environment.development';
import { RouterLink } from '@angular/router';

@Component({
  selector: 'app-banner',
  imports: [RouterLink],
  template: `
<div
    class="flex overflow-hidden relative justify-center items-center w-full h-96 bg-gray-200"
  >
    @if (contentlet().image; as image) {
    <img
      class="object-cover w-full"
      [src]="imageURL()"
      [alt]="contentlet().title"
      fill
      priority
    />
    }
    <div
      class="flex absolute inset-0 flex-col justify-center items-center p-4 text-center text-white"
    >
      <h2 class="mb-2 text-6xl font-bold text-shadow">
        {{ contentlet().title }}
      </h2>
      <p class="mb-4 text-xl text-shadow">{{ contentlet()['caption'] }}</p>
      <a
        class="p-4 text-xl bg-red-400 rounded transition duration-300 hover:bg-red-500"
        [routerLink]="contentlet()['link']"
      >
        {{ contentlet()['buttonText'] }}
      </a>
    </div>
  </div>
  `,
  styles: ``
})
export class BannerComponent  {
  contentlet = input.required<DotCMSContentlet>();

  imageURL = computed(() => {
    return environment.dotcmsURL + '/dA/' + this.contentlet().image;
  });
}

And our page should look like this:

image8.png

Our Angular application now renders our dotCMS page, Great 👏!

Let's move on to the final details.


Adding Header & Footer

The dotCMS PageAsset includes a Layout property with two boolean attributes: header and footer.

/* PageAsset*/
{
	canCreateTemplate: booleanM
	containers: {},
	layout: {
	  ...
	  footer: boolean,
	  header: boolean,
	  ...	
        },

}

These attributes can be either true or false, depending on the configuration of our dotCMS template. In our case, we want to create header and footer components and add them to the page if they are active in the template. That said, let's go ahead and create those components:

ng generate component layout/header --inline-template=true --inline-style=true --skip-tests


ng generate component layout/footer --inline-template=true --inline-style=true --skip-tests

Header

import { Component } from '@angular/core';

@Component({
  selector: 'app-header',
  template: `
    <div class="flex items-center justify-between p-4 bg-red-400">
      <div class="flex items-center">
        <h2 class="text-3xl font-bold text-white">
          <a href="/">My Angular Page with dotCMS</a>
        </h2>
  `,
  styles: ``
})
export class HeaderComponent {}

Footer

import { Component } from '@angular/core';

@Component({
  selector: 'app-footer',
  template: `
  <footer class="p-4 text-white bg-red-400 py-4">
    <div class="flex w-full items-center justify-center">
      <a href="https://www.dotcms.com/">
        Check out our docs to learn more about dotCMS
      </a>
    </div>
  </footer>
  `,  
  styles: ``
})
export class FooterComponent { }

And now we can use both in our main component:

/* pages.component.ts */

import { Component, inject, signal } from '@angular/core';
import { DotcmsLayoutComponent, DotCMSPageAsset } from '@dotcms/angular';
import { DOTCMS_CLIENT_TOKEN } from '../app.config';
import { DotCMSPageComponent } from '@dotcms/angular';
import { ActivatedRoute } from '@angular/router';
import { HeaderComponent } from "../layout/header/header.component";
import { FooterComponent } from "../layout/footer/footer.component";

@Component({
  selector: 'app-pages',
  imports: [DotcmsLayoutComponent, HeaderComponent, FooterComponent],
  template: `
    @if (page(); as page) {
      <div class="flex flex-col min-h-screen">
          @if(page.layout.header) {
            <app-header />
          }   
          
          <main class="container m-auto">
            <dotcms-layout 
              [pageAsset]="page"
              [components]="components()"
            />
          </main>
      
          @if(page.layout.footer) {
            <app-footer />
          }      
        </div>
    }
  `,
  styles: ``
})
export class PagesComponent {
  #dotcmsClient = inject(DOTCMS_CLIENT_TOKEN);
  #route = inject(ActivatedRoute);

  page = signal<DotCMSPageAsset | null>(null);

  components = signal<DotCMSPageComponent>({
    Banner: import('../content-types/banner/banner.component').then(m => m.BannerComponent)
  })

  ngOnInit() {
    this.#route.url.subscribe((urlSegments) => {
      const pathname = urlSegments.join('/');

      this.#fetchPageAsset(pathname);
  });
  }

  #fetchPageAsset(pathname: string) {
    this.#dotcmsClient.page.get({ path: pathname, language_id: 1 })
      .then((page) => {
        console.log("Page Asset: ",page);
        this.page.set(page as DotCMSPageAsset);
      })
      .catch((error) => {
        console.error("Error fetching page asset: ",error);
      });
  }
}

And now our page should look like this:

image3.png

We now have our page rendering the Header, Footer, and our contentlets. Let's add a few final touches. 😎


Let’s make the page fully editable in dotCMS

At this point, we can render our page in our Angular application. The next step is to make it fully editable.

We will add an extra function to our app component.

/* pages.component.ts */

import { Component, inject, signal } from '@angular/core';
import { DotcmsLayoutComponent, DotCMSPageAsset, DotCMSPageComponent } from '@dotcms/angular';
import { DOTCMS_CLIENT_TOKEN } from '../app.config';
import { ActivatedRoute } from '@angular/router';
import { HeaderComponent } from "../layout/header/header.component";
import { FooterComponent } from "../layout/footer/footer.component";
import { isInsideEditor } from '@dotcms/client';

@Component({
  selector: 'app-pages',
  imports: [DotcmsLayoutComponent, HeaderComponent, FooterComponent],
  template: `
    @if (page(); as page) {
      <div class="flex flex-col min-h-screen">
          @if(page.layout.header) {
            <app-header />
          }   
          
          <main class="container m-auto">
            <dotcms-layout 
              [pageAsset]="page"
              [components]="components()"
            />
          </main>
      
          @if(page.layout.footer) {
            <app-footer />
          }      
        </div>
    }
  `,
  styles: ``
})
export class PagesComponent {
  #dotcmsClient = inject(DOTCMS_CLIENT_TOKEN);
  #route = inject(ActivatedRoute);

  page = signal<DotCMSPageAsset | null>(null);

  components = signal<DotCMSPageComponent>({
    Banner: import('../content-types/banner/banner.component').then(m => m.BannerComponent)
  })

  ngOnInit() {
    if (isInsideEditor()) {
      this.#listenToEditorChanges();
    }
    
    this.#route.url.subscribe((urlSegments) => {
      const pathname = urlSegments.join('/');

      this.#fetchPageAsset(pathname);
  });
  }

  #fetchPageAsset(pathname: string) {
    this.#dotcmsClient.page.get({ path: pathname, language_id: 1 })
      .then((page) => {
        console.log("Page Asset: ",page);
        this.page.set(page as DotCMSPageAsset);
      })
      .catch((error) => {
        console.error("Error fetching page asset: ",error);
      });
  }

  #listenToEditorChanges() {
    this.#dotcmsClient.editor.on('changes', (page) => {
      if (!page) {
        return;
      }

      this.page.set(page as DotCMSPageAsset);
    });
  }
}

We’ve added the listenToEditorChanges function, which will handle updating the page asset every time the editor makes changes. To achieve this, we use the changes event from the Universal Visual Editor. This event emits every change made to the page content. Additionally, we use the isInsideEditor function to check if we’re in the UVE context.

And that’s it 🎉! We’ve connected our Angular application with dotCMS and made it fully editable.


Conclusion and Next Steps

In this tutorial, we’ve successfully connected a React app to dotCMS, fetched page assets, rendered dynamic content, and made it editable 🥳 🚀. For the next steps, consider exploring dotCMS Content Types or the Page API to suit your app's needs.