Upgrading to Angular version 18

Sterling Store Engagement is upgraded to Angular version 18. Make sure that you migrate your extensions to Angular version 18.

Before you begin

Note: If you cannot migrate to Angular version 18, but want to modify your extensions in Angular version 15, you can continue to build your customizations with the 24.3.9.2-10.0.2409.2 developer toolkit. To get the most recent features, enhancements, and fixes, you must upgrade to Angular version 18.
  • You must have Angular version 15 workspace from the 24.3.9.2-10.0.2409.2 or any Angular version 15 developer toolkit.
  • Make sure that you are on node.js version 18.20.0.

About this task

The migration process involves three steps. First, upgrade from Angular version 15 to 16, second upgrade from version 16 to 17, and third upgrade from version 17 to 18.
Note: Make sure to run all commands from <WORKSPACE>/store-frontend-src folder.

Procedure

  1. Update node-sass to version 8.0.0 in both packages/libs/styles/package.json and extensions/libs/styles/package.json files, then run one of the following commands:
    yarn bootstrap
  2. Upgrade Angular core and CLI to version 16 by running the following command:
    ng update @angular/core@16 @angular/cli@16 --allow-dirty --force
  3. Upgrade Angular core and CLI to version 17 by running the following command:
    ng update @angular/core@17 @angular/cli@17 --allow-dirty --force
  4. Upgrade Angular core and CLI to version 18 by running the following command:
    ng update @angular/core@18 @angular/cli@18 --allow-dirty --force
    Note: When you run this command you encounter an optional migration to use application builder use-application-builder, press enter to proceed
  5. Search and replace the existing code with the new code in all the app.component.ts constructor methods within the extension directory. If there is no occurrence of this code snippet, proceed to step 6.
    The following code illustrates the existing code:
    this.router.events.subscribe((event: RouterEvent) => {
        this.routingStateService.navigationInterceptor(event);
    });
    The following code illustrates the new code:
    this.router.events.pipe(
        filter((event: Event | RouterEvent): event is RouterEvent => event instanceof RouterEvent)
    ).subscribe((event: RouterEvent) => {
        this.routingStateService.navigationInterceptor(event);
    });
    Then, import Event from @angular/router and filter from rxjs/operators.
    import { filter, take } from 'rxjs/operators';
    import { Router, RouterEvent, Event } from '@angular/router';
  6. If you have extended packages/libs/common-components/src/lib/components/donut-chart/donut-chart.component.ts, then search and replace the existing code with the new code in the donut-chart.component.ts file.
    The following code illustrates the existing code:
    const pie = d3.pie().value((d) => d.count).sort(null);
    The following code illustrates the new code:
    const pie = d3.pie().value((d) => d?.['count']).sort(null);
  7. In the angular.json file, search for browserTarget and replace it with buildTarget.
  8. In the most recent developer toolkit 25.1.3.0-10.0.2503.0, browse to the extensions/libs/styles directory, and copy the package.json and webpack.config.js files to your extension folder.
  9. Set up a new workspace from the most recent developer toolkit 25.1.3.0-10.0.2503.0 by completing the steps that follow.
    1. Copy the angular.json file from the previous workspace to the new workspace.
    2. Copy the migrated extensions from the previous workspace to the new workspace.
    3. Rerun the following commands to test extensions:
      yarn bootstrap
      ln -s <store-temp>/store-cli/schematics/node_modules <store-temp>/store-cli/node_modules

      Make sure that you use absolute path while you run the command before this.

    4. Merge incoming changes from application-provided code with extensions to make sure that you are on the most recent version of the code and to avoid any compilation errors.
    5. Consider the next set of steps while you merge your changes.
      1. With the most recent version of RxJS, TypeScript displays an error message, if the argument for the next method is not provided. If you do not want to pass an argument, declare the subject with a void type. Alternatively, if the subject is emitting values from another source and integrated by observers, but you do not want to send any value from one source, pass null as the argument to the next method. The following code illustrates the existing code:
        public addToCartSubject = new Subject<any>();
        this.refreshParentComponent.next();

        The following code illustrates the new code:

        public addToCartSubject = new Subject<void>();
        this.refreshParentComponent.next(null);
      2. ngbAccordion is changed from component to directive in version 16.0.0 of ng-bootstrap. Follow the example after this to make sure all occurrence of ngbAccordion in extensions is upgraded to directive.
        1. Change the accordion code in the component.html file as illustrated in the following example. The following code illustrates the old code:
          <ngb-accordion #acc="ngbAccordion" activeIds="{{activeIds}}"
                                 [attr.tid]="componentId+'filterStatus'">
                                 <ngb-panel id="ngb-panel-assignto">
                                     <ng-template ngbPanelTitle>
                                         <span
                                             [ngClass]="{' app-icon-open-chevron_down_14': isOpen(acc, 'ngb-panel-assignto'), 'app-icon-collapsed-chevron_right_14': !isOpen(acc, 'ngb-panel-assignto') }"
                                             class="app-glyphicons"></span>
                                         <span translate> filterOptions.LABEL_FilterGroupDisplayValueAssignTo</span>
                                     </ng-template>
                                     <ng-template ngbPanelContent>
                                         <isf-combo-box [attr.tid]="componentId+'filterAssignTo'"
                                             [searchText]="selectedUserName" [items]="userList" [columnName]="'name'"
                                             [selectedValues]="[selectedUserName]"
                                             [placeholder]="'filterOptions.LABEL_SelectAssociate'"
                                             [isItemSelected]="selectedUserName" (applySelection)="onSelection($event)"
                                             [resetValue]="resetComboBox"></isf-combo-box>
                                     </ng-template>
                                 </ngb-panel>
                </ngb-accordion>

          The following code illustrates the new code:

          <div ngbAccordion #acc="ngbAccordion"
                                 [attr.tid]="componentId+'filterStatus'">
                                 <div ngbAccordionItem="ngb-panel-assignto" id="ngb-panel-assignto" [collapsed]="!activeIds.includes('ngb-panel-assignto')">
                                     <div ngbAccordionHeader>
                                         <button ngbAccordionButton>
                                             <span
                                             [ngClass]="{' app-icon-open-chevron_down_14': isOpen(acc, 'ngb-panel-assignto'), 'app-icon-collapsed-chevron_right_14': !isOpen(acc, 'ngb-panel-assignto') }"
                                             class="app-glyphicons"></span>
                                         <span translate> filterOptions.LABEL_FilterGroupDisplayValueAssignTo</span>
                                         </button>
                                     </div>
                                     <div ngbAccordionCollapse>
                                         <div ngbAccordionBody>
                                             <ng-template>
                                                 <isf-combo-box [attr.tid]="componentId+'filterAssignTo'"
                                                     [searchText]="selectedUserName" [items]="userList" [columnName]="'name'"
                                                     [selectedValues]="[selectedUserName]"
                                                     [placeholder]="'filterOptions.LABEL_SelectAssociate'"
                                                     [isItemSelected]="selectedUserName" (applySelection)="onSelection($event)"
                                                     [resetValue]="resetComboBox"></isf-combo-box>
                                             </ng-template>
                                         </div>
                                     </div>
                                 </div>
           </div>
        2. In the component.ts file, remove the import of NgbAccordion and add NgbAccordionDirective from @ng-bootstrap/ng-bootstrap.
          import { NgbAccordionDirective } from '@ng-bootstrap/ng-bootstrap';
        3. In the component.ts file, change the isOpen function as shown in the code sample that follows.
          public isOpen(acc: NgbAccordion, panelId: string) {
              return (acc.activeIds.includes(panelId));
            }
          Change as follows:
          public isOpen(acc: NgbAccordionDirective, panelId: string) {
            return acc.isExpanded(panelId)
          }
        4. The PanelChange event is removed in the most recent ngbootsrap. Proceed with the changes that are mentioned by the following:
          1. Replace panelChange with show in the component.html file as shown in the following example:
            (panelChange)="setSelectedLocation($event)"
            Replace as follows:
            (show)="setSelectedLocation($event)"
          2. In component.ts file, change the panelChange event handler function as shown in the following example:
            public setSelectedLocation(event: NgbPanelChangeEvent) {
                if (UIUtil.isNotVoid(event.nextState)) {
                  if (UIUtil.isNotVoid(event.panelId)) {
                    this.selectedLocationId = event.panelId;
                    this._setDefaultAttributeSet(this.selectedLocationId);
                  }
                } else {
                  this.selectedLocationId = '';
                }
              }
            Change to
            public setSelectedLocation(panelId: string) {
                if (UIUtil.isNotVoid(panelId)) {
                    this.selectedLocationId = panelId;
                    this._setDefaultAttributeSet(this.selectedLocationId);
                } else {
                  this.selectedLocationId = '';
                }
              }
      3. If you use ngbDropdown in the bootstrap's navbar, set display attribute to dynamic as shown.
        <div ngbDropdown display="dynamic"></div>
  10. Start the application by running the following command:
    yarn start-app
    Note: When you run the yarn start-app command and encounter the following error, delete the .angular folder from the workspace and rerun the command.

    Module not found: Error: node_modules/@angular-devkit/build-angular/node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js'

    Note: If you use dev or qa cloud instance as a remote server for development purpose, you need to make the changes mentioned in Configuring local development server.