diff --git a/src/app/common/breadcrumb/breadcrumb.component.ts b/src/app/common/breadcrumb/breadcrumb.component.ts index 3295f5dc..a1c320af 100644 --- a/src/app/common/breadcrumb/breadcrumb.component.ts +++ b/src/app/common/breadcrumb/breadcrumb.component.ts @@ -15,6 +15,9 @@ import { Breadcrumb, BreadcrumbService } from './breadcrumb.service'; imports: [RouterLink] }) export class BreadcrumbComponent { + readonly #route = inject(ActivatedRoute); + readonly #router = inject(Router); + @Input({ required: true }) set homeBreadcrumb(hb: Breadcrumb) { this._homeBreadcrumb = hb; @@ -25,11 +28,8 @@ export class BreadcrumbComponent { breadcrumbs: Breadcrumb[] = []; - private route = inject(ActivatedRoute); - private router = inject(Router); - constructor() { - const navEnd$: Observable = this.router.events.pipe( + const navEnd$: Observable = this.#router.events.pipe( filter((event) => event instanceof NavigationEnd) ); merge(navEnd$, this.homeBreadcrumbChanged$) @@ -39,7 +39,7 @@ export class BreadcrumbComponent { this.breadcrumbs = [this._homeBreadcrumb]; this.breadcrumbs = this.breadcrumbs.concat( BreadcrumbService.getBreadcrumbs( - this.route.root.snapshot, + this.#route.root.snapshot, this._homeBreadcrumb.url ) ); diff --git a/src/app/common/flyout/flyout.component.html b/src/app/common/flyout/flyout.component.html index a51445d0..5ac472b8 100644 --- a/src/app/common/flyout/flyout.component.html +++ b/src/app/common/flyout/flyout.component.html @@ -1,13 +1,10 @@ -
+
diff --git a/src/app/common/flyout/flyout.component.ts b/src/app/common/flyout/flyout.component.ts index 6e68cd93..4253df29 100644 --- a/src/app/common/flyout/flyout.component.ts +++ b/src/app/common/flyout/flyout.component.ts @@ -1,12 +1,13 @@ import { NgClass } from '@angular/common'; import { Component, - ContentChild, ElementRef, - Input, Renderer2, - ViewChild, - inject + contentChild, + inject, + input, + signal, + viewChild } from '@angular/core'; @Component({ @@ -17,44 +18,41 @@ import { imports: [NgClass] }) export class FlyoutComponent { - @ViewChild('flyoutContentContainer') container?: ElementRef; - @ContentChild('flyoutContent') content?: ElementRef; + readonly #renderer = inject(Renderer2); - @Input() - label = ''; + readonly container = viewChild.required('flyoutContentContainer'); + readonly content = contentChild.required('flyoutContent'); - @Input() - placement: 'left' | 'right' | 'top' | 'bottom' = 'right'; + readonly label = input(''); + readonly placement = input<'left' | 'right' | 'top' | 'bottom'>('right'); - isOpen = false; - - private renderer = inject(Renderer2); + readonly isOpen = signal(false); toggle() { - if (this.content && this.container) { - if (this.placement === 'top' || this.placement === 'bottom') { - if (this.isOpen) { - this.renderer.setStyle(this.container.nativeElement, 'height', 0); + if (this.content() && this.container()) { + if (this.placement() === 'top' || this.placement() === 'bottom') { + if (this.isOpen()) { + this.#renderer.setStyle(this.container().nativeElement, 'height', 0); } else { - this.renderer.setStyle( - this.container.nativeElement, + this.#renderer.setStyle( + this.container().nativeElement, 'height', - `${this.content.nativeElement.clientHeight}px` + `${this.content().nativeElement.clientHeight}px` ); } } else { - if (this.isOpen) { - this.renderer.setStyle(this.container.nativeElement, 'width', 0); + if (this.isOpen()) { + this.#renderer.setStyle(this.container().nativeElement, 'width', 0); } else { - this.renderer.setStyle( - this.container.nativeElement, + this.#renderer.setStyle( + this.container().nativeElement, 'width', - `${this.content.nativeElement.clientWidth}px` + `${this.content().nativeElement.clientWidth}px` ); } } - this.isOpen = !this.isOpen; + this.isOpen.set(!this.isOpen()); } } } diff --git a/src/app/common/loading-overlay/loading-overlay.component.html b/src/app/common/loading-overlay/loading-overlay.component.html index 4701c220..7a1d0b93 100644 --- a/src/app/common/loading-overlay/loading-overlay.component.html +++ b/src/app/common/loading-overlay/loading-overlay.component.html @@ -1,15 +1,14 @@ -@if (isLoading) { +@if (isLoading()) {
- @if (isError) { - + @if (isError()) { + - + - } - @if (!isError) { + } @else {
- +
}
diff --git a/src/app/common/loading-overlay/loading-overlay.component.spec.ts b/src/app/common/loading-overlay/loading-overlay.component.spec.ts index 3be6967f..21be2f17 100644 --- a/src/app/common/loading-overlay/loading-overlay.component.spec.ts +++ b/src/app/common/loading-overlay/loading-overlay.component.spec.ts @@ -1,34 +1,34 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; import { LoadingOverlayComponent } from './loading-overlay.component'; describe('LoadingOverlayComponent', () => { let fixture: ComponentFixture; let rootHTMLElement: HTMLElement; - let component: LoadingOverlayComponent; beforeEach(() => { - const testbed = TestBed.configureTestingModule({ + fixture = TestBed.configureTestingModule({ imports: [LoadingOverlayComponent] - }); + }).createComponent(LoadingOverlayComponent); - fixture = testbed.createComponent(LoadingOverlayComponent); - component = fixture.componentInstance; rootHTMLElement = fixture.debugElement.nativeElement; fixture.detectChanges(); }); it('should display loading overlay and loading spinner', () => { - component.isLoading = true; + fixture.componentRef.setInput('isLoading', true); fixture.detectChanges(); + // expect(rootHTMLElement.innerHTML).toEqual(''); + // expect(fixture.debugElement.query(By.css('.overlay'))).toBeDefined(); expect(rootHTMLElement.getElementsByClassName('overlay').length).toEqual(1); expect(rootHTMLElement.getElementsByClassName('overlay-spinner').length).toEqual(1); expect(rootHTMLElement.getElementsByClassName('alert').length).toEqual(0); }); it('should display loading overlay and error message', () => { - component.isLoading = true; - component.isError = true; + fixture.componentRef.setInput('isLoading', true); + fixture.componentRef.setInput('isError', true); fixture.detectChanges(); expect(rootHTMLElement.getElementsByClassName('overlay').length).toEqual(1); expect(rootHTMLElement.getElementsByClassName('overlay-spinner').length).toEqual(0); @@ -36,7 +36,7 @@ describe('LoadingOverlayComponent', () => { }); it('should not display loading overlay', () => { - component.isLoading = false; + fixture.componentRef.setInput('isLoading', false); fixture.detectChanges(); expect(rootHTMLElement.getElementsByClassName('overlay').length).toEqual(0); expect(rootHTMLElement.getElementsByClassName('overlay-spinner').length).toEqual(0); diff --git a/src/app/common/loading-overlay/loading-overlay.component.ts b/src/app/common/loading-overlay/loading-overlay.component.ts index 1952d0b8..75ea7d2f 100644 --- a/src/app/common/loading-overlay/loading-overlay.component.ts +++ b/src/app/common/loading-overlay/loading-overlay.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, input, output } from '@angular/core'; import { LoadingSpinnerComponent } from '../loading-spinner/loading-spinner.component'; import { NotificationComponent } from '../notification/notification.component'; @@ -8,25 +8,14 @@ import { NotificationComponent } from '../notification/notification.component'; templateUrl: 'loading-overlay.component.html', styleUrls: ['loading-overlay.component.scss'], standalone: true, - imports: [NotificationComponent, LoadingSpinnerComponent] + imports: [NotificationComponent, LoadingSpinnerComponent], + changeDetection: ChangeDetectionStrategy.OnPush }) export class LoadingOverlayComponent { - @Input() - message = 'Loading...'; + readonly message = input('Loading...'); + readonly isLoading = input(false); + readonly isError = input(false); + readonly errorMessage = input(''); - @Input() - isLoading = false; - - @Input() - isError = false; - - @Input() - errorMessage = ''; - - @Output() - readonly retry = new EventEmitter(); - - handleRetry() { - this.retry.emit(true); - } + readonly retry = output(); } diff --git a/src/app/common/notification/notification.component.html b/src/app/common/notification/notification.component.html index b9a54755..0b179105 100644 --- a/src/app/common/notification/notification.component.html +++ b/src/app/common/notification/notification.component.html @@ -1,10 +1,10 @@ -
-
- {{ message }} +
+
+ {{ message() }}
- @if (showActions) { + @if (showActions() && actionTemplate()) {
- +
}
diff --git a/src/app/common/notification/notification.component.ts b/src/app/common/notification/notification.component.ts index eb0f1ec8..b9335b4a 100644 --- a/src/app/common/notification/notification.component.ts +++ b/src/app/common/notification/notification.component.ts @@ -1,26 +1,26 @@ import { NgTemplateOutlet } from '@angular/common'; -import { Component, ContentChild, Input, TemplateRef, booleanAttribute } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + TemplateRef, + booleanAttribute, + contentChild, + input +} from '@angular/core'; @Component({ selector: 'notification', templateUrl: 'notification.component.html', styleUrls: ['notification.component.scss'], standalone: true, - imports: [NgTemplateOutlet] + imports: [NgTemplateOutlet], + changeDetection: ChangeDetectionStrategy.OnPush }) export class NotificationComponent { - @ContentChild('notificationActions', { static: true }) actionTemplate: TemplateRef | null = - null; + readonly actionTemplate = contentChild>('notificationActions'); - @Input() - notificationType: 'info' | 'success' | 'warning' | 'danger' = 'info'; - - @Input() - message = ''; - - @Input({ transform: booleanAttribute }) - showActions = false; - - @Input({ transform: booleanAttribute }) - small = false; + readonly notificationType = input<'info' | 'success' | 'warning' | 'danger'>('info'); + readonly message = input(''); + readonly showActions = input(false, { transform: booleanAttribute }); + readonly small = input(false, { transform: booleanAttribute }); } diff --git a/src/app/common/search-input/search-input.component.html b/src/app/common/search-input/search-input.component.html index 8dce7efb..d4cecefb 100644 --- a/src/app/common/search-input/search-input.component.html +++ b/src/app/common/search-input/search-input.component.html @@ -3,20 +3,20 @@ class="form-control" type="text" [(ngModel)]="search" - [placeholder]="placeholder" + [placeholder]="placeholder()" (input)="onInput()" (keyup)="onKeyup()" /> - @if (search.length === 0) { + @if (search().length === 0) { } @else { }
-@if (!disableMinCountMessage && showMinCountMessage) { +@if (!disableMinCountMessage() && showMinCountMessage()) {
- Searches require a minimum of {{ minSearchCharacterCount }} characters. + Searches require a minimum of {{ minSearchCharacterCount() }} characters.
} diff --git a/src/app/common/search-input/search-input.component.spec.ts b/src/app/common/search-input/search-input.component.spec.ts index 292313ef..adc56a0f 100644 --- a/src/app/common/search-input/search-input.component.spec.ts +++ b/src/app/common/search-input/search-input.component.spec.ts @@ -41,7 +41,7 @@ describe('SearchInputComponent', () => { })); it('should not apply the search if keyup is never triggered', fakeAsync(() => { - componentInstance.preferInputEvent = false; + fixture.componentRef.setInput('preferInputEvent', false); inputElement.nativeElement.value = 'search value'; fixture.detectChanges(); // need to trigger input event here so ngModel will set `search` property correctly @@ -53,8 +53,8 @@ describe('SearchInputComponent', () => { expect(componentInstance.applySearch.emit).toHaveBeenCalledTimes(0); })); - it('should apply search if preferInputEvent is true and an input event is trigered', fakeAsync(() => { - componentInstance.preferInputEvent = true; + it('should apply search if preferInputEvent is true and an input event is triggered', fakeAsync(() => { + fixture.componentRef.setInput('preferInputEvent', true); inputElement.nativeElement.value = 'search value'; fixture.detectChanges(); @@ -67,7 +67,7 @@ describe('SearchInputComponent', () => { })); it('should not apply search if minSearchCharacterCount is specified not adhered to', fakeAsync(() => { - componentInstance.minSearchCharacterCount = 3; + fixture.componentRef.setInput('minSearchCharacterCount', 3); inputElement.nativeElement.value = 'se'; fixture.detectChanges(); @@ -80,7 +80,7 @@ describe('SearchInputComponent', () => { })); it('should show warning message if minSearchCharacterCount is specified and not adhered to', fakeAsync(() => { - componentInstance.minSearchCharacterCount = 3; + fixture.componentRef.setInput('minSearchCharacterCount', 3); inputElement.nativeElement.value = 'se'; fixture.detectChanges(); @@ -99,7 +99,7 @@ describe('SearchInputComponent', () => { })); it('should not show warning message if minSearchCharacterCount is specified and adhered to', fakeAsync(() => { - componentInstance.minSearchCharacterCount = 3; + fixture.componentRef.setInput('minSearchCharacterCount', 3); inputElement.nativeElement.value = 'sea'; fixture.detectChanges(); @@ -114,8 +114,8 @@ describe('SearchInputComponent', () => { })); it('should not show warning message if disableMinCountMessage is set when minSearchCharacterCount is adhered to', fakeAsync(() => { - componentInstance.minSearchCharacterCount = 3; - componentInstance.disableMinCountMessage = true; + fixture.componentRef.setInput('minSearchCharacterCount', 3); + fixture.componentRef.setInput('disableMinCountMessage', true); inputElement.nativeElement.value = 'sea'; fixture.detectChanges(); @@ -130,8 +130,8 @@ describe('SearchInputComponent', () => { })); it('should not show warning message if disableMinCountMessage is set when minSearchCharacterCount is not adhered to', fakeAsync(() => { - componentInstance.minSearchCharacterCount = 3; - componentInstance.disableMinCountMessage = true; + fixture.componentRef.setInput('minSearchCharacterCount', 3); + fixture.componentRef.setInput('disableMinCountMessage', true); inputElement.nativeElement.value = 'se'; fixture.detectChanges(); @@ -176,7 +176,7 @@ describe('SearchInputComponent', () => { fixture.detectChanges(); expect(clearSearchSpy).toHaveBeenCalledTimes(1); - expect(componentInstance.search).toBe(''); + expect(componentInstance.search()).toBe(''); expect(componentInstance.applySearch.emit).toHaveBeenCalledTimes(1); expect(componentInstance.applySearch.emit).toHaveBeenCalledWith(''); diff --git a/src/app/common/search-input/search-input.component.ts b/src/app/common/search-input/search-input.component.ts index a4b2d43a..fc95c962 100644 --- a/src/app/common/search-input/search-input.component.ts +++ b/src/app/common/search-input/search-input.component.ts @@ -1,5 +1,13 @@ import { animate, style, transition, trigger } from '@angular/animations'; -import { Component, EventEmitter, Input, Output, booleanAttribute } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + booleanAttribute, + input, + model, + output, + signal +} from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; @@ -23,69 +31,67 @@ import { debounceTime } from 'rxjs/operators'; ]) ], standalone: true, - imports: [FormsModule] + imports: [FormsModule], + changeDetection: ChangeDetectionStrategy.OnPush }) export class SearchInputComponent { - @Input() - placeholder = 'Search...'; + search = model(''); - @Input() - search = ''; - - @Output() - readonly applySearch: EventEmitter = new EventEmitter(); + readonly placeholder = input('Search...'); /** * If true, searches will be made on `input` events, otherwise searches will be made on `keyup` events */ - @Input({ transform: booleanAttribute }) - preferInputEvent = true; + readonly preferInputEvent = input(true, { transform: booleanAttribute }); /** * Specifies a minimum character count required to search. * In the event the number of characters is between 0 and the minimum, a warning message is shown beneath the search bar */ - @Input() - minSearchCharacterCount = 0; + readonly minSearchCharacterCount = input(0); /** * When set to true, the minimum search character * message count will not be displayed, even if the search * value is less than the minimum number of characters. */ - @Input({ transform: booleanAttribute }) - disableMinCountMessage = false; + readonly disableMinCountMessage = input(false, { transform: booleanAttribute }); - searchInput$ = new Subject(); + readonly showMinCountMessage = signal(false); - showMinCountMessage = false; + readonly applySearch = output(); + + searchInput$ = new Subject(); constructor() { this.searchInput$.pipe(debounceTime(350), takeUntilDestroyed()).subscribe(() => { - if (this.search.length === 0 || this.search.length >= this.minSearchCharacterCount) { - this.showMinCountMessage = false; - this.applySearch.emit(this.search); + if ( + this.search().length === 0 || + this.search().length >= this.minSearchCharacterCount() + ) { + this.showMinCountMessage.set(false); + this.applySearch.emit(this.search()); } else { - this.showMinCountMessage = true; + this.showMinCountMessage.set(true); } }); } onKeyup() { - if (!this.preferInputEvent) { + if (!this.preferInputEvent()) { this.searchInput$.next(); } } onInput() { - if (this.preferInputEvent) { + if (this.preferInputEvent()) { this.searchInput$.next(); } } clearSearch(event?: MouseEvent) { - this.search = ''; - this.applySearch.emit(this.search); + this.search.set(''); + this.applySearch.emit(this.search()); if (event) { event.stopPropagation(); } diff --git a/src/app/common/table/column-chooser/column-chooser.component.html b/src/app/common/table/column-chooser/column-chooser.component.html index cba69bd7..f5489ef7 100644 --- a/src/app/common/table/column-chooser/column-chooser.component.html +++ b/src/app/common/table/column-chooser/column-chooser.component.html @@ -18,9 +18,9 @@ cdkDrag cdkDragBoundary=".column-drag-boundary" [cdkDragData]="column" - [cdkDragDisabled]="sortingDisabled" + [cdkDragDisabled]="sortingDisabled()" > - @if (!sortingDisabled) { + @if (!sortingDisabled()) { } (); - @Input({ transform: booleanAttribute }) - sortingDisabled = false; + readonly sortingDisabled = input(false, { transform: booleanAttribute }); - @Output() readonly columnsChange: EventEmitter = new EventEmitter(); + readonly columnsChange = output(); private storage = new LocalStorageService(); @@ -71,13 +78,13 @@ export class ColumnChooserComponent implements OnInit { } _loadState() { - if (this.storageKey) { + if (this.storageKey()) { const columnsOrder: string[] = this.storage.getValue( - `${this.storageKey}-columns-order`, + `${this.storageKey()}-columns-order`, [] ); const columnsSelected: string[] = this.storage.getValue( - `${this.storageKey}-columns-selected`, + `${this.storageKey()}-columns-selected`, [] ); @@ -114,14 +121,14 @@ export class ColumnChooserComponent implements OnInit { } _saveState() { - if (this.storageKey) { + if (this.storageKey()) { this.storage.setValue( - `${this.storageKey}-columns-selected`, + `${this.storageKey()}-columns-selected`, this._columns.filter((c) => c.selected).map((c) => c.key) ); this.storage.setValue( - `${this.storageKey}-columns-order`, + `${this.storageKey()}-columns-order`, this._columns.map((c) => c.key) ); } diff --git a/src/app/common/table/columns/ago-date/ago-date-column.component.html b/src/app/common/table/columns/ago-date/ago-date-column.component.html index 6411cd97..02cc7bfa 100644 --- a/src/app/common/table/columns/ago-date/ago-date-column.component.html +++ b/src/app/common/table/columns/ago-date/ago-date-column.component.html @@ -12,8 +12,8 @@ -
- {{ obj[name] | agoDate: hideAgo }} +
+ {{ obj[name] | agoDate: hideAgo() }}
diff --git a/src/app/common/table/columns/ago-date/ago-date-column.component.ts b/src/app/common/table/columns/ago-date/ago-date-column.component.ts index 025f07dd..f8c7cdc9 100644 --- a/src/app/common/table/columns/ago-date/ago-date-column.component.ts +++ b/src/app/common/table/columns/ago-date/ago-date-column.component.ts @@ -1,6 +1,6 @@ import { CdkTableModule } from '@angular/cdk/table'; import { CommonModule } from '@angular/common'; -import { Component, Input, booleanAttribute } from '@angular/core'; +import { Component, booleanAttribute, input } from '@angular/core'; import { TooltipModule } from 'ngx-bootstrap/tooltip'; @@ -24,6 +24,5 @@ import { DateColumnComponent } from '../date/date-column.component'; styleUrls: ['./ago-date-column.component.scss'] }) export class AgoDateColumnComponent extends DateColumnComponent { - @Input({ transform: booleanAttribute }) - hideAgo = false; + readonly hideAgo = input(false, { transform: booleanAttribute }); } diff --git a/src/app/common/table/columns/date/date-column.component.html b/src/app/common/table/columns/date/date-column.component.html index d84793a2..222226a6 100644 --- a/src/app/common/table/columns/date/date-column.component.html +++ b/src/app/common/table/columns/date/date-column.component.html @@ -12,6 +12,6 @@ - {{ obj[name] | utcDate: format }} + {{ obj[name] | utcDate: format() }} diff --git a/src/app/common/table/columns/date/date-column.component.ts b/src/app/common/table/columns/date/date-column.component.ts index dc02904e..f881a5ff 100644 --- a/src/app/common/table/columns/date/date-column.component.ts +++ b/src/app/common/table/columns/date/date-column.component.ts @@ -1,6 +1,6 @@ import { CdkTableModule } from '@angular/cdk/table'; import { CommonModule } from '@angular/common'; -import { Component, Input } from '@angular/core'; +import { Component, input } from '@angular/core'; import { UtcDatePipe } from '../../../pipes/utc-date-pipe/utc-date.pipe'; import { AsySortHeaderComponent } from '../../sort/asy-sort-header/asy-sort-header.component'; @@ -14,6 +14,5 @@ import { AsyAbstractValueColumnComponent } from '../asy-abstract-value-column.co styleUrls: ['./date-column.component.scss'] }) export class DateColumnComponent extends AsyAbstractValueColumnComponent { - @Input() - format?: string; + readonly format = input(); } diff --git a/src/app/common/table/columns/expander/asy-expander-column.component.ts b/src/app/common/table/columns/expander/asy-expander-column.component.ts index 073354b4..a2b84a22 100644 --- a/src/app/common/table/columns/expander/asy-expander-column.component.ts +++ b/src/app/common/table/columns/expander/asy-expander-column.component.ts @@ -1,6 +1,13 @@ import { SelectionModel } from '@angular/cdk/collections'; import { CdkTableModule } from '@angular/cdk/table'; -import { ChangeDetectionStrategy, Component, Input, OnInit, booleanAttribute } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + Input, + OnInit, + booleanAttribute, + input +} from '@angular/core'; import { AsyAbstractColumnComponent } from '../asy-abstract-column.component'; @@ -16,10 +23,9 @@ export class AsyExpanderColumnComponent extends AsyAbstractColumnComponent implements OnInit { - @Input({ transform: booleanAttribute }) - multi = true; + #selectionModel: SelectionModel; - private selectionModel: SelectionModel; + readonly multi = input(true, { transform: booleanAttribute }); @Input() isExpandable: (index: number, rowData: T) => boolean = () => true; @@ -35,31 +41,31 @@ export class AsyExpanderColumnComponent override ngOnInit(): void { super.ngOnInit(); - this.selectionModel = new SelectionModel(this.multi); + this.#selectionModel = new SelectionModel(this.multi()); } expand(...trackByValues: TB[]) { - this.selectionModel.select(...trackByValues); + this.#selectionModel.select(...trackByValues); } toggle(index: number, result: T) { - this.selectionModel.toggle(this.trackBy(index, result)); + this.#selectionModel.toggle(this.trackBy(index, result)); } isExpanded(index: number, result: T) { - return this.selectionModel.isSelected(this.trackBy(index, result)); + return this.#selectionModel.isSelected(this.trackBy(index, result)); } get expanded() { - return this.selectionModel.selected; + return this.#selectionModel.selected; } // eslint-disable-next-line rxjs/finnish get changed() { - return this.selectionModel.changed; + return this.#selectionModel.changed; } hasExpanded() { - return this.selectionModel.hasValue(); + return this.#selectionModel.hasValue(); } } diff --git a/src/app/common/table/columns/selection/asy-selection-column.component.html b/src/app/common/table/columns/selection/asy-selection-column.component.html index 22f39b58..4a75fd36 100644 --- a/src/app/common/table/columns/selection/asy-selection-column.component.html +++ b/src/app/common/table/columns/selection/asy-selection-column.component.html @@ -1,6 +1,6 @@ - @if (enableSelectAll && multi) { + @if (enableSelectAll() && multi()) {
extends AsyAbstractColumnComponent implements AfterViewInit, OnInit { - @Input({ transform: booleanAttribute }) - enableSelectAll = true; + #destroyRef = inject(DestroyRef); + #dataSource: AsyTableDataSource; + #selectionModel: SelectionModel; - @Input({ transform: booleanAttribute }) - clearOnLoad = true; - - @Input({ transform: booleanAttribute }) - multi = true; - - _isAllSelected$: Observable; - - _isMultiTemplateDataRows = false; - - private dataSource: AsyTableDataSource; - - private selectionModel: SelectionModel; + readonly enableSelectAll = input(true, { transform: booleanAttribute }); + readonly clearOnLoad = input(true, { transform: booleanAttribute }); + readonly multi = input(true, { transform: booleanAttribute }); @Input() isSelectable: (index: number, rowData: T) => boolean = () => true; @@ -54,7 +46,9 @@ export class AsySelectionColumnComponent @Input() trackBy: (index: number, rowData: T) => TB = (index, rowData) => rowData as unknown as TB; - private destroyRef = inject(DestroyRef); + _isAllSelected$: Observable; + + _isMultiTemplateDataRows = false; constructor() { super(); @@ -64,10 +58,10 @@ export class AsySelectionColumnComponent override ngOnInit(): void { super.ngOnInit(); - this.selectionModel = new SelectionModel(this.multi); + this.#selectionModel = new SelectionModel(this.multi()); if (this._isAsyTableDataSource(this._table.dataSource)) { - this.dataSource = this._table.dataSource; + this.#dataSource = this._table.dataSource; this._isMultiTemplateDataRows = this._table.multiTemplateDataRows; } else { throw Error( @@ -77,50 +71,50 @@ export class AsySelectionColumnComponent } ngAfterViewInit(): void { - if (this.clearOnLoad) { - this.dataSource.pagingResults$ - .pipe(takeUntilDestroyed(this.destroyRef)) + if (this.clearOnLoad()) { + this.#dataSource.pagingResults$ + .pipe(takeUntilDestroyed(this.#destroyRef)) .subscribe(() => { - this.selectionModel.clear(); + this.#selectionModel.clear(); }); } - this._isAllSelected$ = this.selectionModel.changed.pipe( + this._isAllSelected$ = this.#selectionModel.changed.pipe( map(() => this._isAllSelected()), - takeUntilDestroyed(this.destroyRef) + takeUntilDestroyed(this.#destroyRef) ); } select(...trackByValues: TB[]) { - this.selectionModel.select(...trackByValues); + this.#selectionModel.select(...trackByValues); } toggle(index: number, result: T) { - this.selectionModel.toggle(this.trackBy(index, result)); + this.#selectionModel.toggle(this.trackBy(index, result)); } isSelected(index: number, result: T) { - return this.selectionModel.isSelected(this.trackBy(index, result)); + return this.#selectionModel.isSelected(this.trackBy(index, result)); } get selected() { - return this.selectionModel.selected; + return this.#selectionModel.selected; } // eslint-disable-next-line rxjs/finnish get changed() { - return this.selectionModel.changed; + return this.#selectionModel.changed; } hasSelection() { - return this.selectionModel.hasValue(); + return this.#selectionModel.hasValue(); } _isAllSelected() { return ( - this.selectionModel.selected.length > 0 && - this.selectionModel.selected.length === - this.dataSource.pagingResults$.value.elements.filter((result, index) => + this.#selectionModel.selected.length > 0 && + this.#selectionModel.selected.length === + this.#dataSource.pagingResults$.value.elements.filter((result, index) => this.isSelectable(index, result) ).length ); @@ -128,9 +122,9 @@ export class AsySelectionColumnComponent _toggleAll() { if (this._isAllSelected()) { - this.selectionModel.clear(); + this.#selectionModel.clear(); } else { - this.dataSource.pagingResults$.value.elements.forEach((result, index) => { + this.#dataSource.pagingResults$.value.elements.forEach((result, index) => { if (this.isSelectable(index, result)) { this.select(this.trackBy(index, result)); } diff --git a/src/app/common/table/filter/asy-abstract-header-filter.component.ts b/src/app/common/table/filter/asy-abstract-header-filter.component.ts index 3c4688db..19f5568b 100644 --- a/src/app/common/table/filter/asy-abstract-header-filter.component.ts +++ b/src/app/common/table/filter/asy-abstract-header-filter.component.ts @@ -6,7 +6,8 @@ import { Input, OnDestroy, OnInit, - inject + inject, + signal } from '@angular/core'; import isEmpty from 'lodash/isEmpty'; @@ -23,11 +24,7 @@ export interface AsyFilterHeaderColumnDef { export abstract class AsyAbstractHeaderFilterComponent implements AsyFilterable, AfterViewInit, OnDestroy, OnInit { - isFiltered = false; - - isOpen = false; - - private storage = new SessionStorageService(); + #storage = new SessionStorageService(); /** * ID of this sort header. If used within the context of a CdkColumnDef, this will default to @@ -41,13 +38,16 @@ export abstract class AsyAbstractHeaderFilterComponent protected changeDetectorRef = inject(ChangeDetectorRef); + readonly isFiltered = signal(false); + readonly isOpen = signal(false); + + protected constructor(public _columnDef: AsyFilterHeaderColumnDef) {} + abstract _buildFilter(): any; abstract _buildState(): any; abstract _clearState(): void; abstract _restoreState(state: any): void; - protected constructor(public _columnDef: AsyFilterHeaderColumnDef) {} - ngOnInit(): void { if (!this._filter) { throw Error( @@ -68,32 +68,36 @@ export abstract class AsyAbstractHeaderFilterComponent this._filter?.deregister(this); } + toggle() { + this.isOpen.set(!this.isOpen()); + } + onFilterChange() { const filter = this._buildFilter(); - this.isFiltered = !isEmpty(filter); + this.isFiltered.set(!isEmpty(filter)); this._filter.filter(this.id, filter); this.changeDetectorRef.markForCheck(); this.saveState(); } clearFilter() { - this.isFiltered = false; + this.isFiltered.set(false); this._clearState(); this.onFilterChange(); } loadState(): any { - const storageKey = this._filter.dataSource.storageKey; + const storageKey = this._filter.dataSource().storageKey; if (storageKey) { - return this.storage.getValue(`${storageKey}-${this.id}-filter`); + return this.#storage.getValue(`${storageKey}-${this.id}-filter`); } return undefined; } saveState() { - const storageKey = this._filter.dataSource.storageKey; + const storageKey = this._filter.dataSource().storageKey; if (storageKey) { - this.storage.setValue(`${storageKey}-${this.id}-filter`, this._buildState()); + this.#storage.setValue(`${storageKey}-${this.id}-filter`, this._buildState()); } } diff --git a/src/app/common/table/filter/asy-filter.directive.ts b/src/app/common/table/filter/asy-filter.directive.ts index 7ea606f1..5f954778 100644 --- a/src/app/common/table/filter/asy-filter.directive.ts +++ b/src/app/common/table/filter/asy-filter.directive.ts @@ -1,4 +1,4 @@ -import { Directive, Input } from '@angular/core'; +import { Directive, Input, input } from '@angular/core'; import isEmpty from 'lodash/isEmpty'; @@ -19,14 +19,13 @@ export interface AsyFilterable { standalone: true }) export class AsyFilterDirective { + dataSource = input.required>(); + /** Collection of all registered filterables that this directive manages. */ filterables = new Map(); filters = new Map(); - @Input({ required: true }) - dataSource: AsyTableDataSource; - /** * Register function to be used by the contained AsyFilterables. Adds the AsyFilterable to the * collection of AsyFilterables. @@ -53,7 +52,7 @@ export class AsyFilterDirective { filter(id: string, query: any): void { this.filters.set(id, query); - this.dataSource.filter(this._buildFilter()); + this.dataSource().filter(this._buildFilter()); } clearFilter(): void { diff --git a/src/app/common/table/filter/asy-header-date-filter/asy-header-date-filter.component.html b/src/app/common/table/filter/asy-header-date-filter/asy-header-date-filter.component.html index c6f4648f..43eecb3a 100644 --- a/src/app/common/table/filter/asy-header-date-filter/asy-header-date-filter.component.html +++ b/src/app/common/table/filter/asy-header-date-filter/asy-header-date-filter.component.html @@ -2,29 +2,29 @@ class="dropdown-toggle dropdown-toggle-hide-caret" cdkOverlayOrigin #trigger - (click)="isOpen = !isOpen" + (click)="toggle()" >