Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: more updates to use signals #385

Merged
merged 1 commit into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/app/common/breadcrumb/breadcrumb.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,11 +28,8 @@ export class BreadcrumbComponent {

breadcrumbs: Breadcrumb[] = [];

private route = inject(ActivatedRoute);
private router = inject(Router);

constructor() {
const navEnd$: Observable<Event> = this.router.events.pipe(
const navEnd$: Observable<Event> = this.#router.events.pipe(
filter((event) => event instanceof NavigationEnd)
);
merge(navEnd$, this.homeBreadcrumbChanged$)
Expand All @@ -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
)
);
Expand Down
7 changes: 2 additions & 5 deletions src/app/common/flyout/flyout.component.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
<section
class="flyout flyout-{{ placement }} d-flex text-nowrap"
[ngClass]="{ 'flyout-open': isOpen }"
>
<section class="flyout flyout-{{ placement() }} d-flex text-nowrap" [class.flyout-open]="isOpen()">
<button
class="btn btn-primary flyout-btn d-flex align-items-center"
data-inline="true"
(click)="toggle()"
>
{{ label }}
{{ label() }}
<span class="fa-solid fa-lg fa-angle-down ms-2" data-inline="true"></span>
</button>

Expand Down
50 changes: 24 additions & 26 deletions src/app/common/flyout/flyout.component.ts
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -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<ElementRef>('flyoutContentContainer');
readonly content = contentChild.required<ElementRef>('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());
}
}
}
13 changes: 6 additions & 7 deletions src/app/common/loading-overlay/loading-overlay.component.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
@if (isLoading) {
@if (isLoading()) {
<div class="overlay">
@if (isError) {
<notification notificationType="danger" showActions [message]="errorMessage">
@if (isError()) {
<notification notificationType="danger" showActions [message]="errorMessage()">
<ng-template #notificationActions>
<button class="btn btn-primary" (click)="handleRetry()">Retry</button>
<button class="btn btn-primary" (click)="retry.emit()">Retry</button>
</ng-template>
</notification>
}
@if (!isError) {
} @else {
<div class="overlay-spinner">
<loading-spinner [message]="message"> </loading-spinner>
<loading-spinner [message]="message()"> </loading-spinner>
</div>
}
</div>
Expand Down
18 changes: 9 additions & 9 deletions src/app/common/loading-overlay/loading-overlay.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';

Check warning on line 2 in src/app/common/loading-overlay/loading-overlay.component.spec.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

'By' is defined but never used

Check warning on line 2 in src/app/common/loading-overlay/loading-overlay.component.spec.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

'By' is defined but never used

Check warning on line 2 in src/app/common/loading-overlay/loading-overlay.component.spec.ts

View workflow job for this annotation

GitHub Actions / build (22.x)

'By' is defined but never used

import { LoadingOverlayComponent } from './loading-overlay.component';

describe('LoadingOverlayComponent', () => {
let fixture: ComponentFixture<LoadingOverlayComponent>;
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);
expect(rootHTMLElement.getElementsByClassName('alert').length).toEqual(1);
});

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);
Expand Down
27 changes: 8 additions & 19 deletions src/app/common/loading-overlay/loading-overlay.component.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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();
}
10 changes: 5 additions & 5 deletions src/app/common/notification/notification.component.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<div class="alert alert-{{ notificationType }} d-flex" [class.small]="small">
<div class="flex-grow-1" [class.action-text]="showActions">
{{ message }}
<div class="alert alert-{{ notificationType() }} d-flex" [class.small]="small()">
<div class="flex-grow-1" [class.action-text]="showActions()">
{{ message() }}
</div>
@if (showActions) {
@if (showActions() && actionTemplate()) {
<div class="table-actions d-flex">
<ng-container *ngTemplateOutlet="actionTemplate"></ng-container>
<ng-container *ngTemplateOutlet="actionTemplate() ?? null"></ng-container>
</div>
}
</div>
30 changes: 15 additions & 15 deletions src/app/common/notification/notification.component.ts
Original file line number Diff line number Diff line change
@@ -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<any> | null =
null;
readonly actionTemplate = contentChild<TemplateRef<any>>('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 });
}
8 changes: 4 additions & 4 deletions src/app/common/search-input/search-input.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
<span class="icon fa-solid fa-search"></span>
} @else {
<span class="icon fa-solid fa-times" (click)="clearSearch($event)"></span>
}
</div>
@if (!disableMinCountMessage && showMinCountMessage) {
@if (!disableMinCountMessage() && showMinCountMessage()) {
<div class="text-muted pt-2" [@minCount]>
<div class="pt-2">
Searches require a minimum of {{ minSearchCharacterCount }} characters.
Searches require a minimum of {{ minSearchCharacterCount() }} characters.
</div>
</div>
}
Loading