Upgrade to Angular 15
Edit on GitHubUpgrading Angular from version 12.* to version 15.*
This document provides instructions for upgrading Angular to version 15 in your Spryker project.
Overview
Our current version of Angular is v12 with v9 compatibility. Angular v12 was deprecated on 2022-11-12, according to the policy. The current stable version of Angular is v15.
Upgrade to the latest major version of Angular to get the most recent bug fixes and security updates. Additionally, this will lead to optimization in both runtime performance and tooling.
Keep in mind that updating to Angular v15 results in incompatibility with older versions of Angular. Therefore, a major release is necessary for these modules:
ZedUiDashboardMerchantPortalGuiGuiTableMerchantProfileMerchantPortalGuiProductMerchantPortalGuiProductOfferMerchantPortalGuiSalesMerchantPortalGuiSecurityMerchantPortalGuiUserMerchantPortalGui
Estimated migration time: 2h
1) Update modules
- Upgrade modules to the new version:
The marketplace modules must correspond to the following versions:
| NAME | VERSION |
|---|---|
| DashboardMerchantPortalGui | >= 2.0.0 |
| GuiTable | >= 2.0.0 |
| MerchantProfileMerchantPortalGui | >= 2.0.0 |
| ProductMerchantPortalGui | >= 3.0.0 |
| ProductOfferMerchantPortalGui | >= 2.0.0 |
| SalesMerchantPortalGui | >= 2.0.0 |
| SecurityMerchantPortalGui | >= 2.0.0 |
| UserMerchantPortalGui | >= 2.0.0 |
| ZedUi | >= 2.0.0 |
If not, update module versions manually or by using the following command:
composer update spryker/dashboard-merchant-portal-gui spryker/gui-table spryker/merchant-profile-merchant-portal-gui spryker/product-merchant-portal-gui spryker/product-offer-merchant-portal-gui spryker/sales-merchant-portal-gui spryker/security-merchant-portal-gui spryker/user-merchant-portal-gui spryker/zed-ui
- Regenerate the data transfer object:
console transfer:generate
2) Update Webpack
Before starting the migration, make sure that Webpack is v5; if it’s v4, the Webpack migration guide is required.
3) Update npm dependencies
Make sure you are using Node.js 18 or later.
-
In
package.json, do the following:-
Update or add the following dependencies:
{ "dependencies": { "@angular/animations": "~15.0.3", "@angular/cdk": "~15.0.3", "@angular/elements": "~15.0.3", "@angular/forms": "~15.0.3", "@angular/router": "~15.0.3", "@angular/common": "~15.0.3", "@angular/compiler": "~15.0.3", "@angular/core": "~15.0.3", "@angular/platform-browser": "~15.0.3", "@angular/platform-browser-dynamic": "~15.0.3", "rxjs": "~7.5.7", "zone.js": "~0.12.0" }, "devDependencies": { "@angular-builders/custom-webpack": "~15.0.0", "@angular-devkit/build-angular": "~15.0.3", "@angular-eslint/builder": "~15.0.0", "@angular-eslint/eslint-plugin": "~15.0.0", "@angular-eslint/eslint-plugin-template": "~15.0.0", "@angular-eslint/schematics": "~15.0.0", "@angular-eslint/template-parser": "~15.0.0", "@angular/cli": "~15.0.3", "@angular/compiler-cli": "~15.0.3", "@angular/language-service": "~15.0.3", "@nrwl/cli": "~15.0.7", "@nrwl/jest": "~15.0.7", "@nrwl/workspace": "~15.0.7", "@types/jest": "~28.1.1", "@typescript-eslint/eslint-plugin": "~5.44.0", "@typescript-eslint/parser": "~5.44.0", "eslint": "~8.28.0", "eslint-plugin-deprecation": "~1.3.3", "jest": "~28.1.3", "jest-environment-jsdom": "~28.1.1", "jest-preset-angular": "~12.2.3", "nx": "~15.0.7", "ts-jest": "~28.0.8", "ts-node": "~10.9.1", "typescript": "~4.8.4" } } -
Remove the following dependencies:
{ "devDependencies": { "@nrwl/tao": "~12.10.1", "codelyzer": "~6.0.0", "typescript-eslint-parser": "~22.0.0" } }
-
-
Update and install package dependencies:
rm -rf node_modules
npm install
Ensure that the package-lock.json file and the node_modules folder have been updated.
4) Update Angular configuration
-
In
frontend/merchant-portalfolder, do the following:-
Rename the
jest.config.jsfile tojest.config.tsand replace its content with the following:export default { displayName: 'merchant-portal', preset: './jest.preset.js', setupFilesAfterEnv: ['<rootDir>/test-setup.ts'], globals: { 'ts-jest': { stringifyContentPathRegex: '\\.(html|svg)$', tsconfig: '<rootDir>/tsconfig.spec.json', }, }, roots: ['<rootDir>/../../vendor/spryker/spryker/Bundles'], testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'], resolver: '@nrwl/jest/plugins/resolver', moduleFileExtensions: ['ts', 'js', 'html'], collectCoverageFrom: ['**/*.ts', '!**/*.stories.ts', '!**/node_modules/**'], coverageReporters: ['lcov', 'text'], coverageDirectory: '<rootDir>/../../coverage/merchant-portal', passWithNoTests: true, }; -
In
jest.preset.js, replace its content with the following:const nxPreset = require('@nrwl/jest/preset').default; module.exports = { ...nxPreset, transform: { '^.+\\.(ts|mjs|js|html)$': 'jest-preset-angular', }, transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], snapshotSerializers: [ 'jest-preset-angular/build/serializers/no-ng-attributes', 'jest-preset-angular/build/serializers/ng-snapshot', 'jest-preset-angular/build/serializers/html-comment', ], }; -
In
tsconfig.spec.json, add theemitDecoratorMetadataproperty to thecompilerOptionssection and add the"jest.config.ts"path to theincludesection:{ ..., "compilerOptions": { ..., "emitDecoratorMetadata": false }, ..., "include": [ ..., "jest.config.ts" ] } -
In
webpack.config.ts, add aliases to resolve imports paths in.lessfiles:config.resolve.alias = { '~@spryker': path.resolve(__dirname, '../../node_modules/@spryker'), '~@angular': path.resolve(__dirname, '../../node_modules/@angular'), };
-
-
In
angular.json, add the following changes:-
Update the
testsection:"jestConfig": "frontend/merchant-portal/jest.config.js", // must be "jestConfig": "frontend/merchant-portal/jest.config.ts","outputs": ["coverage/."] // must be "outputs": ["{projectRoot}/coverage"] -
Remove the
defaultProjectsection.
-
-
Add the
.angularfolder to.gitignoreand.prettierignorefiles:
/.angular/
- In
nx.json, replace its content with the following:
{
"affected": {
"defaultBase": "master"
},
"cli": {
"analytics": false
},
"defaultProject": "merchant-portal",
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
},
"test": {
"inputs": ["default", "^production"]
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"sharedGlobals": [],
"production": [
"default",
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/jest.config.[jt]s"
]
}
}
- In
tsconfig.base.json, add the following changes:- In
compilerOptionssection, change thetargetproperty and add the newuseDefineForClassFieldsproperty. - In
excludesection, add the"**/*.test.ts"file extension.
- In
{
...,
"compilerOptions": {
...,
"target": "es2020",
"useDefineForClassFields": false,
...
},
...,
"exclude": [..., "**/*.test.ts"]
}
- In
tsconfig.mp.json, add the"src/Pyz/Zed/ZedUi/Presentation/Components/environments/environment.prod.ts"path to theincludesection:
{
...,
"include": [
...,
"src/Pyz/Zed/ZedUi/Presentation/Components/environments/environment.prod.ts"
],
}
- In
src/Pyz/Zed/ZedUi/Presentation/Layout/layout.twigtemplate, remove theimportConfigoverride, or remove the whole template if it only contains this change.
5) Add Eslint configuration
-
Remove the following files from the root folder:
.eslintrc.jstslint.mp-githook.jsontslint.mp.json
-
Add a new
.eslintrc.mp.jsonfile to the root folder with the following content:
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "@angular-eslint"],
"env": {
"node": true,
"es6": true
},
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module",
"project": "./tsconfig.mp.json"
},
"extends": [
"./node_modules/@spryker/frontend-config.eslint/.eslintrc.js",
"plugin:@typescript-eslint/recommended",
"plugin:@angular-eslint/recommended"
],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts"],
"plugins": ["deprecation"],
"extends": ["plugin:@angular-eslint/template/process-inline-templates"],
"rules": {
"deprecation/deprecation": "warn",
"no-console": ["warn", {"allow": ["warn", "error"]}],
"no-empty": "error",
"no-use-before-define": "off",
"max-classes-per-file": "off",
"@typescript-eslint/array-type": "off",
"@typescript-eslint/no-restricted-imports": ["error", "rxjs/Rx"],
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-inferrable-types": ["error", {"ignoreParameters": true}],
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/member-ordering": [
"error",
{
"default": ["instance-field", "instance-method", "static-field", "static-method"]
}
],
"@angular-eslint/contextual-lifecycle": "error",
"@angular-eslint/component-class-suffix": "error",
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "mp",
"style": "kebab-case"
}
],
"@angular-eslint/directive-class-suffix": "error",
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "mp",
"style": "camelCase"
}
],
"@angular-eslint/no-conflicting-lifecycle": "error",
"@angular-eslint/no-host-metadata-property": "off",
"@angular-eslint/no-input-rename": "error",
"@angular-eslint/no-inputs-metadata-property": "error",
"@angular-eslint/no-output-native": "error",
"@angular-eslint/no-output-on-prefix": "error",
"@angular-eslint/no-output-rename": "error",
"@angular-eslint/no-outputs-metadata-property": "error",
"@angular-eslint/use-lifecycle-interface": "error",
"@angular-eslint/use-pipe-transform-interface": "error"
}
},
{
"files": ["*.html"],
"parser": "@angular-eslint/template-parser",
"extends": ["plugin:@angular-eslint/template/recommended"],
"rules": {
"@angular-eslint/template/banana-in-box": "error",
"@angular-eslint/template/no-negated-async": "error"
}
}
]
}
- Adjust
angular.jsonto use a neweslintschematic:
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"eslintConfig": ".eslintrc.mp.json",
"lintFilePatterns": [
"src/Pyz/Zed/*/Presentation/Components/**/*.ts",
"src/Pyz/Zed/*/Presentation/Components/**/*.html"
]
}
},
- In
tslint.json, add the"src/Pyz/Zed/*/Presentation/Components/**"path to thelinterOptions.exludesection:
{
...,
"linterOptions": {
"exclude": [
...,
"src/Pyz/Zed/*/Presentation/Components/**"
]
}
}
- Make sure to replace
tslintdisable comments witheslint—for example:
/* tslint:disable:no-unnecessary-class */
// must be
/* eslint-disable @typescript-eslint/no-extraneous-class */
6) Build the project
// Development mode
npm run mp:build
// Production mode
npm run mp:build:production
// Watch mode
npm run mp:build:watch
Thank you!
For submitting the form