Tips for working with JSS for Angular

Version: 22.x

Working with JSS Angular apps is similar to working with regular Angular applications. This topic describes some JSS-specific recommendations.

Do not use the <base> tag

Most Angular applications use the <base /> tag in their HTML template to set a base href for routing. JSS Angular apps cannot use this technique because it is incompatible with Sitecore Experience Editor capabilities. An alternative is to use the APP_BASE_HREF token to provide the routing base href through dependency injection. The sample application uses the APP_BASE_REF technique.

Use lazy-loaded components for infrequently used JSS components

JSS Angular supports lazy-loading components to improve initial page load times and reduce bandwidth. This is similar to lazy-loaded routes in Angular without JSS in that the component gets a dynamically loaded module when the component is used on a route. Generally, any component not used on almost every route is a good candidate to be lazy-loaded.

Tip

To scaffold and auto-register a lazy-loaded component, you can use jss scaffold <componentName> --lazyload.

To lazy-load tan existing component, you must add an Angular module to the component folder. For example, for a MyComponent component:

RequestResponse
import { NgModule } from '@angular/core';
import { JssModule } from '@sitecore-jss/sitecore-jss-angular';
import { MyComponent } from './my.component';

@NgModule({
  imports: [
    JssModule.forChild(MyComponent)
  ],
  declarations: [
    MyComponent,
  ],
})
export class MyModule { }
Important

Angular 20 uses standalone components by default and must be registered in the imports section instead of declarations.

If you use the default code-generated app-components.module.ts, the code generator automatically registers the component as lazy-loaded. Otherwise, you can use the second parameter of JssModule.withComponents() for lazy-loaded components, which works just like lazy-loaded routes. For example:

RequestResponse
@NgModule({
  imports: [
    AppComponentsSharedModule,
    JssModule.withComponents([
      // non-lazy components
    ], [
      // In this case, 'path' is the component name, and loadChildren is the module to load for it.
      // This is exactly like lazy loaded routes. In fact, it uses the router under the hood to do it.
      // loadChildren is the resolved module, with the path and then the export name.
      { path: 'My', loadChildren: import('./my/my.module').then(mod => mod.MyModule) }
    ]),
  ],
  exports: [
    JssModule,
    AppComponentsSharedModule,
  ],
  declarations: [
    // non-lazy components
  ],
})
export class AppComponentsModule { }

You can also load multiple components from one module. This improves the code-splitting of your application and reduces the calls for loading application resources by bundling components into loadable groups.

Note

Multiple component loading is not compatible with the default code generation. To use this technique, you must turn off code generation and manually maintain the app-components.module.ts file.

To lazy load multiple components, you must register two or more components with the JssModule.forChild() method into the lazy-loading module. You must provide the component name and component type. For example:

RequestResponse
import { NgModule } from '@angular/core';
import { ROUTES } from '@angular/router';
import { JssModule, DYNAMIC_COMPONENT } from '@sitecore-jss/sitecore-jss-angular';
import { FirstComponent } from './first.component';
import { SecondComponent } from './second.component';

@NgModule({
  imports: [
    {
      ngModule: JssModule,
      providers: [
        {
          provide: DYNAMIC_COMPONENT,
          useValue: {
            'FirstComponent': FirstComponent,
            'SecondComponent': SecondComponent
          }
        },
        {
          provide: ROUTES,
          useValue: [],
          multi: true
        }
      ]
    }
  ],
  declarations: [
    FirstComponent,
    SecondComponent
  ],
})
export class MyModule { }

There are no changes in your component module. If you lazy load multiple components, you must have the same value for loadChildren if the components are from the same module. Also, the path value must be the same as the component name from the lazy-loading module initialization.

RequestResponse
@NgModule({
  imports: [
    AppComponentsSharedModule,
    JssModule.withComponents(
      [
        // non-lazy components
      ],
      [
        {
          path: 'FirstComponent',
          loadChildren: import('./my/my.module').then((mod) => mod.MyModule),
        },
        {
          path: 'SecondComponent',
          loadChildren: import('./my/my.module').then((mod) => mod.MyModule),
        },
      ]
    ),
  ],
  exports: [JssModule, AppComponentsSharedModule],
  declarations: [
    // non-lazy components
  ],
})
export class AppComponentsModule { }

Components can also support canActivate and resolve options.

  • canActivate contains a guard to determine whether to render the component.

  • resolve loads component data before rendering the component.

For example:

RequestResponse
@NgModule({
  imports: [
    AppComponentsSharedModule,
    JssModule.withComponents(
      [
        // non-lazy components
      ],
      [
        {
          path: 'FirstComponent',
          loadChildren: import('./my/my.module').then((mod) => mod.MyModule),
          canActivate: [CustomCanActivate, (input: GuardInput) => {
            // The following conditional return statements require JSS 21.9+ or 22.5+ to work correctly.
            // If you're using an earlier version of JSS, omit them from your code.
            /* if (input.routerState.url === '/redirect-command') {
              return new RedirectCommand(input.router.parseUrl('/target'));
            }
            if (input.routerState.url === '/tree-url') {
              return input.router.parseUrl('/target');
            } */
            return of(true);
          }],
        },
        {
          path: 'SecondComponent',
          loadChildren: import('./my/my.module').then((mod) => mod.MyModule),
          canActivate: (input: GuardInput) => {
            return of(true);
          },
          resolve: {
            loadedData: {
              resolve(_input: GuardInput) {
                return { foo: 'bar' };
              },
            },
          }
        },
      ]
    ),
  ],
  exports: [JssModule, AppComponentsSharedModule], declarations: [
    // non-lazy components
  ],
})

export class AppComponentsModule { }
Tip

To use these options in classes, implement JssCanActivate and/or JssResolve interfaces.

Ensure components are styled with display: block

When writing UI components in Angular, the default display style for all new components is inline. For components to work as expected in the Experience Editor, you must set the display style to display: block for all component root elements. For example:

RequestResponse
:host {
  display: block;
}

Another way of working around this limitation is to change the component selector to use an existing DOM element such as <div /> to get the required display styling.

For example:

RequestResponse
@Component({
  selector: 'div [app-hello-world]',
  templateUrl: `
    Hello world
  `,
})
export class HelloWorldComponent {
  rendering: any;
}

Use the sc-placeholder attribute to control the wrapping element

If you want to control the wrapper element for your placeholder content, you can use the sc-placeholder attribute instead of the <sc-placeholder> component.

RequestResponse
<section sc-placeholder name="my-section" [rendering]="rendering"></section>

The Angular Style Guide generally recommends using custom elements instead of attributes or class selectors, so consider your use case carefully.

An alternative to using the sc-placeholder attribute is wrapping the placeholder component in the HTML element you want. For example:

RequestResponse
<section> 
  <sc-placeholder
    name="my-section"    
    [rendering]="rendering"/>
</section>

Use ng-container with structural directives

JSS uses structural directives for field helpers. Because Angular does not allow multiple structural directives on a single element, you might want to apply other structural directives, such as ngIf, ngFor, and ngSwitchCase, on a wrapper component, such as <ng-container />. For example:

RequestResponse
<ng-container *ngIf="condition">
  <span *scText="myField"></span>
</ng-container>

Do you have some feedback for us?

If you have suggestions for improving this article,