From 6df553a3afcf5e0c569cad9b22082b5ccd88da98 Mon Sep 17 00:00:00 2001 From: undsoft Date: Wed, 11 Dec 2024 18:10:07 +0100 Subject: [PATCH] NAS-133028: Enable @angular-eslint/prefer-signals --- eslint/eslint-ts-rules-extra.mjs | 5 +- package.json | 2 +- .../has-access/has-access.directive.ts | 2 + .../directives/has-role/has-role.directive.ts | 13 +- .../new-feature-indicator.directive.ts | 25 +- .../requires-roles.directive.ts | 2 + src/app/directives/ui-search.directive.ts | 16 +- .../mobile-back-button.component.html | 4 +- .../mobile-back-button.component.spec.ts | 4 +- .../mobile-back-button.component.ts | 2 +- .../similar-issues.component.ts | 15 +- .../ix-dynamic-form-item.component.html | 131 ++++----- .../ix-dynamic-form-item.component.ts | 27 +- .../ix-select-with-new-option.directive.ts | 2 +- .../ix-select/ix-select.component.spec.ts | 13 +- .../ix-select/ix-select.component.ts | 15 +- .../basic-search/basic-search.component.html | 2 +- .../basic-search/basic-search.component.ts | 8 +- .../search-input/search-input.component.ts | 26 +- .../ui-search-directives.service.spec.ts | 39 +-- .../ix-drop-grid/ix-drop-grid.directive.ts | 1 + .../ix-table-body/ix-table-body.component.ts | 8 +- .../ix-table-columns-selector.component.html | 2 +- ...x-table-columns-selector.component.spec.ts | 2 +- .../ix-table-columns-selector.component.ts | 28 +- .../ix-table-pager.component.html | 10 +- .../ix-table-pager.component.ts | 32 +-- .../directives/ix-table-cell.directive.ts | 4 +- .../nested-tree-node.component.ts | 5 +- .../tree-node/tree-node.component.ts | 5 +- .../tree-virtual-scroll-view.component.ts | 1 + .../ix-tree/components/tree/tree.component.ts | 5 +- .../directives/tree-node-def.directive.ts | 6 +- .../tree-node-padding-def.directive.ts | 2 + .../with-loading-state.directive.ts | 26 +- .../master-detail-view.component.html | 2 +- .../test-override/test-override.directive.ts | 1 + .../app-bulk-upgrade.component.ts | 3 +- src/app/pages/audit/audit.component.html | 4 +- .../log-details-panel.component.html | 2 +- .../privilege-list.component.html | 4 +- .../user-api-keys.component.html | 4 +- .../dashboard/dashboard.component.html | 1 - .../cloud-backup-details.component.html | 2 +- .../dataset-details-panel.component.html | 2 +- .../dataset-icon/dataset-icon.component.ts | 6 +- .../acl-editor-save-controls.component.ts | 2 +- .../disk-details-panel.component.html | 2 +- .../bootenv-list/bootenv-list.component.ts | 2 +- src/assets/i18n/fr.json | 46 +-- src/assets/icons/custom/file-link.svg | 1 - src/assets/icons/sprite-config.json | 2 +- src/assets/icons/sprite.svg | 2 +- yarn.lock | 264 +++++++++++------- 54 files changed, 473 insertions(+), 369 deletions(-) delete mode 100644 src/assets/icons/custom/file-link.svg diff --git a/eslint/eslint-ts-rules-extra.mjs b/eslint/eslint-ts-rules-extra.mjs index 47c057a6682..32e25b702ff 100644 --- a/eslint/eslint-ts-rules-extra.mjs +++ b/eslint/eslint-ts-rules-extra.mjs @@ -1,5 +1,4 @@ import airbnbBestPractices from "eslint-config-airbnb-base/rules/best-practices"; -import airbnbStyle from "eslint-config-airbnb-base/rules/style"; import airbnbVariables from "eslint-config-airbnb-base/rules/variables"; /** @@ -40,6 +39,10 @@ export const extraRules = { }], "@angular-eslint/prefer-standalone": "error", "@angular-eslint/prefer-on-push-component-change-detection": "error", + "@angular-eslint/prefer-signals": ["error", { + preferQuerySignals: false, + preferReadonlySignalProperties: false, + }], // Angular file naming "angular-file-naming/component-filename-suffix": "error", diff --git a/package.json b/package.json index 4cc1b6236e4..9fa5a3a16a1 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "@xterm/addon-fit": "~0.10.0", "@xterm/xterm": "~5.5.0", "angular-dual-listbox": "~7.0.0", - "angular-eslint": "~18.4.0", + "angular-eslint": "~19.0.1", "angular-resize-event": "^3.2.0", "angular2-uuid": "~1.1.1", "chart.js": "~4.4.4", diff --git a/src/app/directives/has-access/has-access.directive.ts b/src/app/directives/has-access/has-access.directive.ts index 18de412facc..b4a3b5ce4ca 100644 --- a/src/app/directives/has-access/has-access.directive.ts +++ b/src/app/directives/has-access/has-access.directive.ts @@ -13,6 +13,7 @@ export class HasAccessDirective { private wrapperContainer: ComponentRef; private previousAccess: boolean = null; + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() set ixHasAccess(hasAccess: boolean) { if (this.previousAccess === hasAccess) { @@ -32,6 +33,7 @@ export class HasAccessDirective { protected cssClassList: string[] = []; + // eslint-disable-next-line @angular-eslint/prefer-signals @Input('class') @HostBinding('class') get elementClass(): string { diff --git a/src/app/directives/has-role/has-role.directive.ts b/src/app/directives/has-role/has-role.directive.ts index 852f8ef6fa3..3dda7d8f656 100644 --- a/src/app/directives/has-role/has-role.directive.ts +++ b/src/app/directives/has-role/has-role.directive.ts @@ -1,6 +1,6 @@ import { ChangeDetectorRef, - Directive, Input, TemplateRef, ViewContainerRef, + Directive, effect, input, TemplateRef, ViewContainerRef, } from '@angular/core'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { distinctUntilChanged } from 'rxjs'; @@ -13,8 +13,12 @@ import { AuthService } from 'app/services/auth/auth.service'; standalone: true, }) export class HasRoleDirective { - @Input() set ixHasRole(roles: Role[]) { - this.authService.hasRole(roles).pipe( + readonly roles = input.required({ + alias: 'ixHasRole', + }); + + private readonly updateView = effect(() => { + this.authService.hasRole(this.roles()).pipe( distinctUntilChanged(), untilDestroyed(this), ).subscribe((hasRole) => { @@ -24,8 +28,9 @@ export class HasRoleDirective { } this.cdr.markForCheck(); + this.cdr.detectChanges(); }); - } + }); constructor( private templateRef: TemplateRef, diff --git a/src/app/directives/new-feature-indicator/new-feature-indicator.directive.ts b/src/app/directives/new-feature-indicator/new-feature-indicator.directive.ts index 7b80a4f63cb..28617d66775 100644 --- a/src/app/directives/new-feature-indicator/new-feature-indicator.directive.ts +++ b/src/app/directives/new-feature-indicator/new-feature-indicator.directive.ts @@ -1,10 +1,11 @@ import { - ComponentRef, Directive, Input, TemplateRef, ViewContainerRef, + ComponentRef, Directive, input, OnChanges, OnInit, TemplateRef, ViewContainerRef, } from '@angular/core'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { NewFeatureIndicatorWrapperComponent } from 'app/directives/new-feature-indicator/new-feature-indicator-wrapper.component'; import { NewFeatureIndicator } from 'app/directives/new-feature-indicator/new-feature-indicator.interface'; import { NewFeatureIndicatorService } from 'app/directives/new-feature-indicator/new-feature-indicator.service'; +import { IxSimpleChanges } from 'app/interfaces/simple-changes.interface'; /** * Usage: adding an indicator with a hint about a new feature. @@ -21,15 +22,13 @@ import { NewFeatureIndicatorService } from 'app/directives/new-feature-indicator selector: '[ixNewFeatureIndicator]', standalone: true, }) -export class NewFeatureIndicatorDirective { +export class NewFeatureIndicatorDirective implements OnInit, OnChanges { private wrapperContainer: ComponentRef; private indicator: NewFeatureIndicator; - @Input() - set ixNewFeatureIndicator(indicator: NewFeatureIndicator) { - this.indicator = indicator; - this.updateIndicator(); - } + readonly newFeatureIndicator = input.required({ + alias: 'ixNewFeatureIndicator', + }); constructor( private indicatorService: NewFeatureIndicatorService, @@ -43,6 +42,18 @@ export class NewFeatureIndicatorDirective { }); } + ngOnInit(): void { + this.indicator = this.newFeatureIndicator(); + this.updateIndicator(); + } + + ngOnChanges(changes: IxSimpleChanges): void { + if ('newFeatureIndicator' in changes) { + this.indicator = this.newFeatureIndicator(); + this.updateIndicator(); + } + } + updateIndicator(): void { this.viewContainerRef.clear(); if (this.indicatorService.wasIndicatorShown(this.indicator)) { diff --git a/src/app/directives/requires-roles/requires-roles.directive.ts b/src/app/directives/requires-roles/requires-roles.directive.ts index f9824b8aee4..3ba0a5e7077 100644 --- a/src/app/directives/requires-roles/requires-roles.directive.ts +++ b/src/app/directives/requires-roles/requires-roles.directive.ts @@ -16,6 +16,7 @@ import { AuthService } from 'app/services/auth/auth.service'; export class RequiresRolesDirective extends HasAccessDirective { private previousRoles: Role[] = []; + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() set ixRequiresRoles(roles: Role[]) { if (isEqual(this.previousRoles, roles)) { @@ -37,6 +38,7 @@ export class RequiresRolesDirective extends HasAccessDirective { protected override cssClassList: string[] = []; + // eslint-disable-next-line @angular-eslint/prefer-signals @Input('class') @HostBinding('class') override get elementClass(): string { diff --git a/src/app/directives/ui-search.directive.ts b/src/app/directives/ui-search.directive.ts index 84f916489cc..98c6786bac5 100644 --- a/src/app/directives/ui-search.directive.ts +++ b/src/app/directives/ui-search.directive.ts @@ -1,6 +1,6 @@ import { - Directive, Input, ElementRef, Renderer2, OnInit, - OnDestroy, + Directive, ElementRef, Renderer2, OnInit, + OnDestroy, input, } from '@angular/core'; import { Timeout } from 'app/interfaces/timeout.interface'; import { searchDelayConst } from 'app/modules/global-search/constants/delay.const'; @@ -13,18 +13,20 @@ import { UiSearchDirectivesService } from 'app/modules/global-search/services/ui standalone: true, }) export class UiSearchDirective implements OnInit, OnDestroy { - @Input({ required: true, alias: 'ixUiSearch' }) config: UiSearchableElement; + readonly config = input.required({ + alias: 'ixUiSearch', + }); get id(): string { - return getSearchableElementId(this.config); + return getSearchableElementId(this.config()); } get ariaLabel(): string { - const hierarchyItem = this.config.hierarchy?.[this.config.hierarchy.length - 1] || ''; + const hierarchyItem = this.config().hierarchy?.[this.config().hierarchy.length - 1] || ''; const isSingleWord = hierarchyItem.trim().split(/\s+/).length === 1; - if (isSingleWord && this.config.synonyms?.length > 0) { - return this.config.synonyms.reduce((best, synonym) => { + if (isSingleWord && this.config().synonyms?.length > 0) { + return this.config().synonyms.reduce((best, synonym) => { const synonymWordCount = synonym.trim().split(/\s+/).length; const bestWordCount = best.trim().split(/\s+/).length; return synonymWordCount > bestWordCount ? synonym : best; diff --git a/src/app/modules/buttons/mobile-back-button/mobile-back-button.component.html b/src/app/modules/buttons/mobile-back-button/mobile-back-button.component.html index 297ee9bd8d8..c0761ef5cc6 100644 --- a/src/app/modules/buttons/mobile-back-button/mobile-back-button.component.html +++ b/src/app/modules/buttons/mobile-back-button/mobile-back-button.component.html @@ -4,8 +4,8 @@ id="mobile-back-button" ixTest="disk-details-back" [attr.aria-label]="'Back' | translate" - (click)="onClose.emit()" - (keydown.enter)="onClose.emit()" + (click)="close.emit()" + (keydown.enter)="close.emit()" > diff --git a/src/app/modules/buttons/mobile-back-button/mobile-back-button.component.spec.ts b/src/app/modules/buttons/mobile-back-button/mobile-back-button.component.spec.ts index da216234524..f3077b25f77 100644 --- a/src/app/modules/buttons/mobile-back-button/mobile-back-button.component.spec.ts +++ b/src/app/modules/buttons/mobile-back-button/mobile-back-button.component.spec.ts @@ -19,13 +19,13 @@ describe('MobileBackButtonComponent', () => { }); it('should emit onClose when the button is clicked', () => { - const onCloseSpy = jest.spyOn(spectator.component.onClose, 'emit'); + const onCloseSpy = jest.spyOn(spectator.component.close, 'emit'); spectator.click('#mobile-back-button'); expect(onCloseSpy).toHaveBeenCalled(); }); it('should emit onClose when the Enter key is pressed', () => { - const onCloseSpy = jest.spyOn(spectator.component.onClose, 'emit'); + const onCloseSpy = jest.spyOn(spectator.component.close, 'emit'); spectator.dispatchKeyboardEvent('#mobile-back-button', 'keydown', 'Enter'); expect(onCloseSpy).toHaveBeenCalled(); }); diff --git a/src/app/modules/buttons/mobile-back-button/mobile-back-button.component.ts b/src/app/modules/buttons/mobile-back-button/mobile-back-button.component.ts index a487e2ac456..572e568ed9f 100644 --- a/src/app/modules/buttons/mobile-back-button/mobile-back-button.component.ts +++ b/src/app/modules/buttons/mobile-back-button/mobile-back-button.component.ts @@ -20,5 +20,5 @@ import { TestDirective } from 'app/modules/test-id/test.directive'; styleUrls: ['./mobile-back-button.component.scss'], }) export class MobileBackButtonComponent { - readonly onClose = output(); + readonly close = output(); } diff --git a/src/app/modules/feedback/components/similar-issues/similar-issues.component.ts b/src/app/modules/feedback/components/similar-issues/similar-issues.component.ts index 17f706b769f..4682e472208 100644 --- a/src/app/modules/feedback/components/similar-issues/similar-issues.component.ts +++ b/src/app/modules/feedback/components/similar-issues/similar-issues.component.ts @@ -1,6 +1,6 @@ import { AsyncPipe } from '@angular/common'; import { - ChangeDetectionStrategy, Component, Input, + ChangeDetectionStrategy, Component, input, OnChanges, } from '@angular/core'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { TranslateModule } from '@ngx-translate/core'; @@ -8,6 +8,7 @@ import { sortBy, uniqBy } from 'lodash-es'; import { BehaviorSubject, Observable, debounceTime, distinctUntilChanged, filter, pairwise, switchMap, } from 'rxjs'; +import { IxSimpleChanges } from 'app/interfaces/simple-changes.interface'; import { SimilarIssue } from 'app/modules/feedback/interfaces/file-ticket.interface'; import { FeedbackService } from 'app/modules/feedback/services/feedback.service'; import { IxIconComponent } from 'app/modules/ix-icon/ix-icon.component'; @@ -27,10 +28,8 @@ import { TestDirective } from 'app/modules/test-id/test.directive'; AsyncPipe, ], }) -export class SimilarIssuesComponent { - @Input() set query(value: string) { - this.query$.next(value); - } +export class SimilarIssuesComponent implements OnChanges { + readonly query = input(); protected similarIssues$ = new BehaviorSubject([]); protected isLoading$ = new BehaviorSubject(false); @@ -44,6 +43,12 @@ export class SimilarIssuesComponent { this.listenForQueryChanges(); } + ngOnChanges(changes: IxSimpleChanges): void { + if ('query' in changes) { + this.query$.next(this.query()); + } + } + private listenForQueryChanges(): void { this.query$.pipe( filter((query) => query?.length >= 3), diff --git a/src/app/modules/forms/ix-dynamic-form/components/ix-dynamic-form/ix-dynamic-form-item/ix-dynamic-form-item.component.html b/src/app/modules/forms/ix-dynamic-form/components/ix-dynamic-form/ix-dynamic-form-item/ix-dynamic-form-item.component.html index 48c0b4191c6..956cff6e6d8 100644 --- a/src/app/modules/forms/ix-dynamic-form/components/ix-dynamic-form/ix-dynamic-form-item/ix-dynamic-form-item.component.html +++ b/src/app/modules/forms/ix-dynamic-form/components/ix-dynamic-form/ix-dynamic-form-item/ix-dynamic-form-item.component.html @@ -1,43 +1,44 @@ -@if (dynamicSchema) { +@let schema = dynamicSchema(); +@if (schema) {
@if (!(isHidden$ | async)) { - @switch (dynamicSchema.type) { + @switch (schema.type) { @case (DynamicFormSchemaType.Cron) { } @case (DynamicFormSchemaType.Dict) { - @if (dynamicSchema.title) { + @if (schema.title) {
- {{ dynamicSchema.title }} + {{ schema.title }}
- @if (dynamicSchema.tooltip) { + @if (schema.tooltip) {
}
} - @for (attr of dynamicSchema.attrs; track attr) { + @for (attr of schema.attrs; track attr) { } @case (DynamicFormSchemaType.List) { @for (element of getFormArray.controls; track element; let i = $index) { - @for (item of dynamicSchema.items; track item) { + @for (item of schema.items; track item) { } - + } @case (DynamicFormSchemaType.Input) { } @case (DynamicFormSchemaType.Uri) { } @case (DynamicFormSchemaType.Select) { } @case (DynamicFormSchemaType.Enum) { } @case (DynamicFormSchemaType.Explorer) { } @case (DynamicFormSchemaType.Checkbox) { } @case (DynamicFormSchemaType.Ipaddr) { } } diff --git a/src/app/modules/forms/ix-dynamic-form/components/ix-dynamic-form/ix-dynamic-form-item/ix-dynamic-form-item.component.ts b/src/app/modules/forms/ix-dynamic-form/components/ix-dynamic-form/ix-dynamic-form-item/ix-dynamic-form-item.component.ts index 4b1ef65866f..a21b15da9e3 100644 --- a/src/app/modules/forms/ix-dynamic-form/components/ix-dynamic-form/ix-dynamic-form-item/ix-dynamic-form-item.component.ts +++ b/src/app/modules/forms/ix-dynamic-form/components/ix-dynamic-form/ix-dynamic-form-item/ix-dynamic-form-item.component.ts @@ -1,6 +1,6 @@ import { AsyncPipe } from '@angular/common'; import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, input, Input, OnInit, output, + ChangeDetectionStrategy, ChangeDetectorRef, Component, input, OnInit, output, } from '@angular/core'; import { UntypedFormArray, UntypedFormGroup, ReactiveFormsModule } from '@angular/forms'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; @@ -57,7 +57,7 @@ import { TooltipComponent } from 'app/modules/tooltip/tooltip.component'; }) export class IxDynamicFormItemComponent implements OnInit { readonly dynamicForm = input(); - @Input() dynamicSchema: DynamicFormSchemaNode; + readonly dynamicSchema = input(); readonly isEditMode = input(); readonly addListItem = output(); @@ -66,7 +66,7 @@ export class IxDynamicFormItemComponent implements OnInit { readonly DynamicFormSchemaType = DynamicFormSchemaType; get isAllListControlsDisabled(): boolean { - return (this.dynamicSchema as DynamicFormSchemaList).items.every((item) => { + return (this.dynamicSchema() as DynamicFormSchemaList).items.every((item) => { return item.editable !== undefined && item.editable !== null && !item.editable; }); } @@ -76,7 +76,7 @@ export class IxDynamicFormItemComponent implements OnInit { ) {} ngOnInit(): void { - const dependsOn = this.dynamicSchema?.dependsOn; + const dependsOn = this.dynamicSchema()?.dependsOn; dependsOn?.forEach((depend) => { this.dynamicForm()?.valueChanges.pipe( @@ -91,30 +91,31 @@ export class IxDynamicFormItemComponent implements OnInit { }); }); if ( - this.dynamicSchema?.editable !== undefined - && !this.dynamicSchema?.editable + this.dynamicSchema()?.editable !== undefined + && !this.dynamicSchema()?.editable ) { - this.dynamicForm()?.get(this.dynamicSchema.controlName)?.disable(); + this.dynamicForm()?.get(this.dynamicSchema().controlName)?.disable(); } - if (this.dynamicSchema?.hidden) { - (this.dynamicForm().controls[this.dynamicSchema.controlName] as CustomUntypedFormField)?.hidden$?.next(true); + if (this.dynamicSchema()?.hidden) { + (this.dynamicForm().controls[this.dynamicSchema().controlName] as CustomUntypedFormField)?.hidden$?.next(true); } } get getFormArray(): UntypedFormArray { - return this.dynamicForm().controls[this.dynamicSchema.controlName] as UntypedFormArray; + return this.dynamicForm().controls[this.dynamicSchema().controlName] as UntypedFormArray; } get isHidden$(): Subject { - return (this.dynamicForm().controls[this.dynamicSchema.controlName] as CustomUntypedFormField)?.hidden$; + return (this.dynamicForm().controls[this.dynamicSchema().controlName] as CustomUntypedFormField)?.hidden$; } addControl(schema?: ChartSchemaNode[]): void { - if (this.dynamicSchema.type === DynamicFormSchemaType.List) { + const dynamicSchema = this.dynamicSchema(); + if (dynamicSchema.type === DynamicFormSchemaType.List) { this.addListItem.emit({ array: this.getFormArray, - schema: schema || this.dynamicSchema.itemsSchema, + schema: schema || dynamicSchema.itemsSchema, }); } } diff --git a/src/app/modules/forms/ix-forms/components/ix-select/ix-select-with-new-option.directive.ts b/src/app/modules/forms/ix-forms/components/ix-select/ix-select-with-new-option.directive.ts index 636a6937770..d1508e55f0a 100644 --- a/src/app/modules/forms/ix-forms/components/ix-select/ix-select-with-new-option.directive.ts +++ b/src/app/modules/forms/ix-forms/components/ix-select/ix-select-with-new-option.directive.ts @@ -56,7 +56,7 @@ export abstract class IxSelectWithNewOption implements OnInit, AfterViewInit { if (!this.ixSelect) { return; } - this.ixSelect.options = this.options.asObservable(); + this.ixSelect.options.set(this.options.asObservable()); this.ixSelect.ngOnChanges(); this.ixSelect.controlDirective.control.valueChanges.pipe( distinctUntilChanged(), diff --git a/src/app/modules/forms/ix-forms/components/ix-select/ix-select.component.spec.ts b/src/app/modules/forms/ix-forms/components/ix-select/ix-select.component.spec.ts index 5aa3999a49c..e2243cbe0c1 100644 --- a/src/app/modules/forms/ix-forms/components/ix-select/ix-select.component.spec.ts +++ b/src/app/modules/forms/ix-forms/components/ix-select/ix-select.component.spec.ts @@ -133,7 +133,7 @@ describe('IxSelectComponent', () => { }); it('shows \'No options\' if options length === 0', async () => { - spectator.component.options = of([]); + spectator.setHostInput('options', of([])); spectator.component.ngOnChanges(); const select = await (await loader.getHarness(IxSelectHarness)).getSelectHarness(); @@ -146,7 +146,7 @@ describe('IxSelectComponent', () => { it('shows \'Options cannot be loaded\' if options has some error', async () => { jest.spyOn(console, 'error').mockImplementation(); - spectator.component.options = throwError(() => new Error('Some Error')); + spectator.setHostInput('options', throwError(() => new Error('Some Error'))); spectator.component.ngOnChanges(); const select = await (await loader.getHarness(IxSelectHarness)).getSelectHarness(); @@ -158,10 +158,11 @@ describe('IxSelectComponent', () => { }); it('allows some options to be disabled', async () => { - spectator.component.options = of([ + spectator.setHostInput('options', of([ { label: 'GBR', value: 'Great Britain' }, { label: 'GRL', value: 'Greenland', disabled: true }, - ]); + ])); + spectator.component.ngOnChanges(); const select = await (await loader.getHarness(IxSelectHarness)).getSelectHarness(); @@ -173,10 +174,10 @@ describe('IxSelectComponent', () => { }); it('shows options tooltip if it is provided', async () => { - spectator.component.options = of([ + spectator.setHostInput('options', of([ { label: 'GBR', value: 'Great Britain' }, { label: 'GRL', value: 'Greenland', tooltip: 'Not really green.' }, - ]); + ])); spectator.component.ngOnChanges(); const select = await (await loader.getHarness(IxSelectHarness)).getSelectHarness(); diff --git a/src/app/modules/forms/ix-forms/components/ix-select/ix-select.component.ts b/src/app/modules/forms/ix-forms/components/ix-select/ix-select.component.ts index 24b79ade1c0..f8496bf7dce 100644 --- a/src/app/modules/forms/ix-forms/components/ix-select/ix-select.component.ts +++ b/src/app/modules/forms/ix-forms/components/ix-select/ix-select.component.ts @@ -1,7 +1,7 @@ import { AsyncPipe } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, - Component, input, Input, OnChanges, OnInit, + Component, input, model, OnChanges, OnInit, } from '@angular/core'; import { ControlValueAccessor, NgControl, FormsModule } from '@angular/forms'; import { MatOption } from '@angular/material/core'; @@ -53,10 +53,7 @@ export type IxSelectValue = SelectOptionValueType; export class IxSelectComponent implements ControlValueAccessor, OnInit, OnChanges { readonly label = input(); readonly hint = input(); - - @Input() - options: Observable; - + readonly options = model>(); readonly required = input(); readonly tooltip = input(); readonly multiple = input(); @@ -101,11 +98,11 @@ export class IxSelectComponent implements ControlValueAccessor, OnInit, OnChange } get disabledState(): boolean { - return this.isDisabled || !this.options; + return this.isDisabled || !this.options(); } get isLoadingState(): boolean { - return this.isLoading || !this.options; + return this.isLoading || !this.options(); } constructor(public controlDirective: NgControl, private cdr: ChangeDetectorRef) { @@ -113,12 +110,12 @@ export class IxSelectComponent implements ControlValueAccessor, OnInit, OnChange } ngOnChanges(): void { - if (!this.options) { + if (!this.options()) { this.hasErrorInOptions = true; } else { this.hasErrorInOptions = false; this.isLoading = true; - this.opts$ = this.options.pipe( + this.opts$ = this.options().pipe( catchError((error: unknown) => { console.error(error); this.hasErrorInOptions = true; diff --git a/src/app/modules/forms/search-input/components/basic-search/basic-search.component.html b/src/app/modules/forms/search-input/components/basic-search/basic-search.component.html index 4613a66b2a6..47cec5d1a1a 100644 --- a/src/app/modules/forms/search-input/components/basic-search/basic-search.component.html +++ b/src/app/modules/forms/search-input/components/basic-search/basic-search.component.html @@ -8,7 +8,7 @@ ixTest="search" class="input" [placeholder]="'Search' | translate" - [ngModel]="query" + [ngModel]="query()" (ngModelChange)="queryChange.emit($event)" (keydown.enter)="runSearch.emit()" /> diff --git a/src/app/modules/forms/search-input/components/basic-search/basic-search.component.ts b/src/app/modules/forms/search-input/components/basic-search/basic-search.component.ts index a50d40fe26b..93e6144126c 100644 --- a/src/app/modules/forms/search-input/components/basic-search/basic-search.component.ts +++ b/src/app/modules/forms/search-input/components/basic-search/basic-search.component.ts @@ -1,6 +1,6 @@ import { AfterViewInit, - ChangeDetectionStrategy, Component, ElementRef, input, Input, output, ViewChild, + ChangeDetectionStrategy, Component, ElementRef, input, model, output, ViewChild, } from '@angular/core'; import { ReactiveFormsModule, FormsModule } from '@angular/forms'; import { MatInput } from '@angular/material/input'; @@ -24,7 +24,7 @@ import { TestDirective } from 'app/modules/test-id/test.directive'; ], }) export class BasicSearchComponent implements AfterViewInit { - @Input() query: string; + readonly query = model(); readonly allowAdvanced = input(false); readonly switchToAdvanced = output(); @@ -38,8 +38,8 @@ export class BasicSearchComponent implements AfterViewInit { } protected resetInput(): void { - this.query = ''; - this.queryChange.emit(this.query); + this.query.set(''); + this.queryChange.emit(''); this.runSearch.emit(); this.focusInput(); } diff --git a/src/app/modules/forms/search-input/components/search-input/search-input.component.ts b/src/app/modules/forms/search-input/components/search-input/search-input.component.ts index d75905253a8..ebc80e6b2d8 100644 --- a/src/app/modules/forms/search-input/components/search-input/search-input.component.ts +++ b/src/app/modules/forms/search-input/components/search-input/search-input.component.ts @@ -1,7 +1,6 @@ import { ChangeDetectionStrategy, - Component, input, - Input, + Component, input, model, OnChanges, output, ViewChild, } from '@angular/core'; @@ -25,7 +24,7 @@ import { export class SearchInputComponent implements OnChanges { readonly allowAdvanced = input(true); readonly properties = input[]>([]); - @Input() query: SearchQuery; + readonly query = model>(); readonly advancedSearchPlaceholder = input(); readonly queryChange = output>(); @@ -50,38 +49,39 @@ export class SearchInputComponent implements OnChanges { protected basicSearchUpdated(query: string): void { this.basicQuery = query; this.updateQuery(); - this.queryChange.emit(this.query); + this.queryChange.emit(this.query()); } protected advancedSearchUpdated(query: QueryFilters): void { this.advancedQuery = query; this.updateQuery(); - this.queryChange.emit(this.query); + this.queryChange.emit(this.query()); } private updateQuery(): void { if (this.isInAdvancedMode) { - this.query = { + this.query.set({ filters: this.advancedQuery, isBasicQuery: false, - }; + }); } else { - this.query = { + this.query.set({ query: this.basicQuery, isBasicQuery: true, - }; + }); } } private selectModeFromQuery(): void { - if (!this.query) { + const query = this.query(); + if (!query) { this.isInAdvancedMode = false; - } else if (this.query.isBasicQuery) { + } else if (query.isBasicQuery) { this.isInAdvancedMode = false; - this.basicQuery = this.query.query; + this.basicQuery = query.query; } else if (this.allowAdvanced()) { this.isInAdvancedMode = true; - this.advancedQuery = (this.query as AdvancedSearchQuery).filters; + this.advancedQuery = (query as AdvancedSearchQuery).filters; } } } diff --git a/src/app/modules/global-search/services/ui-search-directives.service.spec.ts b/src/app/modules/global-search/services/ui-search-directives.service.spec.ts index 2bbe57aea17..3d9e012a326 100644 --- a/src/app/modules/global-search/services/ui-search-directives.service.spec.ts +++ b/src/app/modules/global-search/services/ui-search-directives.service.spec.ts @@ -6,8 +6,6 @@ import { UiSearchDirectivesService } from './ui-search-directives.service'; describe('UiSearchDirectivesService', () => { let spectator: SpectatorService; - let renderer: Renderer2; - let elementRef: ElementRef; const createService = createServiceFactory({ service: UiSearchDirectivesService, @@ -20,27 +18,28 @@ describe('UiSearchDirectivesService', () => { describe('basic operations', () => { beforeEach(() => { spectator = createService(); - renderer = spectator.inject(Renderer2); - elementRef = spectator.inject(ElementRef); }); it('should check register and unregister', () => { - const directive = new UiSearchDirective(renderer, elementRef, spectator.service); - directive.config = { anchor: 'anchor' }; + const fakeDirective = { + config: () => ({ anchor: 'anchor' }), + } as UiSearchDirective; - spectator.service.register(directive); + spectator.service.register(fakeDirective); expect(spectator.service.size()).toBe(1); - spectator.service.unregister(directive); + spectator.service.unregister(fakeDirective); expect(spectator.service.size()).toBe(0); }); it('should check get method', () => { - const directive = new UiSearchDirective(renderer, elementRef, spectator.service); - directive.config = { anchor: 'anchor' }; + const fakeDirective = { + config: () => ({ anchor: 'anchor' }), + id: 'anchor', + } as UiSearchDirective; - spectator.service.register(directive); - expect(spectator.service.get({ anchor: 'anchor' })).toEqual(directive); + spectator.service.register(fakeDirective); + expect(spectator.service.get({ anchor: 'anchor' })).toEqual(fakeDirective); }); it('should check setPendingUiHighlightElement method', () => { @@ -50,12 +49,13 @@ describe('UiSearchDirectivesService', () => { }); it('should check directiveAdded$ BehaviorSubject', () => { - const directive = new UiSearchDirective(renderer, elementRef, spectator.service); - directive.config = { anchor: 'anchor' }; + const fakeDirective = { + config: () => ({ anchor: 'anchor' }), + } as UiSearchDirective; - spectator.service.register(directive); + spectator.service.register(fakeDirective); spectator.service.directiveAdded$.subscribe((value) => { - expect(value).toEqual(directive); + expect(value).toEqual(fakeDirective); }); }); @@ -66,10 +66,11 @@ describe('UiSearchDirectivesService', () => { }); it('should check size method', () => { - const directive = new UiSearchDirective(renderer, elementRef, spectator.service); - directive.config = { anchor: 'anchor' }; + const fakeDirective = { + config: () => ({ anchor: 'anchor' }), + } as UiSearchDirective; - spectator.service.register(directive); + spectator.service.register(fakeDirective); expect(spectator.service.size()).toBe(1); }); diff --git a/src/app/modules/ix-drop-grid/ix-drop-grid.directive.ts b/src/app/modules/ix-drop-grid/ix-drop-grid.directive.ts index 206df2a8040..31dd00c3da7 100644 --- a/src/app/modules/ix-drop-grid/ix-drop-grid.directive.ts +++ b/src/app/modules/ix-drop-grid/ix-drop-grid.directive.ts @@ -31,6 +31,7 @@ import { ixDropGridDirectiveToken } from 'app/modules/ix-drop-grid/ix-drop-grid. standalone: true, }) export class IxDropGridDirective extends CdkDropListGroup implements OnInit { + // eslint-disable-next-line @angular-eslint/prefer-signals @Input() ixDropGridModel: T[]; readonly ixDropGridModelChange = output(); diff --git a/src/app/modules/ix-table/components/ix-table-body/ix-table-body.component.ts b/src/app/modules/ix-table/components/ix-table-body/ix-table-body.component.ts index 7cc210fff91..aa8ade7d5de 100644 --- a/src/app/modules/ix-table/components/ix-table-body/ix-table-body.component.ts +++ b/src/app/modules/ix-table/components/ix-table-body/ix-table-body.component.ts @@ -70,13 +70,13 @@ export class IxTableBodyComponent implements AfterViewInit { constructor(private cdr: ChangeDetectorRef) {} ngAfterViewInit(): void { - const templatedCellIndexes = this.customCells.toArray().map((cell) => cell.columnIndex); + const templatedCellIndexes = this.customCells.toArray().map((cell) => cell.columnIndex()); const availableIndexes = Array.from({ length: this.columns().length }, (_, idx) => idx) .filter((idx) => !templatedCellIndexes.includes(idx)); this.customCells.forEach((cell) => { - if (cell.columnIndex === undefined) { - cell.columnIndex = availableIndexes.shift(); + if (cell.columnIndex() === undefined) { + cell.columnIndex.set(availableIndexes.shift()); } }); @@ -91,7 +91,7 @@ export class IxTableBodyComponent implements AfterViewInit { } getTemplateByColumnIndex(idx: number): TemplateRef<{ $implicit: T }> | undefined { - return this.customCells.toArray().find((cell) => cell.columnIndex === idx)?.templateRef; + return this.customCells.toArray().find((cell) => cell.columnIndex() === idx)?.templateRef; } onToggle(row: T): void { diff --git a/src/app/modules/ix-table/components/ix-table-columns-selector/ix-table-columns-selector.component.html b/src/app/modules/ix-table/components/ix-table-columns-selector/ix-table-columns-selector.component.html index 4f7a9f2cdd5..25bd14b837c 100644 --- a/src/app/modules/ix-table/components/ix-table-columns-selector/ix-table-columns-selector.component.html +++ b/src/app/modules/ix-table/components/ix-table-columns-selector/ix-table-columns-selector.component.html @@ -24,7 +24,7 @@
- @for (column of columns; track column) { + @for (column of columns(); track column) { @if (column.title) { diff --git a/src/app/pages/audit/components/log-details-panel/log-details-panel.component.html b/src/app/pages/audit/components/log-details-panel/log-details-panel.component.html index e87b6fdef23..c5170fcc2ae 100644 --- a/src/app/pages/audit/components/log-details-panel/log-details-panel.component.html +++ b/src/app/pages/audit/components/log-details-panel/log-details-panel.component.html @@ -2,7 +2,7 @@

{{ 'Log Details' | translate }}
diff --git a/src/app/pages/credentials/groups/privilege/privilege-list/privilege-list.component.html b/src/app/pages/credentials/groups/privilege/privilege-list/privilege-list.component.html index d24351237f3..3851fad8af4 100644 --- a/src/app/pages/credentials/groups/privilege/privilege-list/privilege-list.component.html +++ b/src/app/pages/credentials/groups/privilege/privilege-list/privilege-list.component.html @@ -6,10 +6,10 @@ [properties]="searchProperties" [query]="searchQuery" [advancedSearchPlaceholder]="advancedSearchPlaceholder" - (runSearch)="onSearch(searchInput.query);" + (runSearch)="onSearch(searchInput.query());" >
- diff --git a/src/app/pages/dashboard/components/dashboard/dashboard.component.html b/src/app/pages/dashboard/components/dashboard/dashboard.component.html index 67b9c4b8b03..0e35af92938 100644 --- a/src/app/pages/dashboard/components/dashboard/dashboard.component.html +++ b/src/app/pages/dashboard/components/dashboard/dashboard.component.html @@ -1,7 +1,6 @@ @if (!isEditing()) {