Skip to content

[ZEPPELIN-6424] Upgrade zeppelin-web-angular from Angular 13 to Angular 21#5260

Merged
tbonelee merged 39 commits into
apache:masterfrom
tbonelee:upgrade/angular-21
Jun 2, 2026
Merged

[ZEPPELIN-6424] Upgrade zeppelin-web-angular from Angular 13 to Angular 21#5260
tbonelee merged 39 commits into
apache:masterfrom
tbonelee:upgrade/angular-21

Conversation

@tbonelee
Copy link
Copy Markdown
Contributor

@tbonelee tbonelee commented May 31, 2026

What is this PR for?

zeppelin-web-angular was on Angular 13 (end-of-life). This upgrades it to Angular 21 (latest), one major version at a time so each step stays small and reviewable. The production build, lint, and dev-server are green at every bump.

Included:

  • Angular 13 to 21 (framework, CLI, CDK), bumped lock-step one major at a time
  • ng-zorro-antd to 21, adapting to its breaking changes (moved/removed entry points, checkbox / input-number / modal API changes, native CSS animations)
  • Pinned Node 18 to 22 (npm 8 to 10) for the frontend-maven-plugin and engines
  • ESLint 8 (eslintrc) to ESLint 9 (flat config)
  • Adopt required modern Angular APIs: built-in control flow (@if/@for/@switch), provideHttpClient, explicit zone change detection, standalone: false on NgModule declarations, RxJS 7 typing

No intended functional or UI changes. This is a framework and tooling upgrade only. Commits are grouped per major version (and per concern within a version) so the PR can be reviewed commit by commit.

What type of PR is it?

Improvement

Todos

  • Reviewer: manual smoke test of notebook / interpreter / job-manager / credential / theme toggle

What is the Jira issue?

How should this be tested?

  • cd zeppelin-web-angular && npm ci
  • Production build: npm run build:angular (passes)
  • Lint: npm run lint (passes)
  • Dev server: npm start, then click through the main screens to confirm they still work: open and run notebook paragraphs (all result types), the interpreter page, job manager, credentials, header dropdowns, and the dark/light theme toggle.

Screenshots (if appropriate)

N/A (no intended UI changes).

Questions:

  • Does the license files need to update? No
  • Is there breaking changes for older versions? No
  • Does this needs documentation? No

tbonelee added 30 commits May 31, 2026 21:43
Bumps zeppelin-web-angular from Angular 13.4 to 14.3 in a single
lock-step:

- @angular/* 13.4 -> 14.3
- @angular/cdk 13.3 -> 14.2
- @angular/cli 13.3 -> 14.2
- @angular-eslint/* 13.5 -> 14.4
- @angular-architects/module-federation 13.0 -> 14.3
- @angular-builders/custom-webpack 13.1 -> 14.1
- ngx-build-plus 13 -> 14
- ng-packagr 13.3 -> 14.2
- ng-zorro-antd 13.4 -> 14.3
- typescript 4.6 -> 4.7 (Angular 14 minimum)
- rxjs 6.5 -> 6.6 (module-federation 14 peer)
- zone.js 0.11.4 -> 0.11.5

The original spec aimed to leave monaco-editor untouched, but
Angular 14's stricter build pipeline forced compatible bumps for:
- monaco-editor 0.30.1 -> 0.31.1
- monaco-editor-webpack-plugin 6.0.0 -> 7.0.1
- @babel/runtime via overrides to ^7.27 (transitive dep missing
  regeneratorValues helper required by Angular 14 babel pipeline)

angular.json adjustments are mechanical (whitespace, lintFilePatterns
formatting) applied by Angular's update schematics, plus prettier
normalization.

Angular 14's new features (Strict typed forms, Standalone components)
are NOT adopted in this change; only version-level upgrade.
…ms opt-out

Auto-applied by 'ng update @angular/core --from=13 --to=14'.

In Angular 14, FormControl/FormGroup/FormBuilder/FormArray accept a
type parameter for the new strict typed forms. To preserve the
existing untyped behavior without changes to form logic, the
migration switches references to UntypedFormControl, UntypedFormGroup,
UntypedFormBuilder, and UntypedFormArray.

This is a backwards-compatible rename only. No runtime behavior
change.

Adopting strict typed forms is intentionally deferred to a separate
future change.
…naco CSS

Angular 14's @angular-devkit/build-angular tightened its CSS pipeline
so that CSS files imported via JS from inside node_modules (as
monaco-editor does) no longer match the default css rule, breaking
the build with "no loaders are configured to process this file".

Adding a plain style-loader+css-loader rule on top of the object-form
config also failed because Angular's existing postcss+mini-css-extract
rule still matched and chained on the JS output, producing a postcss
SyntaxError.

Switch to the function form supported by @angular-builders/custom-webpack:

  module.exports = (config, options, targetOptions) => { ... };

This lets us walk Angular's existing module.rules, exclude
monaco-editor from any rule that handles .css, then append our own
dedicated style-loader+css-loader rule scoped to /monaco-editor/.
Existing Module Federation and MonacoWebpackPlugin setup is preserved
unchanged.
prettier --check picked up two existing classic-visualization HTML
templates that drift from the project's prettier config. Applying
prettier --write is a no-op for runtime behavior; this just makes
lint green on the upgrade branch.
Bumps zeppelin-web-angular from Angular 14.3 to 15.2:

- @angular/* 14.3 -> 15.2
- @angular/cdk 14.2 -> 15.2
- @angular/cli 14.2 -> 15.2
- @angular-eslint/* 14.4 -> 15.2
- @angular-architects/module-federation 14.3 -> 15.0
- @angular-builders/custom-webpack 14.1 -> 15.0
- @angular-devkit/build-angular 14.2 -> 15.2
- ngx-build-plus 14 -> 15
- ng-packagr 14.2 -> 15.2
- ng-zorro-antd 14.3 -> 15.1
- typescript 4.7 -> 4.8 (Angular 15 minimum)

monaco-editor (0.31.1) and monaco-editor-webpack-plugin (7.0.1)
stay unchanged; Angular 15's build pipeline accepts them without
the webpack rule changes that 13 -> 14 required.

Angular 15 schematic migrations applied:
- Router: relativeLinkResolution removed (no code changes needed)
- RouterLinkWithHref unified into RouterLink (no code changes needed)
- ng-zorro v15 migration completed (no code changes needed)

No source code changes from migration; this is a pure version bump
on top of the 14 work in the same branch.
Required to fix a runtime "Class constructor Message cannot be
invoked without 'new'" error in MessageService.

@zeppelin/sdk and @zeppelin/visualization sub-projects build to
FESM2020 (ES2015+ classes). The main app's tsconfig kept target=es5,
so its ES5-transpiled MessageService — which extends Message from
@zeppelin/sdk — emitted a downleveled `Message.call(this)` against
a real ES2015 class. That fails at runtime: ES2015 classes refuse to
be invoked without `new`.

The matching schematic ('Update TypeScript compilation target to
ES2020') was queued by `ng update @angular/cli --from=13 --to=14`
but bailed out with "Path /tsconfig.json does not exist" because
this workspace puts tsconfigs under src/ and projects/ instead of
the root. Applying the change manually.

es2020 is the minimum recommended target for Angular 14+ and is
already supported by the project's browserslist.
Earlier ng update runs handled @angular/core, @angular/cli, @angular/cdk,
and ng-zorro-antd migrations directly, but the @angular-eslint
package has its own migration schematics that were never explicitly
invoked.

Running 'ng update @angular-eslint/schematics --migrate-only' for
both 13->14 and 14->15 produced:

- @typescript-eslint/eslint-plugin ^5.36.2 -> ^5.43.0
- @typescript-eslint/parser ^5.36.2 -> ^5.43.0
- eslint ^8.23.0 -> ^8.28.0
- angular.json: cli.analytics=false (suppresses CLI's telemetry
  prompt during the first ng command)
- angular.json: schematics block with setParserOptionsProject=true
  for @angular-eslint/schematics:{application,library} so future
  generated apps/libraries get type-aware lint by default

Build and lint pass with the same 0 errors / 249 warnings baseline.
Bumps zeppelin-web-angular from Angular 15.2 to 16.2:

- @angular/* 15.2 -> 16.2
- @angular/cdk 15.2 -> 16.2
- @angular/cli 15.2 -> 16.2
- @angular-eslint/* 15.2 -> 16.3
- @angular-architects/module-federation 15.0 -> 16.0
- @angular-builders/custom-webpack 15.0 -> 16.0
- @angular-devkit/build-angular 15.2 -> 16.2
- ngx-build-plus 15 -> 16
- ng-packagr 15.2 -> 16.2
- ng-zorro-antd 15.1 -> 16.2
- typescript 4.8 -> 5.1 (Angular 16 requires >=4.9.3 <5.2)
- zone.js 0.11.5 -> 0.13.3 (Angular 16 default)
- @typescript-eslint/* 5.43 -> 5.62

Adds @ctrl/tinycolor ^3.6.0 as a direct dependency: ng-zorro-antd 16's
core color module imports it but does not declare it as a dependency,
so it must be provided explicitly to resolve at build time.
Auto-applied by 'ng update @angular/core --from=15 --to=16'.

Angular 15.2 deprecated the class-based guard interfaces (CanActivate,
Resolve, etc.). The migration removes 'implements CanActivate' and the
corresponding import from LoginGuard and WorkspaceGuard. The canActivate
methods themselves are unchanged and still wired through the route
config, so runtime guard behavior is identical.
ng-zorro-antd 16 removed the deprecated `nzComponentParams` option from
`NzModalService.create` / `ModalOptions`. Component data is now passed
via `nzData` and delivered to the opened component through the
`NZ_MODAL_DATA` injection token instead of being set as @input
properties.

Migrated all four modal call sites and the three opened components:
- action-bar.component.ts        -> NoteCreateComponent (cloneNote)
- note-action.service.ts         -> NoteRenameComponent (id, newName)
- note-action.service.ts         -> FolderRenameComponent (folderId, newFolderPath)
- note-action.service.ts         -> NoteCreateComponent (path)

Each component now injects NZ_MODAL_DATA in its constructor and assigns
the values to the same fields it used before, so all internal logic and
templates are untouched. The former @input decorators (no longer fed by
ng-zorro) are dropped; the properties remain.

Behavior of the Clone Note, Rename Note, and Rename Folder dialogs is
preserved.
@angular-eslint 16 removed the legacy TSLint-compatibility presets
'@angular-eslint/ng-cli-compat' and 'ng-cli-compat--formatting-add-on'
that this project's .eslintrc.json extended (a leftover from the original
TSLint -> ESLint conversion). Without them ng lint failed to load.

Changes:
- Replace the two ng-cli-compat extends with the standard
  'plugin:@angular-eslint/recommended'.
- Declare the plugins whose rules the config references but that
  ng-cli-compat used to load implicitly: @typescript-eslint, import,
  jsdoc, prefer-arrow.
- Disable @angular-eslint/no-empty-lifecycle-method (newly enforced as
  error by 'recommended'). 17 pre-existing empty lifecycle methods
  tripped it; turning it off keeps the lint baseline at 0 errors rather
  than churning 17 unrelated components in a dependency-upgrade PR.

ng lint passes with the same 0 errors / 248 warnings baseline as before.
Bumps zeppelin-web-angular from Angular 16.2 to 17.3:

- @angular/* 16.2 -> 17.3
- @angular/cdk 16.2 -> 17.3
- @angular/cli 16.2 -> 17.3
- @angular-eslint/* 16.3 -> 17.5
- @angular-architects/module-federation 16.0 -> 17.0
- @angular-builders/custom-webpack 16.0 -> 17.0
- @angular-devkit/build-angular 16.2 -> 17.3
- ngx-build-plus 16 -> 17
- ng-packagr 16.2 -> 17.3
- ng-zorro-antd 16.2 -> 17.4
- typescript 5.1 -> 5.4 (Angular 17 requires >=5.2 <5.5)
- zone.js 0.13 -> 0.14 (Angular 17 default)
- @typescript-eslint/* 5.x -> 7.x (required by @angular-eslint 17)
- eslint 8.39 -> 8.57

The project keeps the webpack-based browser builder via
@angular-builders/custom-webpack; Angular 17's new esbuild/vite
application builder is not adopted (it would not support the existing
custom webpack config / Module Federation setup).
Auto-applied by 'ng update @angular/core --from=16 --to=17' (the
migration left a dangling comma due to a partial edit, fixed here).

Under Ivy, CompilerOptions.useJit (and missingTranslation) have no
effect and were removed from the type in Angular 17. The runtime
compiler providers still create the JIT compiler via JitCompilerFactory;
dropping the no-op useJit flag changes nothing at runtime.
After the ng-zorro upgrade, clicking the header user menu items
(Interpreter, Notebook Repos, Credential, Configuration) only navigated
when the click landed on the link text. Clicking the row padding closed
the dropdown without navigating.

Root cause: ng-zorro renders nz-menu-item content inside
<span class="ant-menu-title-content"> even inside a dropdown, but the
upstream rule that stretches the inner <a> across the whole item targets
`.ant-dropdown-menu-title-content > a::after`. The class mismatch
(ant-menu-* vs ant-dropdown-menu-*) left the <a> covering only its text,
so clicking the padding hit the <li>, which closes the dropdown via
descendantMenuItemClick without firing routerLink. ng-zorro 13 had no
title-content span wrapper, so the link filled the row; the wrapper
introduced in later versions is what regressed it.

Fix, scoped to this one dropdown so nothing else is affected:
- Tag the dropdown overlay with nzOverlayClassName="zeppelin-user-menu".
- In header.component.less, an absolutely-filled <a>::after reclaims the
  whole row as the link hit area. The dropdown renders in a body-level
  CDK overlay, so the rule uses ::ng-deep to escape view encapsulation,
  but the .zeppelin-user-menu scope keeps it from leaking to any other
  dropdown or component. The rule is colocated with the component that
  owns the dropdown instead of living in global styles.

Regression test: e2e/tests/workspace/user-menu-navigation.spec.ts opens
the dropdown and clicks the empty row padding (not the text) for each
item, asserting navigation. A future ng-zorro/Angular upgrade that
reintroduces the dead zone fails here.

Verified with a headless elementFromPoint probe: before the fix, clicks
on the row padding resolve to the <li>; after, every point resolves to
the <a>.
Bumps zeppelin-web-angular from Angular 17.3 to 18.2:

- @angular/* 17.3 -> 18.2
- @angular/cdk 17.3 -> 18.2
- @angular/cli 17.3 -> 18.2
- @angular-eslint/* 17.5 -> 18.4
- @angular-architects/module-federation 17.0 -> 18.0
- @angular-builders/custom-webpack 17.0 -> 18.0
- @angular-devkit/build-angular 17.3 -> 18.2
- ngx-build-plus 17 -> 18
- ng-packagr 17.3 -> 18.2
- ng-zorro-antd 17.4 -> 18.2
- typescript 5.4 -> 5.5 (Angular 18 requires >=5.4 <5.6)
- zone.js stays ~0.14.10

@typescript-eslint stays on ^7.x: @angular-eslint 18 accepts
@typescript-eslint/utils ^7.11.0 || ^8.0.0, and its migration schematic
pins 7.x, which is compatible with TypeScript 5.5. The bump to
@typescript-eslint 8 is deferred to the Angular 19 step that requires it.
…ngular 18

Auto-applied by 'ng update @angular/core --from=17 --to=18'.

Angular 18 deprecates the module-based HTTP setup. The migration swaps
HttpClientModule for provideHttpClient(withInterceptorsFromDi()) in both
AppModule and WorkspaceModule. withInterceptorsFromDi() preserves the
existing class-based HTTP_INTERCEPTORS registrations (the app's request/
message interceptors), so HTTP behavior is unchanged.

Both modules already imported HttpClientModule before, so the provider
placement mirrors the previous structure exactly.
Angular 18's build pipeline (newer less) broke the dark/light theme
build with "ReferenceError: colorPalette is not defined". The theme
files computed the antd color ramp via inline JavaScript:

  @primary-1: color(~`colorPalette('@{primary-color}', 1) `);

ng-zorro registers colorPalette as a Less function via
`@plugin "./colorPalette"` (node_modules/ng-zorro-antd/src/style/color/
colorPalette.less), not as a JavaScript global, so the inline-JS form
can no longer resolve it. ng-zorro's own colors.less calls it as a Less
function: `color(colorPalette('@{blue-6}', 1))`.

Switch all 38 usages (primary ramp, link colors, alert colors) in
theme-dark.less and theme-light.less from the inline-JS backtick form to
the plain Less-function form. The colorPalette function is registered by
the colors.less import that theme-mixin.less already pulls in, so it is
in scope. Computed colors are unchanged.
Bump @angular/* 18.2.14 -> 19.2.24, @angular/cdk -> 19.2.19, @angular/cli
and @angular-devkit/build-angular -> 19.2.26, ng-zorro-antd 18.2.1 ->
19.3.1, ng-packagr -> 19.2.2, @angular-architects/module-federation ->
19.0.3, @angular-builders/custom-webpack -> 19.0.1, ngx-build-plus ->
19.0.0, @angular-eslint/* 18.4.3 -> 19.8.1, @typescript-eslint/* 7.x ->
8.x, typescript 5.5.4 -> 5.7.3, zone.js 0.14.10 -> 0.15.1.

Versions hand-picked to satisfy peer ranges (Angular 19 compiler-cli
allows TypeScript >=5.5 <5.9; ng-zorro 19 requires Angular ^19). ESLint
stays on 8.57 -- the angular-eslint 19 migration's bump to ESLint 9 /
flat config is deferred to a dedicated step. RxJS remains on 6.

Schematic migrations and the resulting code changes follow in subsequent
commits.
…for Angular 19

Auto-applied by 'ng update @angular/core --from=18 --to=19'.

Angular 19 flips the Component/Directive/Pipe `standalone` default from
false to true. This app is NgModule-based -- every declarable is listed
in an NgModule -- so the migration adds explicit `standalone: false` to
each of them to preserve module compilation. No behavior change.

The dynamic-forms and note-import components receive the same standalone:
false addition in their respective fix commits (the ng-zorro checkbox
migration and the lint adaptation), where they are also touched.
Auto-applied by 'ng update ng-zorro-antd --from=18 --to=19'.

ng-zorro 19 ships a redesigned nz-input-number and moves the previous
implementation to ng-zorro-antd/input-number-legacy. The migration swaps
NzInputNumberModule for NzInputNumberLegacyModule in the runtime dynamic
module, which keeps the same nz-input-number selector and rendered
control. (No template in the app currently uses nz-input-number, so this
only preserves the existing import behavior.)
…ng-zorro 19

ng-zorro 19 splits nz-checkbox-group's old dual-purpose model. Before, a
single [(ngModel)] array of {label, value, checked} objects both rendered
the options and carried the checked state. Now the choices are passed via
[nzOptions] ({label, value} only) and the ngModel holds the array of
selected values directly -- NzCheckboxOption no longer has a `checked`
field (NzCheckBoxOptionInterface is now a deprecated alias of it).

dynamic-forms.component built the old shape and read e.checked in the
change handler, which no longer compiles under v19 (TS2339 'checked' does
not exist on NzCheckboxOption; TS2322 (string|number)[] not assignable to
string|string[]). Migrate it while preserving behavior:

- checkboxGroups[name] now holds NzCheckboxOption[] ({label, value}) for
  [nzOptions].
- new checkboxValues[name] holds the selected values, seeded from
  paramDefs so the initial checked state is unchanged.
- template binds [nzOptions]=options and one-way [ngModel]=selected
  values, with (ngModelChange) writing the selected values back to
  paramDefs (one-way + write-back avoids the string|string[] vs
  (string|number)[] two-way type mismatch).

This component also carries the Angular 19 `standalone: false` addition
from the core schematic.
…script-eslint 8

angular-eslint 19 adds @angular-eslint/prefer-standalone to the
recommended set, which errors on the `standalone: false` the Angular 19
migration just added to every NgModule declarable. This app is
intentionally NgModule-based, so the rule is turned off.

typescript-eslint 8 removes the `ban-types` rule (split into
no-wrapper-object-types / no-unsafe-function-type / no-empty-object-type)
and tightens no-unused-vars (caughtErrors now defaults to 'all', and vars
used only in a type position are reported):

- .eslintrc.json: replace the ban-types config with no-wrapper-object-
  types + no-unsafe-function-type, preserving the ban on Object/String/
  Number/Boolean/Function while still allowing {} (which ban-types had
  recommended as the alternative).
- shortcut.service.ts: retarget the inline disable at the new rule name
  (no-unsafe-function-type) for the deliberate `Function` type.
- copy-text-to-clipboard / note-import / spell-result: use optional catch
  binding (`catch {`) for the three intentionally-ignored errors.
- notebook-paragraph-keyboard-event-handler: disable no-unused-vars on
  the const array referenced only via `typeof` to derive a type.

note-import.component also carries its `standalone: false` addition from
the core schematic. ng build, ng build --configuration production and ng
lint are all green (0 errors).
…rver

`ng serve` failed with "Schema validation failed ... must have required
property 'buildTarget'". Angular renamed the dev-server / extract-i18n
`browserTarget` option to `buildTarget` in v17 and removed the old name
in v19. `ng build` never touches these targets, so the gap only surfaced
at serve time (not in the build/prod-build/lint gate run during the
upgrade).

The 18->19 `ng update @angular/cli` migration didn't catch it: the rename
shipped as a v16->17 migration (not re-run on an 18->19 step) and it skips
custom builder targets anyway -- serve uses
@angular-builders/custom-webpack:dev-server. v18 still accepted
browserTarget as a deprecated alias, so it slipped through until v19
dropped it.

Rename all four occurrences (serve options + production/development
configurations + extract-i18n) to buildTarget. `ng serve` now boots and
compiles successfully; `ng build` and build:angular are unaffected (they
use the build target, untouched).
Angular 20 drops Node 18 support (requires ^20.19 || ^22.12 || >=24), so
the frontend build must move off the pinned Node 18.20.8 before the 19->20
upgrade. Pin Node 22.21.1 (current active LTS) with its bundled npm 10.9.4
in pom.xml's frontend-maven-plugin, and widen package.json engines to
>=22.12.0.

Done as an isolated step ahead of the Angular 20 dep bump: the existing
Angular 19 app reinstalls from the unchanged lockfile (npm ci) and builds
(dev + production), lints (0 errors) and `ng serve` boots cleanly under
Node 22 / npm 10. No lockfile churn.

Local dev builds now run on Node 22 (e.g. via mise); the vendored ./node
binary is gitignored and is refreshed to 22.21.1 on the next Maven
frontend build.
Bump @angular/* 19.2.24 -> 20.3.23, @angular/cdk -> 20.2.14, @angular/cli
and @angular-devkit/build-angular -> 20.3.26, ng-zorro-antd 19.3.1 ->
20.4.4, ng-packagr -> 20.3.2, @angular-architects/module-federation,
@angular-builders/custom-webpack and ngx-build-plus -> 20.0.0,
@angular-eslint/* 19.8.1 -> 20.7.0, @typescript-eslint/* -> ^8.33.1,
typescript 5.7.3 -> 5.9.3.

rxjs ~6.6.7 -> ~7.8.2 is forced: ng-zorro 20's compiled code imports
operators (debounceTime, finalize, filter) from the 'rxjs' root, which
only resolves under RxJS 7. Angular 20 supports both 6 and 7; ng-zorro 20
needs 7.

ESLint stays on ^8.57.0: the @angular-eslint 20 migration bumps it to 9,
but ESLint 9 defaults to flat config and the lint-staged pre-commit hook
runs eslint directly (not via the angular-eslint builder), so under 9 it
can't resolve .eslintrc.json. angular-eslint 20 still supports eslint 8 +
eslintrc; the ESLint 9 + flat-config migration is deferred to its own
step. zone.js stays ~0.15.1; Node was bumped to 22 separately.

Schematic migrations and code adaptations follow in subsequent commits.
Auto-applied by 'ng update @angular/core --from=19 --to=20'.

Angular 20 deprecates re-exporting DOCUMENT from @angular/common and
exports it from @angular/core instead. The migration moves the import in
the two services that inject it (shortcut.service, code-editor.service).
Same token, no behavior change.
Covers ng-zorro 19 -> 20 breaking changes (schematic + manual):

- nz-tabset -> nz-tabs: the `ng update ng-zorro-antd` schematic renamed
  the component (note-import template + the NzTabsModule imports).
- NzMessageModule / NzNotificationModule removed: ng-zorro 20 drops these
  modules; NzMessageService / NzNotificationService are providedIn 'root'
  and create their (standalone) containers on demand, so the modules are
  just deleted from the five modules that imported them. Service usage is
  unchanged.
- nz-button-group removed (Ant Design 5 dropped Button.Group): replace
  with nz-space-compact, which propagates nzSize to child buttons the
  same way. Updates action-bar and result templates, the colocated
  button-group LESS selectors, and adds NzSpaceModule to the two
  declaring modules.
…create

Angular CDK 20 removed the long-deprecated PortalInjector, and
ComponentPortal dropped its componentFactoryResolver constructor argument
(its 4th parameter is now projectableNodes).

Build the portal injector with Injector.create({ providers, parent })
instead -- same semantics: the VISUALIZATION token resolves to the
visualization instance and falls back to the view-container injector.
Drop the now-unused componentFactoryResolver constructor parameter (all
six call sites already omit it) and the matching ComponentPortal
argument. The WeakMap + eslint-disable the old PortalInjector required
are no longer needed.
RxJS 7 tightens types that RxJS 6 left loose:

- Subject.next() now requires an argument. Type the nine signal subjects
  used purely for teardown as Subject<void> so next() stays valid; the
  SDK's close$ (a WebSocket closeObserver of CloseEvent) gets a real
  normal-closure CloseEvent in close() instead of an undefined push (the
  old no-arg next() would have made the event.code subscriber throw).
- interpreter's search$.next() on destroy dropped: it only needs
  complete(), and Subject<string>.next() can no longer be called bare.
- KeyBinder's destroySubject is only read via takeUntil, so type it
  Observable<unknown> (Subject<void> is assignable to it; Subject<void>
  is not assignable to the old Subject<unknown>).
- toPromise() now resolves T | undefined and is deprecated; switch the
  two typed call sites (configuration, classic-visualization) to
  lastValueFrom, which keeps the non-undefined result type.

No observable behavior change.
…ngular-eslint 20)

angular-eslint 20 adds @angular-eslint/prefer-inject to the recommended
set, which errors on every constructor-parameter injection (226 across
the app). This codebase uses constructor DI throughout; migrating to the
inject() function is a large, separate refactor, so the rule is turned
off -- same call made for prefer-standalone at Angular 19.

ng lint runs clean (0 errors) under angular-eslint 20, which still
supports the existing .eslintrc.json on ESLint 8.
tbonelee added 8 commits May 31, 2026 21:44
angular-eslint 20's `ng lint` bridges ESLint 9 to the legacy .eslintrc.json,
but the lint-staged pre-commit hook runs `eslint` directly, where ESLint 9
defaults to flat config and can't load eslintrc. Rather than pin the hook to
ESLINT_USE_FLAT_CONFIG=false, move to flat config so every path (ng lint, raw
eslint, pre-commit) shares one modern config and we are off EOL ESLint 8.

- eslint ^8.57.0 -> ^9.28.0; swap the individual @angular-eslint and
  @typescript-eslint plugin/parser packages for the `angular-eslint` and
  `typescript-eslint` meta-packages (which ship the flat presets). Keep
  @angular-eslint/builder and @angular-eslint/schematics (angular.json refs).
- Add eslint.config.js as a 1:1 port of the three .eslintrc.json files: the
  root TS rules + inline-template processor + prettier, the `zeppelin`
  selector prefixes, and the `lib` prefix override for the two library
  projects. dist/, target/, .angular/ and the React sub-app are ignored.
- Drop the four @typescript-eslint formatting rules (member-delimiter-style,
  semi, type-annotation-spacing, quotes) that v8 removed; they were already
  `off` and Prettier owns formatting. Flat config validates configured rules,
  so dead entries can't remain.
- Comment, in the config, why every rule the 13->20 upgrade turned off is off
  (no-empty-lifecycle-method @16, prefer-standalone @19, prefer-inject @20)
  and why ban-types became no-wrapper-object-types + no-unsafe-function-type.

ng lint, raw `eslint` and `npm run lint` all run clean (0 errors). The
member-ordering / prefer-arrow warning counts shift slightly (250 -> 265)
from ESLint 9 / flat-config processing -- same two rules, no new rules,
no errors.
Bump @angular/* 20.3.23 -> 21.2.15, @angular/cdk -> 21.2.13, @angular/cli
and @angular-devkit/build-angular -> 21.2.13, ng-zorro-antd 20.4.4 ->
21.3.0, ng-packagr -> 21.2.3, @angular-architects/module-federation ->
21.2.2, @angular-builders/custom-webpack -> 21.0.3, angular-eslint /
@angular-eslint/* 20.7.0 -> 21.4.0.

Node 22.21.1 and TypeScript 5.9.3 already satisfy Angular 21's
requirements (node ^20.19 || ^22.12 || >=24; typescript >=5.9 <6.1, and
ng-packagr caps it at <6.0). rxjs 7.8, zone.js 0.15, eslint 9 and
typescript-eslint 8 are all still in range, so they stay put.

ngx-build-plus stays at ^20.0.0: it has no 21 release, but it is unused
(no angular.json builder references it) and its peers are satisfied by
Angular 21 (>=20). Schematic migrations and code adaptations follow.
…exports

Angular 21 (and ng-zorro 21, @ant-design/icons-angular) drop their
per-secondary-entry-point directories and expose entry points only
through the package `exports` map. The classic `moduleResolution: node`
can't read exports maps, so imports like @angular/common/http,
@angular/platform-browser/animations and @ant-design/icons-angular/icons
stopped resolving during type-checking (the webpack bundle resolved them,
but tsc did not).

Switch tsconfig.base.json to `moduleResolution: bundler`, the modern
default for a bundled Angular app, which reads exports maps. `module`
stays es2020 (bundler allows es2015+).

The `ng update @angular/cli` migration also dropped the redundant
`lib: ["dom", "es2018"]` override from the two library tsconfigs (they
inherit it from the base), included here.
Auto-applied by 'ng update @angular/core --from=20 --to=21', which runs
the built-in control-flow migration by default in v21: *ngIf / *ngFor /
*ngSwitch become @if / @for / @switch across 37 templates. Behavior is
unchanged; the new block syntax is Angular's recommended, more performant
form and needs no CommonModule structural directives.

One hand-fix on top of the migration output: for the two paragraph
templates it emitted `track trackByIndexFn(i, result)`, but
trackByIndexFn takes a single `index` parameter, so the explicit two-arg
call failed type-checking (TS2554). Dropped the extra argument to
`track trackByIndexFn(i)` (the function only ever used the index).
…ular 21

Auto-applied by the Angular 21 migration. bootstrapModule now takes an
options bag with applicationProviders, and the migration makes the
(previously implicit) zone-based change detection explicit via
provideZoneChangeDetection(). Behavior is unchanged -- the app still runs
with Zone.js change detection.
ng-zorro 21 restructured several secondary entry points (its own
migration schematic crashed under Node 22 with "exports is not defined in
ES module scope", so these are applied by hand):

- ng-zorro-antd/core/no-animation is gone; NzNoAnimationModule now lives
  in ng-zorro-antd/core/animation (runtime-dynamic-module + notebook
  module).
- ng-zorro-antd/input-number-legacy is gone (the v19 migration window
  closed); switch the aggregator back to NzInputNumberModule from
  ng-zorro-antd/input-number. nz-input-number is not used in any template,
  so this only keeps the runtime module list valid.
- ng-zorro-antd/back-top (NzBackTopModule) was removed; drop it from the
  aggregator -- nz-back-top is unused.
- job-manager: NzHighlightModule was removed in favour of the standalone
  NzHighlightPipe; import the pipe directly (the nzHighlight pipe is still
  used in the job template).
…lar/animations

ng-zorro 21 removed its @angular/animations-based motion. The collapseMotion
trigger no longer exists, and overlays (dropdown/select/tooltip/popover) now
animate through Angular's native CSS animate.enter/animate.leave instead of the
JS animation engine, so neither the @angular/animations package nor a
BrowserAnimations provider is required anymore.

- Migrate the credential and interpreter collapse panels from the removed
  collapseMotion trigger to NzAnimationCollapseDirective
  (ng-zorro-antd/core/animation).
- Wrap each panel body in a flow-root BFC so the directive's
  sum-of-children-offsetHeight open measurement matches the panel's final auto
  height; without it the animation stops short and the layout jumps.
- Remove BrowserAnimationsModule from AppModule. Native animate.enter/leave
  needs no provider: ng-zorro enables animations unless ANIMATION_MODULE_TYPE
  resolves to 'NoopAnimations'.
- Drop the now-unused @angular/animations dependency.
…1 overlays

dark-theme-overrides.css reset both `transition: none !important` and
`animation: none !important` on a broad antd selector list (.ant-dropdown,
.ant-select, .ant-tooltip, .ant-popover, .ant-menu, ...) plus the header
.search, to make dark/light theme switching instant (no color-transition
flicker).

Under ng-zorro <= 20 the overlay open/close motion was driven by
@angular/animations (JS), so the CSS `animation: none` was a harmless no-op.
ng-zorro 21 moved overlays to native CSS animations: animate.enter applies
ant-slide-up-enter / ant-zoom-big-* classes that run @Keyframes. The
`animation: none !important` reset therefore silently killed every
dropdown/select/tooltip/popover open and close animation, in both themes and
regardless of the animation provider.

Drop the five `animation: none !important` declarations and keep
`transition: none !important`, which is what actually prevents the theme-switch
flicker. Overlay animations are restored, and .ant-spin keyframes animate again.
@tbonelee tbonelee force-pushed the upgrade/angular-21 branch from 6b91106 to ae392ef Compare May 31, 2026 12:44
The Node 18 -> 22 bump updated pom.xml (<node.version>) and package.json
`engines`, but missed `.nvmrc`. `.nvmrc` is read by the frontend workflow's
npm-audit job (actions/setup-node `node-version-file`) and by local `nvm use`,
so it has to match. With the stale 18.20.8, running `nvm use` and then
`npm install` fails the `engines: >=22.12.0` check locally.

Align it with pom.xml's v22.21.1 and engines >=22.12.0.
@tbonelee tbonelee merged commit 1893e11 into apache:master Jun 2, 2026
19 checks passed
@tbonelee tbonelee deleted the upgrade/angular-21 branch June 2, 2026 16:01
@tbonelee
Copy link
Copy Markdown
Contributor Author

tbonelee commented Jun 2, 2026

Merged into master

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants