Upgrade JSS apps to JSS 22.10
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.
-
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:
-
In your existing application's
package.jsonfile, update the following dependencies to the specified versions:RequestResponse"@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" -
In the
src/appfolder:-
Add a new file called
ViewBag.tswith the following content:RequestResponseexport interface ViewBag { [key: string]: unknown; dictionary: { [key: string]: string }; } -
Add a new file called
injection-tokens.tswith the following content:RequestResponseimport { 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');
-
-
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:RequestResponseconstructor(protected transferState: TransferState, protected layoutService: JssLayoutService, protected stateService: JssStateService<JssState>) {}To use the
inject()method here:-
Import the method from the
angularpackage:RequestResponseimport { Injectable, TransferState, makeStateKey, inject } from '@angular/core'; -
Use the
inject()function to remove the field initializations from the constructor as shown:RequestResponseprotected 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.tsfiles), etc.
-
-
For all the modules using
declarations, move the entries intoimportsinstead.-
For example, in
src/app/routing/routing.module.ts, the existing declarations are as shown:RequestResponse@NgModule({ imports: [ .... ], exports: [...], declarations: [NotFoundComponent, ServerErrorComponent, LayoutComponent, NavigationComponent],Move the entries to the imports as shown:
RequestResponse@NgModule({ imports: [ ... LayoutComponent, NotFoundComponent, ServerErrorComponent, ], exports: [RouterModule, TranslateModule], ... }) -
Similarly, in
src/app/routing/scripts/scripts.module.ts, move theScriptsComponentandVisitorIdentificationComponent(if present) fromdeclarationstoimportsas shown:RequestResponse@NgModule({ exports: [ScriptsComponent], imports: [ScriptsComponent, VisitorIdentificationComponent], }) -
Perform the same step for
StyleguideSpecimenComponentinsrc/app/components/app-components.shared.module.tsand any other component modules you wish to have as standalone.
-
-
The
app.component.tsfile will remain non-standalone for simplicity and continued support of SSR proxy. Make the following changes to theapp.component.tsfile:-
Mark the component with
standalone: falseas shownRequestResponse@Component({ selector: 'app-root', templateUrl: './app.component.html' templateUrl: './app.component.html', /* eslint-disable-next-line @angular-eslint/prefer-standalone */ standalone: false, }) -
Implement the
OnInitinterfaceRequestResponseexport class AppComponent implements OnInit, OnDestroy { -
Add the
ngInit()method:RequestResponsengOnInit() { 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:RequestResponse... private contextSubscription: Subscription; private translate = inject(TranslateService); private jssContextService = inject(JssContextService); ... -
Making the app-component non-standalone allows you to keep the
declarationssection inapp.module.tsas is, butapp.server.module.tsshould be modified to use an injection token.Find the existing string token:
RequestResponseTranslateModule.forRoot({ .... deps: ['JSS_SERVER_VIEWBAG', TransferState], .... }),Replace it with an imported token object:
RequestResponseimport { JSS_SERVER_VIEWBAG } from './src/app/injection-tokens'; .... .... TranslateModule.forRoot({ ... deps: [JSS_SERVER_VIEWBAG, TransferState], ... }),
-
-
Use injection tokens in the
server.bundle.tsfile:-
Import the injection tokens:
RequestResponseimport { JSS_SERVER_LAYOUT_DATA, JSS_SERVER_VIEWBAG } from './src/app/injection-tokens'; -
Add the injection tokens to the
extraProviderssection of therenderModulefunction as shown:RequestResponserenderModule(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 }, ], })
-
-
In the app's
src/app/routing/layout/layout.component.tsfile:-
Ensure the following imports are present:
RequestResponseimport { 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
importssection for theComponentdirective:RequestResponse@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:RequestResponseprivate activatedRoute = inject(ActivatedRoute); private readonly meta = inject(JssMetaService); private linkService = inject(JssLinkService);
-
-
In the app's
jss-graphql.module.tsfile:-
Import
injectfrom theangularpackage, replaceApolloModulewithprovideApollofrom theapollo-angularpackage, and import theAPOLLO_CACHEinjection token as shown:RequestResponseimport { 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:RequestResponseconst 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
ApolloModulefromimportsand expand theproviderssection as shown:RequestResponse@NgModule({ imports: [ HttpClientModule, // provides HttpClient for HttpLink ], providers: [ JssGraphQLService, { provide: APOLLO_CACHE, useValue: getGraphQLCache() }, provideApollo(getApolloOptions), ], }) -
Modify the module constructor as shown:
RequestResponseconstructor() { this.addCacheEvents(); } -
Initialize two module fields with the
inject()function:RequestResponseprivate readonly transferState = inject(TransferState); apolloClient = inject(Apollo); -
Replace the
createApolloClientmethod with the following:RequestResponseprivate addCacheEvents(): void { const cache = inject(APOLLO_CACHE); const isBrowser = this.transferState.hasKey(STATE_KEY); if (isBrowser) { this.onBrowser(cache); } else { this.onServer(cache); } }
-
-
In the app's
src/app/jss-graphql.service.tsfile, use theinject()function to remove the field initializations from the constructor as shown:RequestResponseprotected apollo = inject(Apollo); protected sitecoreContext = inject(JssContextService); protected platformId = inject(PLATFORM_ID); -
You can optionally replace all usages of
ngIfandngForin your components with the new@ifand@fordirectives. See the updated Angular sample for examples on how to do it. To continue usingNgIfandNgForwith your standalone components, importngIfandngForfrom the@angular/core packageand add them to your component’simportssection:RequestResponse@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:
-
In your existing application's
package.jsonfile, update every@sitecore-jsspackage to version~22.10.0. -
Install the dependencies with the following command:
RequestResponsenpm install