Working with placeholders in a JSS Angular app

Current version: 21.x

Placeholders are vital components in any application built with Sitecore JSS. To provide examples, this topic uses the Sitecore JSS Angular sample application.

The JSS Angular sample application comes with a root placeholder. The root placeholder is defined in src/app/routing/layout/layout.component.html.

The sc-placeholder component is imported from Sitecore JSS for Angular, @sitecorelabs/sitecore-jss-angular.

RequestResponse
<sc-placeholder name="jss-main" [rendering]="route" (loaded)="onPlaceholderLoaded($event)"></sc-placeholder><sc-placeholder name="jss-main" [rendering]="route"></sc-placeholder>

The sc-placeholder Angular component has a name attribute with the value jss-main, indicating that it represents the Sitecore placeholder named jss-main. The placeholder renders one or more components based on route data, as indicated by the data bound to the [rendering] input of the placeholder component:

RequestResponse
<sc-placeholder name="jss-main" [rendering]="route" (loaded)="onPlaceholderLoaded($event)"></sc-placeholder>
Note

You can apply sc-placeholder to an existing DOM element as an attribute.

Understanding the origin of route data

To understand how the placeholder component receives the route data, you must understand how the various components, application modules, and services work together.

In production, the script for building the JSS Angular application builds a server bundle and a client bundle to support universal rendering. After running the script jss build, you can find the bundles at the following locations:

  • Client bundle - located in dist/browser/.

  • Server bundle - located at dist/server.bundle.js.

The two bundles cannot run independently. They must be served by a server. JSS comes with a JavaScript view engine for Node that can run the server bundle. The JSS JavaScript view engine expects a server bundle that exports a renderView function.

To build and serve the two bundles, the JSS Angular app defines two entry points for the application, with associated app modules and data services.

The file src/main.server.ts defines the entry point for the server bundle, bootstrapping the application module for the server AppServerModule, defined in src/app/app.server.module.ts. The AppServerModule uses the JssContextServerSideService, defined in src/app/jss-context.server-side.service.ts, that runs on the server and stores the context data for the JSS app. The context data contains the data for the current route and Sitecore context data.

The file src/main.ts defines the entry point for the client bundle, bootstrapping the application module for client rendering AppModule, defined in src/app/app.module.ts. The AppModule uses the JssContextService, defined in src/app/jss-context.service.ts, that runs on the client (in the browser) and stores the context data for the JSS app.

When the application runs in the browser from the server bundle, the server invokes the renderView function that renders the AppServerModule, preparing the context data for the JSS app. For implementation details, see the file server.bundle.ts and associated services. Both context services use the Angular TransferState class. The class is a key-value store that is transferred from the application module on the server-side to the application module on the client-side, after normalizing the data:

RequestResponse
    renderModule(AppServerModule, {
      document: template,
      url: path,
      extraProviders: [
        // custom injection with the initial state that SSR should utilize
        { provide: 'JSS_SERVER_LAYOUT_DATA', useValue: transferState },
        { provide: 'JSS_SERVER_VIEWBAG', useValue: state.viewBag },
      ],
    })
      .then((html) => callback(null, { html }))
      .catch((err) => callback(err, null));

The client-side AppModule bootstraps the root application component AppComponent, defined in src/app/app.component.ts , and imports the RoutingModule class, defined in the file src/app/routing/routing.module.ts. The RoutingModule is responsible for attaching the Angular router to the application root, allowing the JSS Angular app to use an Angular router outlet.

The RoutingModule defines which component renders in the router outlet placeholder. This component is LayoutComponent, defined in the src/app/routing/layout/layout.component.ts file. The RoutingModule uses the JssRouteResolver provider that, when resolving a route, sets the state for the active route.

Finally, the LayoutComponent, injected with both route and layout data, can subscribe to the active route and extract the route data from the Sitecore context.

At this point, the placeholder component can render the HTML markup for the route in a production setting.

If you work in disconnected mode, the JSS application retrieves route data from locally defined files. The sample application includes some pre-defined local data in the folder src/data. When loading a route, for example, the home page, the application fetches data from the file src/data/en.yml. The response contains the JSON data for the route:

RequestResponse
{
  "context": {
    "pageEditing": false,
    "site": {
      "name": "JssDisconnectedLayoutService"
    },
    "pageState": "normal",
    "language": "en"
  },
  "route": {
    "databaseName": "available-in-connected-mode",
    "deviceId": "available-in-connected-mode",
    "itemId": "home-page",
    "itemLanguage": "en",
    "itemVersion": 1,
    "layoutId": "available-in-connected-mode",
    "templateId": "available-in-connected-mode",
    "templateName": "available-in-connected-mode",
    "name": "home",
    "fields": {
      "pageTitle": {
        "value": "Welcome to Sitecore JSS"
      }
    },
    "placeholders": {
      "jss-main": [
        {
          "uid": "{2C4A53CC-9DA8-5F51-9D79-6EE2FC671B2D}",
          "componentName": "ContentBlock",
          "dataSource": "available-in-connected-mode",
          "params": {},
          "fields": {
            "heading": {
              "value": "Welcome to Sitecore JSS"
            },
            "content": {
              "value": "<p>Thanks for using JSS. Here are some resources to get you started:</p>"
            }
          }
        }
      ]
    }
  }}

The JSON data structure, in the route property, contains a list of placeholders. Each placeholder name is associated with an array of components. We call the mapping of components to placeholders component placement rules.

Because the sc-placeholder component has access to the placeholders object within the route property, it finds the matching placeholder by name (jss-main) and renders the array of components within it. In this example, it renders the ContentBlockComponent component, a regular Angular component, dynamically and provides the heading and content, fields through a dependency injection token:

RequestResponse
import { Component, Input } from '@angular/core';
import { ComponentRendering } from '@sitecore-jss/sitecore-jss-angular';
@Component({
  selector: 'app-content-block',
  templateUrl: './content-block.component.html',
})
export class ContentBlockComponent {
  @Input() rendering: ComponentRendering;
}

The rendering property includes all the information regarding the ContentBlockComponent component coming from the data:

RequestResponse
"componentName": "ContentBlock",
    "dataSource": "available-in-connected-mode",
    "params": {},
    "fields": {
        "heading": {
            "value": "Welcome to Sitecore JSS"
        },
    "content": {
        "value": "<p>Thanks for using JSS. Here are some resources to get you started:</p>"
    }
}

Nesting placeholders and components

Because components can contain their own sc-placeholder components, you can define their contents with placeholders in the component placement rules.

For example, the WelcomeComponent component might contain the following:

RequestResponse
@Component({
  selector: 'app-welcome',
  template: `
    <sc-placeholder name="welcome" [rendering]="rendering"></sc-placeholder>
  `
})
export class WelcomeComponent {
  rendering: any;
}

In this case, it then defines its child components in its component placement rules:

RequestResponse
{
  componentName: 'Welcome',
    fields: {
      title: {
        value: 'Sitecore Experience Platform + JSS',
      },
      text: {
        value: '<p>...</p>',
      },
      logoImage: {
        value: {
          src: '/assets/img/sc_logo.png',
          alt: 'Logo'
        },
      },
    },
    placeholders: {
      welcome: [
        {
          componentName: 'AnotherComponent',
          fields: {
            /* etc */
          }
        }
      ]
    }
}

Input and output binding

It is possible to bind custom properties on components using the [inputs] and [outputs] binding properties on sc-placeholder. These properties allow input and output binding to properties on all components within the placeholder.

This allows for orchestration/interaction with child components, even though they are created dynamically by sc-placeholder, and can be particularly useful in complex user flows, which you still want to manage/optimize through Sitecore XP.

The following is a simplified example of using [inputs] and [outputs]:

RequestResponse
@Component({
  selector: 'my-container-component',
  template: `<sc-placeholder
                name="placeholder"
                [rendering]="rendering"
                [inputs]="inputs"
                [outputs]="outputs"
                ></sc-placeholder>`
})
class MyContainerComponent {
  @Input() public rendering: any;

  inputs = {
    hello: 'world',
    something: () => 'can be really complex'
  };
  outputs = {
    onSomething: (type) => alert(type)
  }
}

@Component({selector: 'my-rendering')
class MyRendering {
  // standard on any placeholder-managed component
  @Input() public rendering: any;

  // additional properties for input/output binding
  @Input() hello: string;
  @Input() something: Function;
  @Output() onSomething = new EventEmitter<string>();
}

Lazy loading placeholders

When lazy loading a Sitecore component, the placeholder appears empty by default. You can specify a temporary body for the component that shows while the component is loading. When the component is finished loading, the temporary body is replaced with the actual content. The following is a simplified example:

RequestResponse
  <sc-placeholder [rendering]="rendering">
    <img *scPlaceholderLoading src="loading.gif">
  </sc-placeholder>

Enhancing placeholders

The Placeholder component supports several customizations that can enhance the developer experience.

Error components

If a rendering error occurs in a placeholder, the placeholder displays an error component instead of the placeholder contents and logs the error details to the console. You can customize this component by substituting your own React component using the errorComponent prop.

Missing Component components

If a placeholder contains a rendering name that is unknown to the componentFactory (for example, a back-end developer creates a Foo rendering and adds it to a page, but there is no foo.component.ts yet), the rendering is replaced with a MissingComponent component defined in the missingComponentComponent property of your Angular component. The default implementation is a simple message, but you can customize it by using your own Angular component on the missingComponentComponent property.

Handling hidden renderings

If a rendering is hidden by personalization rules, the rendering is replaced by a HiddenRendering component to prevent error messages when the rendering is missing a JSS implementation. You can create your own component and provide it to the placeholder using the hiddenRenderingComponent property.

Do you have some feedback for us?

If you have suggestions for improving this article,