1. Sitecore JavaScript Services SDK (JSS)

Upgrade JSS apps to JSS 22.10

Version:

Sitecore JSS version 22.10 primarily focuses on support for Angular 20. For Angular apps, you will need to update multiple dependencies. In general, the only other requirement is to update and install core JSS dependencies before re-testing your apps.

Before you begin
  • If you haven't already done so, upgrade your app to JSS 22.9.

  • Since this process also involves updating Angular to version 20, you'll need to refer to the official guide to complete the upgrade process for your apps.

  • Familiarize yourself with the JSS 22.10 changelog to ensure you understand the implications of upgrading.

This topic describes how to:

Update the Angular template files in your existing app

This version of JSS upgrades Angular to version 20 that switches your components to be standalone by default. You should refer to the official Angular guide to complete the upgrade process for your apps and familiarize with standalone components and relevant migrations.

To upgrade your JSS app to Angular 20:

  1. In your existing application's package.json file, update the following dependencies to the specified versions:

    "@angular/animations": "~20.3.10",
    "@angular/common": "~20.3.10",
    "@angular/compiler": "~20.3.10",
    "@angular/core": "~20.3.10",
    "@angular/forms": "~20.3.10",
    "@angular/platform-browser": "~20.3.10",
    "@angular/platform-browser-dynamic": "~20.3.10",
    "@angular/platform-server": "~20.3.10",
    "@angular/router": "~20.3.10",
    ....
    "apollo-angular": "~11.0.0",
    ...
    "graphql": "~16.11.0",
    ...
    "zone.js": "~0.15.0",
    ...
    "@angular-builders/custom-webpack": "^20.0.0",
    "@angular-devkit/build-angular": "^20.3.9",
    "@angular-eslint/builder": "^20.5.1",
    "@angular-eslint/eslint-plugin": "^20.5.1",
    "@angular-eslint/eslint-plugin-template": "^20.5.1",
    "@angular-eslint/schematics": "^20.5.1",
    "@angular-eslint/template-parser": "^20.5.1",
    "@angular/cli": "~20.3.9",
    "@angular/compiler-cli": "~20.3.10",
    "@angular/language-service": "~20.3.10",
    ...
    "typescript": "~5.8.0"
  2. In the src/app folder:

    • Add a new file called ViewBag.ts with the following content:

      export interface ViewBag {
          [key: string]: unknown;
          dictionary: { [key: string]: string };
      }
    • Add a new file called injection-tokens.ts with the following content:

      import { InjectionToken } from '@angular/core';
      import { InMemoryCache } from '@apollo/client/core';
      import { JssState } from './JssState';
      import { ViewBag } from './ViewBag';
      
      /**
       * Injection token for server-side layout data provided by server.bundle
       */
      export const JSS_SERVER_LAYOUT_DATA = new InjectionToken<JssState>('JSS_SERVER_LAYOUT_DATA');
      
      export const JSS_SERVER_VIEWBAG = new InjectionToken<ViewBag>('JSS_SERVER_VIEWBAG');
      
      export const APOLLO_CACHE = new InjectionToken<InMemoryCache>('APOLLO_CACHE');
  3. Use the inject() method to to replace field initializations in the constructor for all instances of class fields.

    For example, in src/app/jss-context.service.ts, the original constructor is as shown:

    constructor(protected transferState: TransferState, protected layoutService: JssLayoutService, protected stateService: JssStateService<JssState>) {}

    To use the inject() method here:

    • Import the method from the angular package:

      import { Injectable, TransferState, makeStateKey, inject } from '@angular/core';
    • Use the inject() function to remove the field initializations from the constructor as shown:

      protected transferState = inject(TransferState);
      protected layoutService = inject(JssLayoutService);
      protected stateService = inject(JssStateService<JssState>);
    • Repeat the above steps for the rest of the app logic like components, services (for example, src/app/jss-context.server.ts, jss-graphql.service.ts files), etc.

  4. For all the modules using declarations, move the entries into imports instead.

    • For example, in src/app/routing/routing.module.ts, the existing declarations are as shown:

      @NgModule({
        imports: [
          ....
        ],
        exports: [...],
        declarations: [NotFoundComponent, ServerErrorComponent, LayoutComponent, NavigationComponent],

      Move the entries to the imports as shown:

      @NgModule({
        imports: [
          ...
          LayoutComponent,
          NotFoundComponent,
          ServerErrorComponent,
        ],
        exports: [RouterModule, TranslateModule],
        ...
      })
    • Similarly, in src/app/routing/scripts/scripts.module.ts, move the ScriptsComponent and VisitorIdentificationComponent (if present) from declarations to imports as shown:

      @NgModule({
        exports: [ScriptsComponent],
        imports: [ScriptsComponent, VisitorIdentificationComponent],
      })
    • Perform the same step for StyleguideSpecimenComponent in src/app/components/app-components.shared.module.ts and any other component modules you wish to have as standalone.

  5. The app.component.ts file will remain non-standalone for simplicity and continued support of SSR proxy. Make the following changes to the app.component.ts file:

    • Mark the component with standalone: false as shown

      @Component({
        selector: 'app-root',
        templateUrl: './app.component.html'
        templateUrl: './app.component.html',
        /* eslint-disable-next-line @angular-eslint/prefer-standalone */
        standalone: false,
      })
    • Implement the OnInit interface

      export class AppComponent implements OnInit, OnDestroy {
    • Add the ngInit() method:

      ngOnInit() {
      this.contextSubscription = this.jssContextService.state.subscribe((jssState: { language: string }) => {
          // listen for language changes
          if (jssState.language) {
          this.translate.use(jssState.language);
          }
      });
      }
    • Remove the original constructor and initialize fields with the inject() method:

      ...
      private contextSubscription: Subscription;
      private translate = inject(TranslateService);
      private jssContextService = inject(JssContextService);
      ...
    • Making the app-component non-standalone allows you to keep the declarations section in app.module.ts as is, but app.server.module.ts should be modified to use an injection token.

      Find the existing string token:

      TranslateModule.forRoot({
            ....
              deps: ['JSS_SERVER_VIEWBAG', TransferState],
            ....
          }),

      Replace it with an imported token object:

      import { JSS_SERVER_VIEWBAG } from './src/app/injection-tokens';
      ....
      ....
      TranslateModule.forRoot({
            ...
              deps: [JSS_SERVER_VIEWBAG, TransferState],
            ...
          }),
  6. Use injection tokens in the server.bundle.ts file:

    • Import the injection tokens:

      import { JSS_SERVER_LAYOUT_DATA, JSS_SERVER_VIEWBAG } from './src/app/injection-tokens';
    • Add the injection tokens to the extraProviders section of the renderModule function as shown:

      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 },
            ],
          })
  7. In the app's src/app/routing/layout/layout.component.ts file:

    • Ensure the following imports are present:

      import { Component, OnInit, OnDestroy, inject } from '@angular/core';
      import {
        RouteData,
        Field,
        LayoutServiceContextData,
        getContentStylesheetLink,
        JssModule,
      } from '@sitecore-jss/sitecore-jss-angular';
      ...
      import { NotFoundComponent } from '../not-found/not-found.component';
      import { ServerErrorComponent } from '../server-error/server-error.component';
      import { ScriptsComponent } from '../scripts/scripts.component';
      ...
      import { CommonModule } from '@angular/common';
    • Add an imports section for the Component directive:

      @Component({
        selector: 'app-layout',
        templateUrl: './layout.component.html',
        imports: [CommonModule, JssModule, NotFoundComponent, ServerErrorComponent, ScriptsComponent],
      })
    • Use the inject() function to remove the field initializations from the constructor as shown:

      private activatedRoute = inject(ActivatedRoute);
      private readonly meta = inject(JssMetaService);
      private linkService = inject(JssLinkService);
  8. In the app's jss-graphql.module.ts file:

    • Import inject from the angular package, replace ApolloModule with provideApollo from the apollo-angular package, and import the APOLLO_CACHE injection token as shown:

      import { makeStateKey, TransferState, NgModule, PLATFORM_ID, inject } from '@angular/core';
      ....
      import { Apollo, provideApollo } from 'apollo-angular';
      ...
      import { APOLLO_CACHE } from './injection-tokens';
    • Add the following two new function outside of GraphQLModule:

      const getGraphQLCache = () => {
        const possibleTypes = {} as PossibleTypesMap;
        introspectionQueryResultData.__schema.types.forEach((supertype) => {
          possibleTypes[supertype.name] = supertype.possibleTypes.map((subtype) => subtype.name);
        });
        return new InMemoryCache({
          possibleTypes,
        });
      };
      
      const getApolloOptions = () => {
        const httpLink = inject(HttpBatchLink);
        const platformId = inject(PLATFORM_ID);
        /*
        QUERY LINK SELECTION
        A link is transport which GraphQL queries are pushed across.
        You have many choices.
        See the apollo-link documentation for more details.
      */
      
        // set sc_apikey header which is required for any GraphQL calls
        const sc_apikey = new HttpHeaders().set('sc_apikey', environment.sitecoreApiKey);
      
        // choose between a basic HTTP link to run queries...
        // import { createHttpLink } from 'apollo-angular-link-http';
        // const link = createHttpLink({ uri: endpoint });
      
        // ...or a batched link (multiple queries within 10ms all go in one HTTP request)
        const batchHttp = httpLink.create({
          uri: environment.graphQLEndpoint,
          headers: sc_apikey,
        });
      
        const cache = inject(APOLLO_CACHE);
      
        return {
          link: batchHttp,
          cache,
          ssrMode: isPlatformServer(platformId),
          ssrForceFetchDelay: 100,
        };
      }
    • Remove ApolloModule from imports and expand the providers section as shown:

      @NgModule({
        imports: [
          HttpClientModule, // provides HttpClient for HttpLink
        ],
        providers: [
          JssGraphQLService,
          { provide: APOLLO_CACHE, useValue: getGraphQLCache() },
          provideApollo(getApolloOptions),
        ],
      })
    • Modify the module constructor as shown:

      constructor() {
        this.addCacheEvents();
      }
    • Initialize two module fields with the inject() function:

      private readonly transferState = inject(TransferState);
      apolloClient = inject(Apollo);
    • Replace the createApolloClient method with the following:

      private addCacheEvents(): void {
          const cache = inject(APOLLO_CACHE);
      
          const isBrowser = this.transferState.hasKey(STATE_KEY);
      
          if (isBrowser) {
              this.onBrowser(cache);
          } else {
              this.onServer(cache);
          }
      }
  9. In the app's src/app/jss-graphql.service.ts file, use the inject() function to remove the field initializations from the constructor as shown:

    protected apollo = inject(Apollo);
    protected sitecoreContext = inject(JssContextService);
    protected platformId = inject(PLATFORM_ID);
  10. You can optionally replace all usages of ngIf and ngFor in your components with the new @if and @for directives. See the updated Angular sample for examples on how to do it. To continue using NgIf and NgFor with your standalone components, import ngIf and ngFor from the @angular/core package and add them to your component’s imports section:

    @Component({
      ...
      imports: [NgIf, NgFor, ....],
    })
    export class MyComponent implements .... {...

Update application dependencies in your existing app

For your upgraded application to work correctly, you will need to update dependencies.

To update your dependencies:

  1. In your existing application's package.json file, update every @sitecore-jss package to version ~22.10.0.

  2. Install the dependencies with the following command:

    npm install
If you have suggestions for improving this article, let us know!