Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
264 changes: 264 additions & 0 deletions adev/src/app/features/update/recommendations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2933,4 +2933,268 @@ export const RECOMMENDATIONS: Step[] = [
action:
"Starting `@angular/ssr` 21.1.5, if your application uses SSR with `CommonEngine`, set the `allowedHosts` option in your `server.ts` (for example, `new CommonEngine({allowedHosts: ['localhost', '*.yourdomain.com']})`). Without it, SSR silently falls back to client-side rendering. This requirement comes from security advisory [GHSA-x288-3778-4hhx](https://github.com/angular/angular-cli/security/advisories/GHSA-x288-3778-4hhx) (also backported to 20.3.17 and 19.2.21).",
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Basic,
step: '22.0.0-update-nodejs-version',
action:
'Angular v22 requires Node.js v22.22.3 or v24.15.0 and later. Update your Node.js version to meet this minimum requirement. You can check your current version with `node --version`.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Basic,
step: '22.0.0-update-typescript-version',
action:
'Update your project to use TypeScript 6.0 or later. Versions older than 6.0 are no longer supported. Use `ng update` which will handle this automatically.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Medium,
step: '22.0.0-data-attributes-input-output-binding',
action:
'Data-prefixed attributes (e.g., `data-*`) no longer bind to inputs or outputs. If you were relying on this behavior, use explicit property bindings instead (e.g., `[attr.data-value]="value"` or `[dataValue]="value"`for a component input).',
Copy link
Copy Markdown
Contributor

@erkamyaman erkamyaman May 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'Data-prefixed attributes (e.g., `data-*`) no longer bind to inputs or outputs. If you were relying on this behavior, use explicit property bindings instead (e.g., `[attr.data-value]="value"` or `[dataValue]="value"`for a component input).',
'Data-prefixed attributes (e.g., `data-*`) no longer bind to inputs or outputs. If you were relying on this behavior, use explicit property bindings instead (e.g., `[attr.data-value]="value"` or `[dataValue]="value"` for a component input).',

},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Medium,
step: '22.0.0-duplicate-input-output-bindings',
action:
'The compiler now throws an error when inputs, outputs, or model are binding to the same property/output. Review your component decorators and ensure no duplicate bindings exist.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Medium,
step: '22.0.0-safe-navigation-nullability-narrowing',
action:
'Safe navigation (`?.`) and nullish coalescing (`??`) now correctly narrow down nullable types in templates. This may trigger `nullishCoalescingNotNullable` and `optionalChainNotNullable` diagnostics on existing projects. Either fix the diagnostics by updating your templates, or temporarily disable them in your `tsconfig.json` under `angularCompilerOptions`.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Medium,
step: '22.0.0-safe-navigation-returns-undefined',
action:
'Angular expressions with optional chaining (`?.`) now return `undefined` instead of `null`. Use can use the `$safeNavigationMigration()` magic function to revert to the previous behavior.',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'Angular expressions with optional chaining (`?.`) now return `undefined` instead of `null`. Use can use the `$safeNavigationMigration()` magic function to revert to the previous behavior.',
'Angular expressions with optional chaining (`?.`) now return `undefined` instead of `null`. You can use the `$safeNavigationMigration()` magic function to revert to the previous behavior.',

},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Advanced,
step: '22.0.0-remove-in-expressions',
action:
'`in` variables in template expressions now throw an error. Use `of` instead of `in` when iterating with `@for`. For example, change `@for (item of items; let i = $index; let isFirst = $first)` instead of using `in`.',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: I might be missing context here, but am I reading the breaking change wrong? My understanding is that it's about {{in}} no longer being parsed as the string 'in' in template expressions, and <input #in /> still works. The current text talks about @for ... in but @for has always used of, so I'm a little lost on how the @for example connects. Could the example be about {{in}} instead?

},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Advanced,
step: '22.0.0-animation-callback-event-signature-change',
action:
'The `AnimationCallbackEvent.animationComplete` signature has changed. Update any code that depends on the old signature of this event. Review your animation event handlers and tests.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Medium,
step: '22.0.0-http-xhr-backend-explicit-opt-in',
action:
'If your application uses upload progress reporting through `HttpXhrBackend`, explicitly opt-in by using `provideHttpClient(withXhr)`. The default HTTP client no longer includes XHR support by default.',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'If your application uses upload progress reporting through `HttpXhrBackend`, explicitly opt-in by using `provideHttpClient(withXhr)`. The default HTTP client no longer includes XHR support by default.',
'If your application uses upload progress reporting through `HttpXhrBackend`, explicitly opt-in by using `provideHttpClient(withXhr())`. The default HTTP client no longer includes XHR support by default.',

},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Medium,
step: '22.0.0-deprecate-report-progress-option',
action:
'The `reportProgress` option in HTTP requests is deprecated. Use `reportUploadProgress` or `reportDownloadProgress` instead for more explicit control over progress reporting.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Medium,
step: '22.0.0-provide-routes-removed',
action:
'`provideRoutes()` has been removed. Use `provideRouter()` instead, or configure routes as a multi token using `ROUTES` if necessary. Update your application bootstrap configuration.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Advanced,
step: '22.0.0-upgrade-angular-js-global-migration',
action:
'If using AngularJS interoperability, replace deprecated `getAngularLib()` and `setAngularLib()` with `getAngularJSGlobal()` and `setAngularJSGlobal()` respectively.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Advanced,
step: '22.0.0-remove-component-factory-resolver',
action:
'`ComponentFactoryResolver` and `ComponentFactory` are no longer available. Pass the component class directly to APIs like `ViewContainerRef.createComponent()` or use the standalone `createComponent()` function instead.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Advanced,
step: '22.0.0-remove-create-ng-module-ref',
action:
'`createNgModuleRef` has been removed. Use `createNgModule()` instead for dynamic module creation scenarios.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Medium,
step: '22.0.0-compile-time-duplicate-selectors',
action:
'Elements with multiple matching selectors now throw a compile-time error. Ensure your components use unique selectors and review any directives that might have conflicting selectors.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Medium,
step: '22.0.0-component-onpush-default',
action:
'Components with no `changeDetection` property defined are now `OnPush` by default. To maintain `Eager` (the previous default) change detection, explicitly set `changeDetection: ChangeDetectionStrategy.Eager` in your component decorator.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Advanced,
step: '22.0.0-remove-check-no-changes',
action:
'`ChangeDetectorRef.checkNoChanges()` has been removed. In tests, use `fixture.detectChanges()` instead or verify your component state through other means.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Medium,
step: '22.0.0-leave-animations-scope-change',
action:
'Leave animations are no longer limited to the element being removed. They now support nested animations scoped to component boundaries. Review your animation configurations if you relied on the previous scoping behavior.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Medium,
step: '22.0.0-params-inheritance-strategy-default',
action:
'`paramsInheritanceStrategy` now defaults to `"always"` instead of `"emptyOnly"`. This means route parameters are inherited from all parent routes. To restore the previous behavior, explicitly set `paramsInheritanceStrategy: "emptyOnly"` in your router configuration.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Advanced,
step: '22.0.0-can-match-current-snapshot-required',
action:
'The `currentSnapshot` parameter in `CanMatchFn` and the `canMatch` method of the `CanMatch` interface is now required. Update any class implementations of `CanMatch` to include this required third argument.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Medium,
step: '22.0.0-hammer-js-removed',
action:
'Hammer.js integration has been removed from Angular platform-browser. If you need touch gesture support, implement your own gesture detection or use an alternative library.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Advanced,
step: '22.0.0-app-ref-bootstrap-typing',
action:
'The second argument of `appRef.bootstrap()` no longer accepts `any` type. Ensure the element you pass is not nullable and matches the expected type.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Medium,
step: '22.0.0-platform-browser-styles-removal',
action:
'Unused styles are now automatically removed when their associated `host` is dropped. Be aware that other DOM on the page may be affected if those styles are used by elements outside of Angular or if not using `ViewEncapsulation.Emulated`.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Advanced,
step: '22.0.0-title-strategy-return-type',
action:
'The return type for `TitleStrategy.getResolvedTitleForRoute` has changed from `any` to a stricter type (e.g., `string | undefined`). Update your custom `TitleStrategy` implementations to match the new signature.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Basic,
step: '22.0.0-incremental-hydration-default',
action:
'Incremental hydration is now the default behavior for applications using Server-Side Rendering (SSR). Review your application if you relied on the previous non-incremental hydration behavior.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Medium,
step: '22.0.0-full-template-type-check-removed',
action:
'The `fullTemplateTypeCheck` compiler option has been removed. Use `strictTemplates` instead to enable strict template type checking in your `tsconfig.json`.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Basic,
step: '22.0.0-strict-templates-default',
action:
'The `strictTemplates` compiler option now defaults to `true`. If your project was not using strict template type checking, you may see new compilation errors. Resolve these errors or explicitly set `strictTemplates: false` in your `tsconfig.json` to opt out.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Advanced,
step: '22.0.0-webpack-builders-deprecated',
action:
'Webpack builders (`@angular-devkit/build-angular` and `@angular-devkit/build-webpack`) are now deprecated. Migrate to the `@angular/build` builders (esbuild/application) for your application builds.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Advanced,
step: '22.0.0-ssr-commonengine-deprecated',
action:
'`CommonEngine` APIs from `@angular/ssr` are deprecated. Migrate to `AngularNodeAppEngine` or `AngularAppEngine` instead.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Medium,
step: '22.0.0-istanbul-lib-instrument-optional',
action:
'`istanbul-lib-instrument` is now an optional peer dependency. If your project uses Karma with code coverage enabled, ensure `istanbul-lib-instrument` is explicitly installed.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Basic,
step: '22.0.0-dev-server-port-env-priority',
action:
'`ng serve` now gives the highest priority to the `PORT` environment variable. This value overrides any port configured in `angular.json` or provided via the `--port` flag.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Advanced,
step: '22.0.0-architect-cli-removed',
action:
'The `@angular-devkit/architect-cli` package is no longer available. Use the `architect` CLI tool from the `@angular-devkit/architect` package instead.',
},
{
possibleIn: 2200,
necessaryAsOf: 2200,
level: ApplicationComplexity.Medium,
step: '22.0.0-experimental-test-builders-removed',
action:
'The experimental `@angular-devkit/build-angular:jest` and `@angular-devkit/build-angular:web-test-runner` test builders have been removed.',
},
];
7 changes: 4 additions & 3 deletions adev/src/app/features/update/update.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export default class UpdateComponent {
protected afterRecommendations: Step[] = [];

protected readonly versions = [
{name: '22.0', number: 2200},
{name: '21.0', number: 2100},
{name: '20.0', number: 2000},
{name: '19.0', number: 1900},
Expand Down Expand Up @@ -119,9 +120,9 @@ export default class UpdateComponent {
{name: '2.1', number: 201},
{name: '2.0', number: 200},
];
protected from = this.versions.find((version) => version.name === '20.0')!;
protected to = this.versions.find((version) => version.name === '21.0')!;
protected futureVersion = 2200;
protected from = this.versions.find((version) => version.name === '21.0')!;
protected to = this.versions.find((version) => version.name === '22.0')!;
protected futureVersion = 2300;

protected readonly steps: Step[] = RECOMMENDATIONS;

Expand Down
Loading