Expand my Community achievements bar.

Submissions are now open for the 2026 Adobe Experience Maker Awards.
SOLVED

Upgrading angular 9 to 19

Avatar

Level 2

Hi everyone,

We're kicking off a new AEM project from scratch, and the requirement is to use Angular for the frontend. By default, the Angular version is set to 9 when scaffolding the project, but we needed a more recent version — so we upgraded Angular successfully, and the build completes without errors.

However, we're facing a few issues:

  • The Angular components are not rendering on the pages.

  • Component mapping doesn’t seem to be working.

  • The clientlibs-angular folder is being generated, but the js.txt file inside is empty.

I have limited experience with Angular, so I’d really appreciate it if anyone who has gone through a similar upgrade process or encountered these issues could share some guidance or best practices.

Thanks in advance!

1 Accepted Solution

Avatar

Correct answer by
Community Advisor

Hi @HARPREETSI5,

1. Angular Components Are Not Rendering on Pages

Ensure Angular is properly bootstrapped

In main.ts, make sure this exists:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));
Use the Angular app selector in your HTL

In your AEM component HTL file (.html

<div class="my-angular-wrapper">
  <app-root></app-root> <!-- or whatever your Angular root component selector is -->
</div>

Make sure this selector matches what is defined in your AppComponent:

@Component({
  selector: 'app-root',
  ...
})
Include Angular JS/CSS clientlibs in your AEM template

In the AEM page template HTL (e.g., structure/page.html

<cq:includeClientLib categories="yourproject.angular" />

Replace yourproject.angular with the category defined in your clientlibs-angular/.content.xml.

2. Component Mapping Doesn’t Work

Expose sling:resourceType in the model JSON

In your Sling Model (Java):

@ValueMapValue
@Optional
private String slingResourceType;

public String getSlingResourceType() {
    return slingResourceType;
}

In the model exporter (using @Exporter), make sure the sling:resourceType is included in the output.

Map AEM components to Angular components

In Angular (component-mapping.ts

import { HeroComponent } from './components/hero/hero.component';
import { TeaserComponent } from './components/teaser/teaser.component';

export const COMPONENT_MAPPING: any = {
  'myproject/components/content/hero': HeroComponent,
  'myproject/components/content/teaser': TeaserComponent
};
Use a dynamic component loader

In your container Angular component (e.g., DynamicContainerComponent

import { Component, ComponentFactoryResolver, Input, ViewChild, ViewContainerRef } from '@angular/core';
import { COMPONENT_MAPPING } from './component-mapping';

@Component({
  selector: 'dynamic-container',
  template: `<ng-container #container></ng-container>`
})
export class DynamicContainerComponent {
  @Input() componentType: string;
  @ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef;

  constructor(private resolver: ComponentFactoryResolver) {}

  ngOnInit() {
    const component = COMPONENT_MAPPING[this.componentType];
    if (component) {
      const factory = this.resolver.resolveComponentFactory(component);
      this.container.createComponent(factory);
    }
  }
}

Then use this like:

<dynamic-container [componentType]="model['sling:resourceType']"></dynamic-container>

3. clientlibs-angular/js.txt is Empty

Ensure Angular output files go to the expected path

In angular.json:

"outputPath": "dist/angular-app"
Copy Angular output to AEM clientlibs-angular manually or via script

You can use a postbuild script in package.json:

"scripts": {
  "build": "ng build",
  "postbuild": "node scripts/copy-angular-assets.js"
}

scripts/copy-angular-assets.js:

const fs = require('fs');
const path = require('path');

const sourceDir = path.resolve(__dirname, '../dist/angular-app');
const targetDir = path.resolve(__dirname, '../../ui.apps/src/main/content/jcr_root/apps/yourproject/clientlibs/clientlibs-angular');

const jsFiles = fs.readdirSync(sourceDir).filter(f => f.endsWith('.js'));
const jsTxtContent = jsFiles.join('\n');

// Copy JS files
jsFiles.forEach(file => {
  fs.copyFileSync(path.join(sourceDir, file), path.join(targetDir, file));
});

// Write js.txt
fs.writeFileSync(path.join(targetDir, 'js.txt'), jsTxtContent);
console.log('Copied JS files and updated js.txt');
Check that your .content.xml defines the clientlib correctly

clientlibs-angular/.content.xml:

<jcr:root
    xmlns:jcr="http://www.jcp.org/jcr/1.0"
    xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:primaryType="cq:ClientLibraryFolder"
    categories="[yourproject.angular]"
    dependencies="[yourproject.base]"
    allowProxy="true"/>

Best Practices

Use Angular Elements (optional but helpful)

To avoid bootstrapping issues, wrap your Angular components as Web Components.

import { createCustomElement } from '@angular/elements';

const el = createCustomElement(MyComponent, { injector });
customElements.define('my-component', el);

Then you can use <my-component> directly in AEM HTL without worrying about Angular bootstrapping logic.

Hope that helps!


Santosh Sai

AEM BlogsLinkedIn


View solution in original post

5 Replies

Avatar

Community Advisor

Hi @HARPREETSI5 ,

Before suggesting a solution, could you please confirm:

  - Are you using AEM SPA Editor or traditional components?

  - How are you embedding Angular ClientLibs, iframe, or something else?

  - Are you using Angular Universal or just client-side rendering?

  - What's your build + deployment setup (manual copy, Maven, etc.)?

  - Do you need full Angular routing, or just embed components?

This will help me provide a solution based on your exact setup.

Thanks!

Avatar

Level 2

Hi Amit
-We will be using AEM SPA Editor

-We will be using Angular Universal 
-We are using Maven for building + deployment


For the other questions, I currently don't know. If possible, can we have a small meet?

Avatar

Community Advisor

Hi @HARPREETSI5 ,

Thanks for confirming the setup.

Try with below solution:

Avatar

Correct answer by
Community Advisor

Hi @HARPREETSI5,

1. Angular Components Are Not Rendering on Pages

Ensure Angular is properly bootstrapped

In main.ts, make sure this exists:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));
Use the Angular app selector in your HTL

In your AEM component HTL file (.html

<div class="my-angular-wrapper">
  <app-root></app-root> <!-- or whatever your Angular root component selector is -->
</div>

Make sure this selector matches what is defined in your AppComponent:

@Component({
  selector: 'app-root',
  ...
})
Include Angular JS/CSS clientlibs in your AEM template

In the AEM page template HTL (e.g., structure/page.html

<cq:includeClientLib categories="yourproject.angular" />

Replace yourproject.angular with the category defined in your clientlibs-angular/.content.xml.

2. Component Mapping Doesn’t Work

Expose sling:resourceType in the model JSON

In your Sling Model (Java):

@ValueMapValue
@Optional
private String slingResourceType;

public String getSlingResourceType() {
    return slingResourceType;
}

In the model exporter (using @Exporter), make sure the sling:resourceType is included in the output.

Map AEM components to Angular components

In Angular (component-mapping.ts

import { HeroComponent } from './components/hero/hero.component';
import { TeaserComponent } from './components/teaser/teaser.component';

export const COMPONENT_MAPPING: any = {
  'myproject/components/content/hero': HeroComponent,
  'myproject/components/content/teaser': TeaserComponent
};
Use a dynamic component loader

In your container Angular component (e.g., DynamicContainerComponent

import { Component, ComponentFactoryResolver, Input, ViewChild, ViewContainerRef } from '@angular/core';
import { COMPONENT_MAPPING } from './component-mapping';

@Component({
  selector: 'dynamic-container',
  template: `<ng-container #container></ng-container>`
})
export class DynamicContainerComponent {
  @Input() componentType: string;
  @ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef;

  constructor(private resolver: ComponentFactoryResolver) {}

  ngOnInit() {
    const component = COMPONENT_MAPPING[this.componentType];
    if (component) {
      const factory = this.resolver.resolveComponentFactory(component);
      this.container.createComponent(factory);
    }
  }
}

Then use this like:

<dynamic-container [componentType]="model['sling:resourceType']"></dynamic-container>

3. clientlibs-angular/js.txt is Empty

Ensure Angular output files go to the expected path

In angular.json:

"outputPath": "dist/angular-app"
Copy Angular output to AEM clientlibs-angular manually or via script

You can use a postbuild script in package.json:

"scripts": {
  "build": "ng build",
  "postbuild": "node scripts/copy-angular-assets.js"
}

scripts/copy-angular-assets.js:

const fs = require('fs');
const path = require('path');

const sourceDir = path.resolve(__dirname, '../dist/angular-app');
const targetDir = path.resolve(__dirname, '../../ui.apps/src/main/content/jcr_root/apps/yourproject/clientlibs/clientlibs-angular');

const jsFiles = fs.readdirSync(sourceDir).filter(f => f.endsWith('.js'));
const jsTxtContent = jsFiles.join('\n');

// Copy JS files
jsFiles.forEach(file => {
  fs.copyFileSync(path.join(sourceDir, file), path.join(targetDir, file));
});

// Write js.txt
fs.writeFileSync(path.join(targetDir, 'js.txt'), jsTxtContent);
console.log('Copied JS files and updated js.txt');
Check that your .content.xml defines the clientlib correctly

clientlibs-angular/.content.xml:

<jcr:root
    xmlns:jcr="http://www.jcp.org/jcr/1.0"
    xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:primaryType="cq:ClientLibraryFolder"
    categories="[yourproject.angular]"
    dependencies="[yourproject.base]"
    allowProxy="true"/>

Best Practices

Use Angular Elements (optional but helpful)

To avoid bootstrapping issues, wrap your Angular components as Web Components.

import { createCustomElement } from '@angular/elements';

const el = createCustomElement(MyComponent, { injector });
customElements.define('my-component', el);

Then you can use <my-component> directly in AEM HTL without worrying about Angular bootstrapping logic.

Hope that helps!


Santosh Sai

AEM BlogsLinkedIn


Avatar

Level 2

Hi Santosh
Thanks for you help.

I have fixed the JS issue and I also found out the template issue. In my page component the clientlibs was not included.

 

Now I am facing an issue with the runtime.js file. Whenever I add the runtime.js file to the js.txt. I get errors in the console.  These errors are Angular-related errors. Due to which the responsive grid and other components are not loading in the template 

 

Also, my page component is using the " spa-project-core " page component.

<div id="spa-root"></div>

In which file I need to mention spa-root?


Thanks for all the help!!