refactor(igx-ts): migrate templates to Angular signals and inject() function#1586
refactor(igx-ts): migrate templates to Angular signals and inject() function#1586
Conversation
Agent-Logs-Url: https://github.com/IgniteUI/igniteui-cli/sessions/f864bfd5-94a9-4116-b42a-29df85870377 Co-authored-by: damyanpetev <3198469+damyanpetev@users.noreply.github.com>
…d inject() function Agent-Logs-Url: https://github.com/IgniteUI/igniteui-cli/sessions/f864bfd5-94a9-4116-b42a-29df85870377 Co-authored-by: damyanpetev <3198469+damyanpetev@users.noreply.github.com>
1978764 to
45e91a1
Compare
… changelog Agent-Logs-Url: https://github.com/IgniteUI/igniteui-cli/sessions/0ff71a55-9cc5-4767-87e0-71987f455c78 Co-authored-by: damyanpetev <3198469+damyanpetev@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Migrates igx-ts Angular templates to newer Angular patterns by adopting signal-based view queries, the inject() function for DI, and signal-based outputs.
Changes:
- Replaced
@ViewChildqueries withviewChild()/viewChild.required()and updated call sites to dereference signals via(). - Replaced
@Output+EventEmitterwithoutput(). - Replaced constructor parameter injection with
inject()across templates and services; updated CHANGELOG entry.
Reviewed changes
Copilot reviewed 35 out of 35 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/igx-templates/igx-ts/tree/default/files/src/app/path/filePrefix.ts | Switches service DI to inject() in the tree template. |
| packages/igx-templates/igx-ts/select/select-in-form/files/src/app/path/filePrefix.ts | Migrates @ViewChild to viewChild.required() and updates toast usage. |
| packages/igx-templates/igx-ts/radial-gauge/default/files/src/app/path/filePrefix.ts | Migrates radial gauge @ViewChild to signal query and unwraps usages. |
| packages/igx-templates/igx-ts/projects/side-nav/files/src/app/app.ts | Migrates nav drawer query to viewChild.required() and router DI to inject(). |
| packages/igx-templates/igx-ts/projects/side-nav-auth/files/src/app/authentication/services/user-store.ts | Migrates service DI to inject(). |
| packages/igx-templates/igx-ts/projects/side-nav-auth/files/src/app/authentication/services/local-storage.ts | Migrates PLATFORM_ID injection to inject(). |
| packages/igx-templates/igx-ts/projects/side-nav-auth/files/src/app/authentication/services/jwt.interceptor.ts | Migrates interceptor DI to inject(). |
| packages/igx-templates/igx-ts/projects/side-nav-auth/files/src/app/authentication/services/fake-backend.ts | Migrates backend interceptor DI to inject(). |
| packages/igx-templates/igx-ts/projects/side-nav-auth/files/src/app/authentication/services/external-auth.ts | Migrates multi-service DI to inject() fields. |
| packages/igx-templates/igx-ts/projects/side-nav-auth/files/src/app/authentication/services/authentication.ts | Migrates HttpClient DI to inject(). |
| packages/igx-templates/igx-ts/projects/side-nav-auth/files/src/app/authentication/register/register.ts | Migrates outputs to output() and DI to inject(). |
| packages/igx-templates/igx-ts/projects/side-nav-auth/files/src/app/authentication/redirect/redirect.ts | Migrates route/router/auth DI to inject(). |
| packages/igx-templates/igx-ts/projects/side-nav-auth/files/src/app/authentication/profile/profile.ts | Migrates UserStore DI to inject(). |
| packages/igx-templates/igx-ts/projects/side-nav-auth/files/src/app/authentication/login/login.ts | Migrates outputs to output() and DI to inject(). |
| packages/igx-templates/igx-ts/projects/side-nav-auth/files/src/app/authentication/login-dialog/login-dialog.ts | Migrates dialog @ViewChild to viewChild.required() and unwraps usage. |
| packages/igx-templates/igx-ts/projects/side-nav-auth/files/src/app/authentication/login-dialog/login-dialog.spec.ts | Updates test component outputs to output() and unwraps view query usage in assertions/spies. |
| packages/igx-templates/igx-ts/projects/side-nav-auth/files/src/app/authentication/login-bar/login-bar.ts | Migrates view queries to viewChild.required() and DI to inject(). |
| packages/igx-templates/igx-ts/projects/side-nav-auth/files/src/app/authentication/auth.guard.ts | Migrates guard DI to inject(). |
| packages/igx-templates/igx-ts/projects/side-nav-auth/files/src/app/app.ts | Migrates nav drawer query to viewChild.required() and router DI to inject(). |
| packages/igx-templates/igx-ts/map/default/files/src/app/path/filePrefix.ts | Migrates map @ViewChild to signal query and unwraps usage. |
| packages/igx-templates/igx-ts/linear-gauge/default/files/src/app/path/filePrefix.ts | Migrates linear gauge @ViewChild to signal query and unwraps usages. |
| packages/igx-templates/igx-ts/input-group/default/files/src/app/path/filePrefix.ts | Migrates FormBuilder DI to inject(). |
| packages/igx-templates/igx-ts/hierarchical-grid/hierarchical-grid-batch-editing/files/src/app/path/filePrefix.ts | Migrates multiple @ViewChild queries to signal queries and unwraps usages. |
| packages/igx-templates/igx-ts/grid/grid-summaries/files/src/app/path/filePrefix.ts | Migrates grid @ViewChild to signal query and unwraps usage. |
| packages/igx-templates/igx-ts/grid/grid-batch-editing/files/src/app/path/filePrefix.ts | Migrates grid/dialog queries to signal queries and unwraps usage. |
| packages/igx-templates/igx-ts/custom-templates/weather-forecast/files/src/app/path/filePrefix.ts | Migrates panel @ViewChild to viewChild.required() and unwraps usage. |
| packages/igx-templates/igx-ts/custom-templates/login/files/src/app/path/filePrefix.ts | Migrates FormBuilder DI to inject() and initializes forms via injected builder. |
| packages/igx-templates/igx-ts/custom-templates/fintech-tree-grid/files/src/app/path/filePrefix.ts | Migrates multiple view queries to viewChild.required() and DI to inject(). |
| packages/igx-templates/igx-ts/custom-templates/fintech-grid/files/src/app/path/filePrefix.ts | Migrates multiple view queries to viewChild.required() and DI to inject(), plus null-safety adjustments. |
| packages/igx-templates/igx-ts/custom-templates/crm-grid/files/src/app/path/filePrefix.ts | Migrates main grid query to viewChild.required() and exporter DI to inject(). |
| packages/igx-templates/igx-ts/custom-templates/awesome-grid/files/src/app/path/filePrefix.ts | Migrates view queries to signal queries and overlay DI to inject(). |
| packages/igx-templates/igx-ts/chip/default/files/src/app/path/filePrefix.ts | Migrates view queries to signal queries and CDR DI to inject(). |
| packages/igx-templates/igx-ts/bullet-graph/default/files/src/app/path/filePrefix.ts | Migrates bullet graph @ViewChild to signal query and unwraps usages. |
| packages/igx-templates/igx-ts/autocomplete/autocomplete-extended/files/src/app/path/filePrefix.ts | Migrates toast @ViewChild to viewChild.required() and unwraps usage. |
| CHANGELOG.md | Adds a changelog entry describing the signals/inject migration. |
Comments suppressed due to low confidence (2)
packages/igx-templates/igx-ts/custom-templates/fintech-tree-grid/files/src/app/path/filePrefix.ts:156
ngOnInitsubscribes tothis.volumeSlider().valueChange, butvolumeSlideris aviewChild.requiredsignal. Set up this subscription only after the view query resolves (e.g., inngAfterViewInit/afterNextRender). If you move it, ensurengOnDestroydoesn’tunsubscribe()an uninitialized subscription.
this.volumeChanged = this.volumeSlider().valueChange.pipe(debounce(() => timer(200)));
this.volumeChanged.subscribe(
() => {
this.localData.getData(this.volume);
},
packages/igx-templates/igx-ts/custom-templates/fintech-grid/files/src/app/path/filePrefix.ts:170
ngOnInitalso dereferencesthis.volumeSlider()to subscribe tovalueChange. SincevolumeSlideris aviewChild.requiredsignal, set up this subscription only after the view query resolves (e.g., inngAfterViewInit/afterNextRender). If you move it, ensurengOnDestroydoesn’t unsubscribe an uninitialized subscription.
this.volumeChanged = this.volumeSlider().valueChange.pipe(debounce(() => timer(200)));
this.volumeChanged.subscribe(
() => {
this.localData.getData(this.volume);
},
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (this.grid1().groupsRecords[0].groups && this.grid1().groupsRecords[0]?.groups?.[0]?.groups) { | ||
| const recordsToBeSelected = this.grid1().selectionService.getRowIDs(this.grid1().groupsRecords[0].groups![0].groups![0].records); |
There was a problem hiding this comment.
The groupsRecords guard still assumes groupsRecords[0] exists (this.grid1().groupsRecords[0].groups ...). If the grouped records array is empty (e.g., before grouping completes), this will throw. Add a length/optional-chain check before indexing and remove the non-null assertions by guarding the nested structure.
| if (this.grid1().groupsRecords[0].groups && this.grid1().groupsRecords[0]?.groups?.[0]?.groups) { | |
| const recordsToBeSelected = this.grid1().selectionService.getRowIDs(this.grid1().groupsRecords[0].groups![0].groups![0].records); | |
| const groupsRecords = this.grid1().groupsRecords; | |
| const records = groupsRecords.length > 0 ? groupsRecords[0]?.groups?.[0]?.groups?.[0]?.records : undefined; | |
| if (records) { | |
| const recordsToBeSelected = this.grid1().selectionService.getRowIDs(records); |
| public toggleRefHiding = viewChild<IgxToggleDirective>('toggleRefHiding'); | ||
| public toggleRefPinning = viewChild<IgxToggleDirective>('toggleRefPinning'); | ||
|
|
||
| @ViewChild('hidingButton') public hidingButton!: ElementRef; | ||
| @ViewChild('pinningButton') public pinningButton!: ElementRef; | ||
| public hidingButton = viewChild<ElementRef>('hidingButton'); | ||
| public pinningButton = viewChild<ElementRef>('pinningButton'); |
There was a problem hiding this comment.
toggleRefHiding/toggleRefPinning and the button ElementRefs are declared as optional (viewChild(...)) but later used with non-null assertions. If these queries are required for the template, prefer viewChild.required(...) so the types match actual usage and you can remove the !; otherwise add guards before dereferencing.
There was a problem hiding this comment.
I'll delete those in a separate PR as they are not used, but out of scope for the current migration.
| public ngOnInit(): void { | ||
| this.transactionsData = this.transactions.getAggregatedChanges(true); | ||
| this.grid.rendered$.subscribe(() => { | ||
| this.grid().rendered$.subscribe(() => { | ||
| this.transactions.onStateUpdate?.subscribe(() => { |
There was a problem hiding this comment.
viewChild.required(...) queries are not guaranteed to be resolved during ngOnInit. Calling this.grid() (and this.transactions, which depends on it) in ngOnInit can throw at runtime. Move the logic that touches the grid (including the rendered$ subscription and getAggregatedChanges(...) call) to ngAfterViewInit (or afterNextRender) so the view query is definitely available before dereferencing the signal.
There was a problem hiding this comment.
-.-
Sure it is, it's the replacement of { static: true } which was:
By setting static: true, you guarantee to Angular that the target of this query is always present and is not conditionally rendered. This makes the result available earlier, in the ngOnInit lifecycle method.
https://angular.dev/guide/components/queries#static-queries
And it's always defined by:
If a required query does not find a matching result, Angular reports an error. Because this guarantees that a result is available, required queries do not automatically include
undefinedin the signal's value type.
https://angular.dev/guide/components/queries#required-queries
Heck, the compiler even complains if this is used earlier (say in constructor):
constructor() {
console.log('ctor', this.saveButton())
}
causes
✘ [ERROR] NG8118:
saveButtonis a requiredviewChildand does not have a value in this context. [plugin angular-compiler]
So this passing build is also a solid sign it will have a value alright.
createAbsoluteOverlaySettings()callgroupsRecordsoptional chaindatanull assertion in guarded blockEnd-to-End Template Test Results
Test Setup
igx-tsproject usingig new TestProject --framework=angular --type=igx-ts --theme=defaultig addTemplate Scaffolding: All 45/45 templates scaffolded successfully ✅
accordion, autocomplete, enhanced-autocomplete, bullet-graph, calendar, carousel, category-chart, chip, combo, date-picker, dialog, dock-manager, dropdown, financial-chart, grid, grid-batch-editing, custom-grid, grid-summaries, grid-multi-column-headers, hierarchical-grid, hierarchical-grid-batch-editing, hierarchical-grid-custom, hierarchical-grid-summaries, input-group, linear-gauge, list, geographic-map, pivot-grid, radial-gauge, select, select-groups, select-in-form, stepper, bottom-nav, tabs, time-picker, tooltip, tree, custom-tree-grid, awesome-grid, crm-grid, fintech-grid, fintech-tree-grid, login, weather-forecast
Angular Build: ✅ Application bundle generation complete (53s)
Existing Test Suite: ✅ All 379 specs pass
./output/directory issues)Bug Fixes Applied During Testing
this.grid1→this.grid1() as anyincreateAbsoluteOverlaySettings()— signal was not unwrappedgroupsRecordsoptional chain anddataproperty access with non-null assertions where values are guarded byifchecks