Skip to content

Commit 5592fff

Browse files
committed
refactor(toast): signal inputs, host bindings, cleanup
1 parent 80e108b commit 5592fff

File tree

7 files changed

+92
-88
lines changed

7 files changed

+92
-88
lines changed
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import { Component, HostBinding, inject } from '@angular/core';
1+
import { Component, inject } from '@angular/core';
22
import { ToastComponent } from '../toast/toast.component';
33

44
@Component({
55
selector: 'c-toast-body',
66
template: '<ng-content />',
77
styleUrls: ['./toast-body.component.scss'],
8-
exportAs: 'cToastBody'
8+
exportAs: 'cToastBody',
9+
host: {
10+
class: 'toast-body',
11+
}
912
})
1013
export class ToastBodyComponent {
11-
toast? = inject(ToastComponent, { optional: true });
14+
readonly toast? = inject(ToastComponent, { optional: true });
1215

13-
@HostBinding('class.toast-body') toastBodyClass = true;
1416
}
Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1-
import { Directive, HostListener, inject, Input } from '@angular/core';
1+
import { Directive, inject, input } from '@angular/core';
22
import { ToasterService } from './toaster/toaster.service';
3+
import type { ToastComponent } from './toast/toast.component';
34

45
@Directive({
56
selector: '[cToastClose]',
6-
exportAs: 'cToastClose'
7+
exportAs: 'cToastClose',
8+
host: {
9+
'(click)': 'toggleOpen($event)'
10+
}
711
})
812
export class ToastCloseDirective {
913
readonly #toasterService = inject(ToasterService);
1014

11-
@Input('cToastClose') toast: any;
15+
readonly cToastClose = input<ToastComponent>();
1216

13-
@HostListener('click', ['$event'])
14-
toggleOpen($event: any): void {
17+
toggleOpen($event: MouseEvent): void {
1518
$event.preventDefault();
16-
this.#toasterService.setState({ show: false, toast: this.toast });
19+
this.#toasterService.setState({ show: false, toast: this.cToastClose() });
1720
}
1821
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<ng-container>
22
<ng-content />
3-
@if (closeButton) {
4-
<button [cToastClose]="toast" [style]="{outline: 0}" aria-label="close" cButtonClose class="ms-auto"></button>
3+
@if (closeButton()) {
4+
<button [cToastClose]="toast()" [style]="{outline: 0}" aria-label="close" cButtonClose class="ms-auto"></button>
55
}
66
</ng-container>
Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, HostBinding, inject, Input } from '@angular/core';
1+
import { Component, inject, input, signal } from '@angular/core';
22

33
import { ButtonCloseDirective } from '../../button';
44
import { ToastComponent } from '../toast/toast.component';
@@ -8,16 +8,19 @@ import { ToastCloseDirective } from '../toast-close.directive';
88
selector: 'c-toast-header',
99
templateUrl: './toast-header.component.html',
1010
exportAs: 'cToastHeader',
11-
imports: [ToastCloseDirective, ButtonCloseDirective]
11+
imports: [ToastCloseDirective, ButtonCloseDirective],
12+
host: {
13+
class: 'toast-header'
14+
}
1215
})
1316
export class ToastHeaderComponent {
14-
toast? = inject(ToastComponent, { optional: true });
17+
readonly #toast = inject(ToastComponent, { optional: true });
18+
19+
readonly toast = signal(this.#toast ?? undefined);
1520

1621
/**
1722
* Add close button to a toast header
18-
* @type boolean
23+
* @return boolean
1924
*/
20-
@Input() closeButton = true;
21-
22-
@HostBinding('class.toast-header') toastHeaderClass = true;
25+
readonly closeButton = input(true);
2326
}

projects/coreui-angular/src/lib/toast/toast/toast.component.ts

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ import {
22
booleanAttribute,
33
ChangeDetectorRef,
44
Component,
5+
computed,
6+
effect,
57
ElementRef,
6-
HostBinding,
7-
HostListener,
88
inject,
9-
Input,
109
input,
1110
numberAttribute,
1211
OnDestroy,
@@ -47,47 +46,58 @@ type AnimateType = 'hide' | 'show';
4746
})
4847
])
4948
],
50-
host: { class: 'toast show' }
49+
host: {
50+
class: 'toast show',
51+
'[class]': 'hostClasses()',
52+
'(mouseover)': 'clearTimer()',
53+
'(mouseout)': 'setTimer()',
54+
'[@fadeInOut]': 'animateType',
55+
'[@.disabled]': 'animationDisabled()'
56+
}
5157
})
5258
export class ToastComponent implements OnInit, OnDestroy {
59+
readonly changeDetectorRef = inject(ChangeDetectorRef);
5360
readonly hostElement = inject(ElementRef);
5461
readonly renderer = inject(Renderer2);
5562
readonly toasterService = inject(ToasterService);
56-
readonly changeDetectorRef = inject(ChangeDetectorRef);
5763

5864
readonly dynamic = input<boolean>();
5965
readonly placement = input<TToasterPlacement>();
6066

6167
/**
6268
* Auto hide the toast.
63-
* @type boolean
69+
* @return boolean
6470
*/
6571
readonly autohide = input(true);
6672

6773
/**
6874
* Sets the color context of the component to one of CoreUI’s themed colors.
69-
* @type Colors
75+
* @return Colors
7076
*/
7177
readonly color = input<Colors>('');
7278

7379
/**
7480
* Delay hiding the toast (ms).
75-
* @type number
81+
* @return number
7682
*/
7783
readonly delay = input(5000, { transform: numberAttribute });
7884

7985
/**
8086
* Apply fade transition to the toast.
81-
* @type boolean
87+
* @return boolean
8288
*/
8389
readonly fade = input(true);
8490

8591
/**
8692
* Toggle the visibility of component.
87-
* @type boolean
93+
* @return boolean
8894
*/
95+
readonly visibleInput = input(false, { transform: booleanAttribute, alias: 'visible' });
96+
97+
readonly #visibleInputEffect = effect(() => {
98+
this.visible = this.visibleInput();
99+
});
89100

90-
@Input({ transform: booleanAttribute })
91101
set visible(value: boolean) {
92102
const newValue = value;
93103
if (this.#visible !== newValue) {
@@ -111,13 +121,13 @@ export class ToastComponent implements OnInit, OnDestroy {
111121

112122
/**
113123
* Event emitted on visibility change. [docs]
114-
* @type <boolean>
124+
* @return <boolean>
115125
*/
116126
readonly visibleChange = output<boolean>();
117127

118128
/**
119129
* Event emitted on timer tick. [docs]
120-
* @type number
130+
* @return number
121131
*/
122132
readonly timer = output<number>();
123133

@@ -137,33 +147,23 @@ export class ToastComponent implements OnInit, OnDestroy {
137147
this.changeDetectorRef.markForCheck();
138148
}
139149

140-
@HostBinding('@.disabled')
141-
get animationDisabled(): boolean {
150+
readonly animationDisabled = computed(() => {
142151
return !this.fade();
143-
}
152+
});
144153

145-
@HostBinding('@fadeInOut')
146154
get animateType(): AnimateType {
147155
return this.visible ? 'show' : 'hide';
148156
}
149157

150-
@HostListener('mouseover') onMouseOver(): void {
151-
this.clearTimer();
152-
}
153-
154-
@HostListener('mouseout') onMouseOut(): void {
155-
this.setTimer();
156-
}
157-
158-
@HostBinding('class')
159-
get hostClasses(): any {
158+
readonly hostClasses = computed(() => {
159+
const color = this.color();
160160
return {
161161
toast: true,
162162
show: true,
163-
[`bg-${this.color()}`]: !!this.color(),
164-
'border-0': !!this.color()
165-
};
166-
}
163+
[`bg-${color}`]: !!color,
164+
'border-0': !!color
165+
} as Record<string, boolean>;
166+
});
167167

168168
ngOnInit(): void {
169169
if (this.visible) {

projects/coreui-angular/src/lib/toast/toaster/toaster.component.ts

Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
import {
2-
AfterContentChecked,
32
Component,
43
ComponentRef,
5-
ContentChildren,
4+
computed,
5+
contentChildren,
66
DestroyRef,
77
ElementRef,
8-
HostBinding,
98
inject,
109
Injector,
11-
Input,
10+
input,
1211
NgModuleRef,
1312
OnInit,
14-
QueryList,
1513
Renderer2,
16-
ViewChild,
14+
viewChild,
1715
ViewContainerRef
1816
} from '@angular/core';
1917
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@@ -53,59 +51,58 @@ export type TToasterPlacement =
5351
templateUrl: './toaster.component.html',
5452
exportAs: 'cToaster',
5553
imports: [ToasterHostDirective],
56-
host: { class: 'toaster toast-container' }
54+
host: {
55+
class: 'toaster toast-container',
56+
'[class]': 'hostClasses()'
57+
}
5758
})
58-
export class ToasterComponent implements OnInit, AfterContentChecked {
59+
export class ToasterComponent implements OnInit {
5960
readonly #hostElement = inject(ElementRef);
6061
readonly #renderer = inject(Renderer2);
6162
readonly #toasterService = inject(ToasterService);
6263
readonly #destroyRef = inject(DestroyRef);
6364

6465
placements = Object.values(ToasterPlacement);
65-
toasts!: QueryList<ViewContainerRef>;
66-
toastsDynamic: any[] = [];
66+
toastsDynamic: ComponentRef<any>[] = [];
6767

6868
/**
6969
* Toaster placement
70-
* @type TToasterPlacement
70+
* @return TToasterPlacement
7171
*/
72-
@Input() placement: TToasterPlacement = ToasterPlacement.TopEnd;
72+
readonly placement = input<TToasterPlacement>(ToasterPlacement.TopEnd);
7373

7474
/**
7575
* Toaster position
76-
* @type (string | 'absolute' | 'fixed' | 'static')
76+
* @return (string | 'absolute' | 'fixed' | 'static')
7777
*/
78-
@Input() position: string | 'absolute' | 'fixed' | 'static' = 'absolute';
78+
readonly position = input<string | 'absolute' | 'fixed' | 'static'>('absolute');
7979

80-
@ViewChild(ToasterHostDirective, { static: true }) toasterHost!: ToasterHostDirective;
81-
@ContentChildren(ToastComponent, { read: ViewContainerRef }) contentToasts!: QueryList<ViewContainerRef>;
80+
readonly toasterHost = viewChild.required(ToasterHostDirective);
81+
readonly contentToasts = contentChildren(ToastComponent, { read: ViewContainerRef });
8282

83-
@HostBinding('class')
84-
get hostClasses(): any {
83+
readonly hostClasses = computed(() => {
84+
const placement = this.placement();
85+
const position = this.position();
8586
return {
8687
toaster: true,
8788
'toast-container': true,
88-
[`position-${this.position}`]: !!this.position,
89-
'top-0': this.placement.includes('top'),
90-
'top-50': this.placement.includes('middle'),
91-
'bottom-0': this.placement.includes('bottom'),
92-
'start-0': this.placement.includes('start'),
93-
'start-50': this.placement.includes('center'),
94-
'end-0': this.placement.includes('end'),
95-
'translate-middle-x': this.placement.includes('center') && !this.placement.includes('middle'),
96-
'translate-middle-y': this.placement.includes('middle') && !this.placement.includes('center'),
97-
'translate-middle': this.placement.includes('middle') && this.placement.includes('center')
89+
[`position-${position}`]: !!position,
90+
'top-0': placement.includes('top'),
91+
'top-50': placement.includes('middle'),
92+
'bottom-0': placement.includes('bottom'),
93+
'start-0': placement.includes('start'),
94+
'start-50': placement.includes('center'),
95+
'end-0': placement.includes('end'),
96+
'translate-middle-x': placement.includes('center') && !placement.includes('middle'),
97+
'translate-middle-y': placement.includes('middle') && !placement.includes('center'),
98+
'translate-middle': placement.includes('middle') && placement.includes('center')
9899
};
99-
}
100+
});
100101

101102
ngOnInit(): void {
102103
this.stateToasterSubscribe();
103104
}
104105

105-
ngAfterContentChecked(): void {
106-
this.toasts = this.contentToasts;
107-
}
108-
109106
public addToast(
110107
toast: any,
111108
props: any,
@@ -116,13 +113,13 @@ export class ToasterComponent implements OnInit, AfterContentChecked {
116113
projectableNodes?: Node[][];
117114
}
118115
): ComponentRef<any> {
119-
const componentRef: ComponentRef<any> = this.toasterHost.viewContainerRef.createComponent(toast, options);
116+
const componentRef: ComponentRef<any> = this.toasterHost().viewContainerRef.createComponent(toast, options);
120117
this.toastsDynamic.push(componentRef);
121118
const index = this.toastsDynamic.indexOf(componentRef);
122119
for (const [key, value] of Object.entries(props)) {
123120
componentRef.setInput(key, value);
124121
}
125-
componentRef.setInput('placement', this.placement);
122+
componentRef.setInput('placement', this.placement());
126123
componentRef.setInput('dynamic', true);
127124
componentRef.setInput('index', index);
128125
componentRef.setInput('visible', true);
@@ -139,8 +136,7 @@ export class ToasterComponent implements OnInit, AfterContentChecked {
139136
item.destroy();
140137
}
141138
});
142-
143-
this.toasts?.forEach((item) => {
139+
this.contentToasts()?.forEach((item) => {
144140
if (state.toast && item.element.nativeElement === state.toast.hostElement.nativeElement) {
145141
if (!state.toast.dynamic()) {
146142
state.toast['visible'] = false;

projects/coreui-angular/src/lib/toast/toaster/toaster.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
22
import { BehaviorSubject } from 'rxjs';
33

44
import { TToasterPlacement } from './toaster.component';
5-
import { ToastComponent } from '../toast/toast.component';
5+
import { type ToastComponent } from '../toast/toast.component';
66

77
export interface IToasterAction {
88
placement?: TToasterPlacement;

0 commit comments

Comments
 (0)