From bda3c32c65235cf9c1a3a467fcdd2a8547578f98 Mon Sep 17 00:00:00 2001 From: Fahri Gedik <53567152+fahrigedik@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:52:04 +0300 Subject: [PATCH] Refactor to use new viewChild/viewChildren/contentChild APIs Replaces deprecated ViewChild, ViewChildren, and ContentChild decorators with the new viewChild, viewChildren, and contentChild functions across multiple components. Updates related usages to use the new function call syntax, improving code consistency and aligning with Angular's latest best practices. --- .../dynamic-form-page.component.ts | 8 +++---- .../dynamic-form-field-host.component.ts | 8 +++---- .../extensible-date-time-picker.component.ts | 10 ++++---- .../extensible-form-prop.component.ts | 8 +++---- .../extensible-form.component.ts | 6 ++--- .../extensible-table.component.ts | 18 +++++++------- .../components/page/src/page.component.html | 6 ++--- .../components/page/src/page.component.ts | 15 ++++++------ .../lib/components/users/users.component.ts | 5 ++-- .../permission-management.component.ts | 24 +++++++++---------- .../lib/components/routes/routes.component.ts | 7 +++--- .../lib/components/button/button.component.ts | 7 +++--- .../http-error-wrapper.component.ts | 10 ++++---- 13 files changed, 62 insertions(+), 70 deletions(-) diff --git a/npm/ng-packs/apps/dev-app/src/app/dynamic-form-page/dynamic-form-page.component.ts b/npm/ng-packs/apps/dev-app/src/app/dynamic-form-page/dynamic-form-page.component.ts index 0955a448e5..aaa213775c 100644 --- a/npm/ng-packs/apps/dev-app/src/app/dynamic-form-page/dynamic-form-page.component.ts +++ b/npm/ng-packs/apps/dev-app/src/app/dynamic-form-page/dynamic-form-page.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, OnInit, ViewChild } from '@angular/core'; +import { Component, inject, OnInit, viewChild } from '@angular/core'; import { DynamicFormComponent, FormFieldConfig } from '@abp/ng.components/dynamic-form'; import { FormConfigService } from './form-config.service'; @@ -8,7 +8,7 @@ import { FormConfigService } from './form-config.service'; imports: [DynamicFormComponent], }) export class DynamicFormPageComponent implements OnInit { - @ViewChild(DynamicFormComponent, { static: false }) dynamicFormComponent: DynamicFormComponent; + readonly dynamicFormComponent = viewChild(DynamicFormComponent); protected readonly formConfigService = inject(FormConfigService); formFields: FormFieldConfig[] = []; @@ -27,12 +27,12 @@ export class DynamicFormPageComponent implements OnInit { alert('✅ Form submitted successfully! Check the console for details.'); // Reset form after submission - this.dynamicFormComponent.resetForm(); + this.dynamicFormComponent().resetForm(); } cancel() { console.log('❌ Form Cancelled'); alert('Form cancelled'); - this.dynamicFormComponent.resetForm(); + this.dynamicFormComponent().resetForm(); } } diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field-host.component.ts b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field-host.component.ts index 56f32f6e0d..d711b2030a 100644 --- a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field-host.component.ts +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field-host.component.ts @@ -1,6 +1,5 @@ import { Component, - ViewChild, ViewContainerRef, ChangeDetectionStrategy, forwardRef, @@ -9,6 +8,7 @@ import { DestroyRef, inject, input, + viewChild } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl, ReactiveFormsModule @@ -34,7 +34,7 @@ export class DynamicFieldHostComponent implements ControlValueAccessor { component = input>(); inputs = input>({}); - @ViewChild('vcRef', { read: ViewContainerRef, static: true }) viewContainerRef!: ViewContainerRef; + readonly viewContainerRef = viewChild.required('vcRef', { read: ViewContainerRef }); private componentRef?: any; private value: any; @@ -55,10 +55,10 @@ export class DynamicFieldHostComponent implements ControlValueAccessor { } private createChild() { - this.viewContainerRef.clear(); + this.viewContainerRef().clear(); if (!this.component()) return; - this.componentRef = this.viewContainerRef.createComponent(this.component()); + this.componentRef = this.viewContainerRef().createComponent(this.component()); this.applyInputs(); const instance: any = this.componentRef.instance as controlValueAccessorLike & acceptsFormControl; diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/date-time-picker/extensible-date-time-picker.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/date-time-picker/extensible-date-time-picker.component.ts index 907118283e..05fad7a63d 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/date-time-picker/extensible-date-time-picker.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/date-time-picker/extensible-date-time-picker.component.ts @@ -6,7 +6,7 @@ import { input, Optional, SkipSelf, - ViewChild, + viewChild } from '@angular/core'; import { ControlContainer, ReactiveFormsModule } from '@angular/forms'; import { @@ -76,14 +76,14 @@ export class ExtensibleDateTimePickerComponent { meridian = input(false); placement = input('bottom-left'); - @ViewChild(NgbInputDatepicker) date!: NgbInputDatepicker; - @ViewChild(NgbTimepicker) time!: NgbTimepicker; + readonly date = viewChild.required(NgbInputDatepicker); + readonly time = viewChild.required(NgbTimepicker); setDate(dateStr: string) { - this.date.writeValue(dateStr); + this.date().writeValue(dateStr); } setTime(dateStr: string) { - this.time.writeValue(dateStr); + this.time().writeValue(dateStr); } } diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts index 09a29a98a9..25436c8af0 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts @@ -19,9 +19,9 @@ import { Optional, SimpleChanges, SkipSelf, - ViewChild, signal, effect, + viewChild } from '@angular/core'; import { ControlContainer, @@ -98,7 +98,7 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { @Input() prop!: FormProp; @Input() first?: boolean; @Input() isFirstGroup?: boolean; - @ViewChild('field') private fieldRef!: ElementRef; + private readonly fieldRef = viewChild.required>('field'); injectorForCustomComponent?: Injector; asterisk = ''; @@ -158,9 +158,9 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { } ngAfterViewInit() { - if (this.isFirstGroup && this.first && this.fieldRef) { + if (this.isFirstGroup && this.first && this.fieldRef()) { requestAnimationFrame(() => { - this.fieldRef.nativeElement.focus(); + this.fieldRef().nativeElement.focus(); }); } } diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form.component.ts index 8b5829754d..4da4a21468 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form.component.ts @@ -6,9 +6,8 @@ import { inject, Input, Optional, - QueryList, SkipSelf, - ViewChildren, + viewChildren } from '@angular/core'; import { ControlContainer, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms'; import { EXTRA_PROPERTIES_KEY } from '../../constants/extra-properties'; @@ -41,8 +40,7 @@ export class ExtensibleFormComponent { private readonly extensions = inject(ExtensionsService); private readonly identifier = inject(EXTENSIONS_IDENTIFIER); - @ViewChildren(ExtensibleFormPropComponent) - formProps!: QueryList; + readonly formProps = viewChildren(ExtensibleFormPropComponent); @Input() set selectedRecord(record: R) { diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts index 75253b1f02..8669982c9c 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts @@ -4,7 +4,6 @@ import { ChangeDetectorRef, Component, computed, - ContentChild, EventEmitter, inject, Injector, @@ -18,7 +17,8 @@ import { SimpleChanges, TemplateRef, TrackByFunction, - ViewChild, + contentChild, + viewChild } from '@angular/core'; import { AsyncPipe, isPlatformBrowser, NgComponentOutlet, NgTemplateOutlet } from '@angular/common'; @@ -141,17 +141,16 @@ export class ExtensibleTableComponent implements OnChanges, AfterViewIn @Input() rowDetailHeight: string | number = '100%'; @Output() rowDetailToggle = new EventEmitter(); - @ContentChild(ExtensibleTableRowDetailComponent) - rowDetailComponent?: ExtensibleTableRowDetailComponent; + readonly rowDetailComponent = contentChild(ExtensibleTableRowDetailComponent); - @ViewChild('table', { static: false }) table!: DatatableComponent; + readonly table = viewChild.required('table'); protected get effectiveRowDetailTemplate(): TemplateRef> | undefined { - return this.rowDetailComponent?.template() ?? this.rowDetailTemplate; + return this.rowDetailComponent()?.template() ?? this.rowDetailTemplate; } protected get effectiveRowDetailHeight(): string | number { - return this.rowDetailComponent?.rowHeight() ?? this.rowDetailHeight; + return this.rowDetailComponent()?.rowHeight() ?? this.rowDetailHeight; } hasAtLeastOnePermittedAction: boolean; @@ -318,8 +317,9 @@ export class ExtensibleTableComponent implements OnChanges, AfterViewIn } toggleExpandRow(row: R): void { - if (this.table && this.table.rowDetail) { - this.table.rowDetail.toggleExpandRow(row); + const table = this.table(); + if (table && table.rowDetail) { + table.rowDetail.toggleExpandRow(row); } this.rowDetailToggle.emit(row); } diff --git a/npm/ng-packs/packages/components/page/src/page.component.html b/npm/ng-packs/packages/components/page/src/page.component.html index ee32f2e5e2..1f33a841f4 100644 --- a/npm/ng-packs/packages/components/page/src/page.component.html +++ b/npm/ng-packs/packages/components/page/src/page.component.html @@ -1,6 +1,6 @@ @if (shouldRenderRow) {
- @if (customTitle) { + @if (customTitle()) { } @else { @if (title) { @@ -12,7 +12,7 @@ } } - @if (customBreadcrumb) { + @if (customBreadcrumb()) { } @else { @if (breadcrumb) { @@ -22,7 +22,7 @@ } } - @if (customToolbar) { + @if (customToolbar()) { } @else { @if (toolbarVisible) { diff --git a/npm/ng-packs/packages/components/page/src/page.component.ts b/npm/ng-packs/packages/components/page/src/page.component.ts index bd5242651f..da81504e79 100644 --- a/npm/ng-packs/packages/components/page/src/page.component.ts +++ b/npm/ng-packs/packages/components/page/src/page.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, ViewEncapsulation, ContentChild } from '@angular/core'; +import { Component, Input, ViewEncapsulation, contentChild } from '@angular/core'; import { PageTitleContainerComponent, PageBreadcrumbContainerComponent, @@ -37,19 +37,18 @@ export class PageComponent { toolbar: PageParts.toolbar, }; - @ContentChild(PageTitleContainerComponent) customTitle?: PageTitleContainerComponent; - @ContentChild(PageBreadcrumbContainerComponent) - customBreadcrumb?: PageBreadcrumbContainerComponent; - @ContentChild(PageToolbarContainerComponent) customToolbar?: PageToolbarContainerComponent; + readonly customTitle = contentChild(PageTitleContainerComponent); + readonly customBreadcrumb = contentChild(PageBreadcrumbContainerComponent); + readonly customToolbar = contentChild(PageToolbarContainerComponent); get shouldRenderRow() { return !!( this.title || this.toolbarVisible || this.breadcrumb || - this.customTitle || - this.customBreadcrumb || - this.customToolbar || + this.customTitle() || + this.customBreadcrumb() || + this.customToolbar() || this.pageParts ); } diff --git a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts index 7ff04cf679..ada580059d 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts +++ b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts @@ -39,7 +39,7 @@ import { OnInit, TemplateRef, TrackByFunction, - ViewChild, + viewChild } from '@angular/core'; import { AbstractControl, @@ -99,8 +99,7 @@ export class UsersComponent implements OnInit { data: PagedResultDto = { items: [], totalCount: 0 }; - @ViewChild('modalContent', { static: false }) - modalContent!: TemplateRef; + readonly modalContent = viewChild.required>('modalContent'); form!: UntypedFormGroup; diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts index b2612e9aaf..635f5ed86b 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts +++ b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts @@ -15,21 +15,22 @@ import { UpdatePermissionDto, } from '@abp/ng.permission-management/proxy'; import { + afterNextRender, Component, computed, DOCUMENT, ElementRef, EventEmitter, inject, + Injector, Input, Output, - QueryList, signal, TrackByFunction, - ViewChildren, + viewChildren } from '@angular/core'; -import { concat, of } from 'rxjs'; -import { finalize, switchMap, take, tap } from 'rxjs/operators'; +import { of } from 'rxjs'; +import { finalize, switchMap, tap } from 'rxjs/operators'; import { PermissionManagement } from '../models'; import { FormsModule } from '@angular/forms'; @@ -116,6 +117,7 @@ export class PermissionManagementComponent protected readonly service = inject(PermissionsService); protected readonly configState = inject(ConfigStateService); protected readonly toasterService = inject(ToasterService); + private readonly injector = inject(Injector); private document = inject(DOCUMENT); @Input() @@ -146,11 +148,9 @@ export class PermissionManagementComponent this.openModal().subscribe(() => { this._visible = true; this.visibleChange.emit(true); - concat(this.selectAllInAllTabsRef.changes, this.selectAllInThisTabsRef.changes) - .pipe(take(1)) - .subscribe(() => { - this.initModal(); - }); + afterNextRender(() => { + this.initModal(); + }, { injector: this.injector }); }); } else { this.setSelectedGroup(null); @@ -162,10 +162,8 @@ export class PermissionManagementComponent @Output() readonly visibleChange = new EventEmitter(); - @ViewChildren('selectAllInThisTabsRef') - selectAllInThisTabsRef!: QueryList>; - @ViewChildren('selectAllInAllTabsRef') - selectAllInAllTabsRef!: QueryList>; + selectAllInThisTabsRef = viewChildren>('selectAllInThisTabsRef'); + selectAllInAllTabsRef = viewChildren>('selectAllInAllTabsRef'); data: GetPermissionListResultDto = { groups: [], entityDisplayName: '' }; diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts index a6afd37b12..40782d113c 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts @@ -11,10 +11,9 @@ import { ElementRef, inject, Input, - QueryList, Renderer2, TrackByFunction, - ViewChildren, + viewChildren } from '@angular/core'; import { NgTemplateOutlet, AsyncPipe } from '@angular/common'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; @@ -41,7 +40,7 @@ export class RoutesComponent { @Input() smallScreen?: boolean; - @ViewChildren('childrenContainer') childrenContainers!: QueryList>; + readonly childrenContainers = viewChildren>('childrenContainer'); rootDropdownExpand = {} as { [key: string]: boolean }; @@ -52,7 +51,7 @@ export class RoutesComponent { } closeDropdown() { - this.childrenContainers.forEach(({ nativeElement }) => { + this.childrenContainers().forEach(({ nativeElement }) => { this.renderer.addClass(nativeElement, 'd-none'); setTimeout(() => this.renderer.removeClass(nativeElement, 'd-none'), 0); }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts index 47272992fd..cd717d1a2d 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts @@ -7,8 +7,8 @@ import { OnInit, Output, Renderer2, - ViewChild, inject, + viewChild } from '@angular/core'; import { ABP, StopPropagationDirective } from '@abp/ng.core'; @@ -70,8 +70,7 @@ export class ButtonComponent implements OnInit { @Output() readonly abpBlur = new EventEmitter(); - @ViewChild('button', { static: true }) - buttonRef!: ElementRef; + readonly buttonRef = viewChild.required>('button'); get icon(): string { return `${this.loading ? 'fa fa-spinner fa-spin' : this.iconClass || 'd-none'}`; @@ -81,7 +80,7 @@ export class ButtonComponent implements OnInit { if (this.attributes) { Object.keys(this.attributes).forEach(key => { if (this.attributes?.[key]) { - this.renderer.setAttribute(this.buttonRef.nativeElement, key, this.attributes[key]); + this.renderer.setAttribute(this.buttonRef().nativeElement, key, this.attributes[key]); } }); } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.ts index 24e7fae1d6..c24728b886 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/http-error-wrapper/http-error-wrapper.component.ts @@ -6,12 +6,12 @@ import { ElementRef, EmbeddedViewRef, Type, - ViewChild, AfterViewInit, OnDestroy, createComponent, EnvironmentInjector, DestroyRef, + viewChild } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { DOCUMENT } from '@angular/common'; @@ -53,8 +53,7 @@ export class HttpErrorWrapperComponent implements OnInit, AfterViewInit, OnDestr isHomeShow = true; - @ViewChild('container', { static: false }) - containerRef?: ElementRef; + readonly containerRef = viewChild>('container'); get statusText(): string { return this.status ? `[${this.status}]` : ''; @@ -86,8 +85,9 @@ export class HttpErrorWrapperComponent implements OnInit, AfterViewInit, OnDestr this.appRef.attachView(customComponentRef.hostView); - if (this.containerRef) { - this.containerRef.nativeElement.appendChild( + const containerRef = this.containerRef(); + if (containerRef) { + containerRef.nativeElement.appendChild( (customComponentRef.hostView as EmbeddedViewRef).rootNodes[0], ); }