Browse Source

Refactor components to use Angular signals and input() API

Migrates multiple components and directives to use Angular's new input() and signal() APIs, replacing legacy @Input and @Output decorators. Updates templates to use signal-based accessors, improves reactivity with effect(), and modernizes event emitters with output(). This refactor enhances type safety, consistency, and future-proofs the codebase for upcoming Angular versions.
pull/24777/head
Fahri Gedik 8 months ago
parent
commit
743d660069
  1. 10
      npm/ng-packs/packages/components/page/src/page.component.html
  2. 34
      npm/ng-packs/packages/components/page/src/page.component.ts
  3. 8
      npm/ng-packs/packages/core/src/lib/abstracts/ng-model.component.ts
  4. 15
      npm/ng-packs/packages/core/src/lib/directives/autofocus.directive.ts
  5. 15
      npm/ng-packs/packages/core/src/lib/directives/show-password.directive.ts
  6. 32
      npm/ng-packs/packages/feature-management/src/lib/directives/free-text-input.directive.ts
  7. 4
      npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.html
  8. 4
      npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.ts
  9. 71
      npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts
  10. 20
      npm/ng-packs/packages/theme-shared/src/lib/components/checkbox/checkbox.component.ts
  11. 20
      npm/ng-packs/packages/theme-shared/src/lib/components/form-input/form-input.component.ts
  12. 37
      npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts
  13. 2
      npm/ng-packs/packages/theme-shared/src/lib/components/toast-container/toast-container.component.html
  14. 31
      npm/ng-packs/packages/theme-shared/src/lib/components/toast-container/toast-container.component.ts

10
npm/ng-packs/packages/components/page/src/page.component.html

@ -3,10 +3,10 @@
@if (customTitle) { @if (customTitle) {
<ng-content select="abp-page-title-container"></ng-content> <ng-content select="abp-page-title-container"></ng-content>
} @else { } @else {
@if (title) { @if (title()) {
<div class="col-auto" *abpPagePart="pageParts.title"> <div class="col-auto" *abpPagePart="pageParts.title">
<h1 class="content-header-title"> <h1 class="content-header-title">
{{ title }} {{ title() }}
</h1> </h1>
</div> </div>
} }
@ -25,9 +25,9 @@
@if (customToolbar) { @if (customToolbar) {
<ng-content select="abp-page-toolbar-container"></ng-content> <ng-content select="abp-page-toolbar-container"></ng-content>
} @else { } @else {
@if (toolbarVisible) { @if (toolbarVisible()) {
<div class="col" *abpPagePart="pageParts.toolbar; context: toolbarData"> <div class="col" *abpPagePart="pageParts.toolbar; context: toolbarData()">
<abp-page-toolbar [record]="toolbarData"></abp-page-toolbar> <abp-page-toolbar [record]="toolbarData()"></abp-page-toolbar>
</div> </div>
} }
} }

34
npm/ng-packs/packages/components/page/src/page.component.ts

@ -1,4 +1,4 @@
import { Component, Input, ViewEncapsulation, ContentChild, input } from '@angular/core'; import { Component, ViewEncapsulation, ContentChild, input, effect, signal } from '@angular/core';
import { import {
PageTitleContainerComponent, PageTitleContainerComponent,
PageBreadcrumbContainerComponent, PageBreadcrumbContainerComponent,
@ -16,21 +16,13 @@ import { PagePartDirective } from './page-part.directive';
imports: [BreadcrumbComponent, PageToolbarComponent, PagePartDirective], imports: [BreadcrumbComponent, PageToolbarComponent, PagePartDirective],
}) })
export class PageComponent { export class PageComponent {
@Input() title?: string; readonly title = input<string | undefined>(undefined);
readonly toolbarInput = input<any>(undefined, { alias: 'toolbar' });
toolbarVisible = false;
_toolbarData: any;
@Input() set toolbar(val: any) {
this._toolbarData = val;
this.toolbarVisible = true;
}
get toolbarData() {
return this._toolbarData;
}
readonly breadcrumb = input(true); readonly breadcrumb = input(true);
protected readonly toolbarVisible = signal(false);
protected readonly toolbarData = signal<any>(undefined);
pageParts = { pageParts = {
title: PageParts.title, title: PageParts.title,
breadcrumb: PageParts.breadcrumb, breadcrumb: PageParts.breadcrumb,
@ -42,10 +34,20 @@ export class PageComponent {
customBreadcrumb?: PageBreadcrumbContainerComponent; customBreadcrumb?: PageBreadcrumbContainerComponent;
@ContentChild(PageToolbarContainerComponent) customToolbar?: PageToolbarContainerComponent; @ContentChild(PageToolbarContainerComponent) customToolbar?: PageToolbarContainerComponent;
constructor() {
effect(() => {
const toolbar = this.toolbarInput();
if (toolbar !== undefined) {
this.toolbarData.set(toolbar);
this.toolbarVisible.set(true);
}
});
}
get shouldRenderRow() { get shouldRenderRow() {
return !!( return !!(
this.title || this.title() ||
this.toolbarVisible || this.toolbarVisible() ||
this.breadcrumb() || this.breadcrumb() ||
this.customTitle || this.customTitle ||
this.customBreadcrumb || this.customBreadcrumb ||

8
npm/ng-packs/packages/core/src/lib/abstracts/ng-model.component.ts

@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, inject, Input, input } from '@angular/core'; import { ChangeDetectorRef, Component, inject, input } from '@angular/core';
import { ControlValueAccessor } from '@angular/forms'; import { ControlValueAccessor } from '@angular/forms';
// Not an abstract class on purpose. Do not change! // Not an abstract class on purpose. Do not change!
@ -11,16 +11,16 @@ export class AbstractNgModelComponent<T = any, U = T> implements ControlValueAcc
onChange?: (value: T) => void; onChange?: (value: T) => void;
onTouched?: () => void; onTouched?: () => void;
@Input() // Note: disabled needs to remain as a regular property because setDisabledState assigns to it
disabled?: boolean; disabled?: boolean;
readonly readonly = input<boolean>(undefined); readonly readonly = input<boolean | undefined>(undefined);
readonly valueFn = input<(value: U, previousValue?: T) => T>(value => value as any as T); readonly valueFn = input<(value: U, previousValue?: T) => T>(value => value as any as T);
readonly valueLimitFn = input<(value: T, previousValue?: T) => any>(value => false); readonly valueLimitFn = input<(value: T, previousValue?: T) => any>(value => false);
@Input() // Note: value needs getter/setter for ControlValueAccessor and two-way binding
set value(value: T) { set value(value: T) {
value = this.valueFn()(value as any as U, this._value); value = this.valueFn()(value as any as U, this._value);

15
npm/ng-packs/packages/core/src/lib/directives/autofocus.directive.ts

@ -1,4 +1,4 @@
import { AfterViewInit, Directive, ElementRef, Input, inject } from '@angular/core'; import { AfterViewInit, Directive, ElementRef, inject, input, numberAttribute } from '@angular/core';
@Directive({ @Directive({
selector: '[autofocus]', selector: '[autofocus]',
@ -6,18 +6,9 @@ import { AfterViewInit, Directive, ElementRef, Input, inject } from '@angular/co
export class AutofocusDirective implements AfterViewInit { export class AutofocusDirective implements AfterViewInit {
private elRef = inject(ElementRef); private elRef = inject(ElementRef);
private _delay = 0; readonly delay = input(0, { alias: 'autofocus', transform: numberAttribute });
@Input('autofocus')
set delay(val: number | string | undefined) {
this._delay = Number(val) || 0;
}
get delay() {
return this._delay;
}
ngAfterViewInit(): void { ngAfterViewInit(): void {
setTimeout(() => this.elRef.nativeElement.focus(), this.delay as number); setTimeout(() => this.elRef.nativeElement.focus(), this.delay());
} }
} }

15
npm/ng-packs/packages/core/src/lib/directives/show-password.directive.ts

@ -1,4 +1,4 @@
import { Directive, ElementRef, Input, inject } from '@angular/core'; import { Directive, ElementRef, effect, inject, input } from '@angular/core';
@Directive({ @Directive({
selector: '[abpShowPassword]', selector: '[abpShowPassword]',
@ -6,10 +6,15 @@ import { Directive, ElementRef, Input, inject } from '@angular/core';
export class ShowPasswordDirective { export class ShowPasswordDirective {
protected readonly elementRef = inject(ElementRef); protected readonly elementRef = inject(ElementRef);
@Input() set abpShowPassword(visible: boolean) { readonly abpShowPassword = input(false);
const element = this.elementRef.nativeElement as HTMLInputElement;
if (!element) return;
element.type = visible ? 'text' : 'password'; constructor() {
effect(() => {
const visible = this.abpShowPassword();
const element = this.elementRef.nativeElement as HTMLInputElement;
if (!element) return;
element.type = visible ? 'text' : 'password';
});
} }
} }

32
npm/ng-packs/packages/feature-management/src/lib/directives/free-text-input.directive.ts

@ -1,4 +1,4 @@
import { Directive, HostBinding, Input } from '@angular/core'; import { Directive, effect, inject, input, signal, ElementRef, Renderer2 } from '@angular/core';
// TODO: improve this type // TODO: improve this type
export interface FreeTextType { export interface FreeTextType {
@ -9,7 +9,7 @@ export interface FreeTextType {
}; };
} }
export const INPUT_TYPES = { export const INPUT_TYPES: Record<string, string> = {
numeric: 'number', numeric: 'number',
default: 'text', default: 'text',
}; };
@ -19,21 +19,23 @@ export const INPUT_TYPES = {
exportAs: 'inputAbpFeatureManagementFreeText', exportAs: 'inputAbpFeatureManagementFreeText',
}) })
export class FreeTextInputDirective { export class FreeTextInputDirective {
_feature: FreeTextType; private readonly elRef = inject(ElementRef);
// eslint-disable-next-line @angular-eslint/no-input-rename private readonly renderer = inject(Renderer2);
@Input('abpFeatureManagementFreeText') set feature(val: FreeTextType) {
this._feature = val;
this.setInputType();
}
get feature() { readonly feature = input<FreeTextType | undefined>(undefined, { alias: 'abpFeatureManagementFreeText' });
return this._feature;
}
@HostBinding('type') type: string; constructor() {
effect(() => {
const feature = this.feature();
if (feature) {
this.setInputType(feature);
}
});
}
private setInputType() { private setInputType(feature: FreeTextType) {
const validatorType = this.feature?.valueType?.validator?.name.toLowerCase(); const validatorType = feature?.valueType?.validator?.name?.toLowerCase();
this.type = INPUT_TYPES[validatorType] ?? INPUT_TYPES.default; const type = INPUT_TYPES[validatorType] ?? INPUT_TYPES['default'];
this.renderer.setAttribute(this.elRef.nativeElement, 'type', type);
} }
} }

4
npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.html

@ -1,9 +1,9 @@
@if (items.length) { @if (items().length) {
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"> <li class="breadcrumb-item">
<a routerLink="/"><i class="fa fa-home" aria-hidden="true"></i> </a> <a routerLink="/"><i class="fa fa-home" aria-hidden="true"></i> </a>
</li> </li>
@for (item of items; track $index; let last = $last) { @for (item of items(); track $index; let last = $last) {
<li class="breadcrumb-item" [class.active]="last" aria-current="page"> <li class="breadcrumb-item" [class.active]="last" aria-current="page">
<ng-container <ng-container
*ngTemplateOutlet="item.path ? linkTemplate : textTemplate; context: { $implicit: item }" *ngTemplateOutlet="item.path ? linkTemplate : textTemplate; context: { $implicit: item }"

4
npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.ts

@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core'; import { Component, input } from '@angular/core';
import { NgTemplateOutlet } from '@angular/common'; import { NgTemplateOutlet } from '@angular/common';
import { RouterLink } from '@angular/router'; import { RouterLink } from '@angular/router';
import { ABP, LocalizationPipe } from '@abp/ng.core'; import { ABP, LocalizationPipe } from '@abp/ng.core';
@ -9,5 +9,5 @@ import { ABP, LocalizationPipe } from '@abp/ng.core';
imports: [NgTemplateOutlet, RouterLink, LocalizationPipe], imports: [NgTemplateOutlet, RouterLink, LocalizationPipe],
}) })
export class BreadcrumbItemsComponent { export class BreadcrumbItemsComponent {
@Input() items: Partial<ABP.Route>[] = []; readonly items = input<Partial<ABP.Route>[]>([]);
} }

71
npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts

@ -2,14 +2,15 @@
import { import {
Component, Component,
ElementRef, ElementRef,
EventEmitter,
Input,
OnInit, OnInit,
Output,
Renderer2, Renderer2,
ViewChild, ViewChild,
effect,
inject, inject,
input input,
output,
signal,
computed
} from '@angular/core'; } from '@angular/core';
import { ABP, StopPropagationDirective } from '@abp/ng.core'; import { ABP, StopPropagationDirective } from '@abp/ng.core';
@ -22,12 +23,12 @@ import { ABP, StopPropagationDirective } from '@abp/ng.core';
[attr.type]="buttonType()" [attr.type]="buttonType()"
[attr.form]="formName()" [attr.form]="formName()"
[class]="buttonClass()" [class]="buttonClass()"
[disabled]="loading || disabled()" [disabled]="isLoading() || disabled()"
(click.stop)="click.next($event); abpClick.next($event)" (click.stop)="click.emit($event); abpClick.emit($event)"
(focus)="focus.next($event); abpFocus.next($event)" (focus)="focus.emit($event); abpFocus.emit($event)"
(blur)="blur.next($event); abpBlur.next($event)" (blur)="blur.emit($event); abpBlur.emit($event)"
> >
<i [class]="icon" class="me-1" aria-hidden="true"></i><ng-content></ng-content> <i [class]="icon()" class="me-1" aria-hidden="true"></i><ng-content></ng-content>
</button> </button>
`, `,
imports: [StopPropagationDirective], imports: [StopPropagationDirective],
@ -36,48 +37,48 @@ export class ButtonComponent implements OnInit {
private renderer = inject(Renderer2); private renderer = inject(Renderer2);
readonly buttonId = input(''); readonly buttonId = input('');
readonly buttonClass = input('btn btn-primary'); readonly buttonClass = input('btn btn-primary');
readonly buttonType = input('button'); readonly buttonType = input('button');
readonly formName = input<string | undefined>(undefined);
readonly formName = input<string>(undefined); readonly iconClass = input<string | undefined>(undefined);
readonly loadingInput = input(false, { alias: 'loading' });
readonly iconClass = input<string>(undefined);
@Input()
loading = false;
readonly disabled = input<boolean | undefined>(false); readonly disabled = input<boolean | undefined>(false);
readonly attributes = input<ABP.Dictionary<string> | undefined>(undefined);
readonly attributes = input<ABP.Dictionary<string>>(undefined); // Internal writable signal for loading state - can be set programmatically
private readonly _loading = signal(false);
@Output() readonly click = new EventEmitter<MouseEvent>(); // Computed that combines input and internal state
readonly isLoading = computed(() => this.loadingInput() || this._loading());
@Output() readonly focus = new EventEmitter<FocusEvent>(); // Getter/setter for backward compatibility (used by ModalComponent)
get loading(): boolean {
@Output() readonly blur = new EventEmitter<FocusEvent>(); return this._loading();
}
@Output() readonly abpClick = new EventEmitter<MouseEvent>(); set loading(value: boolean) {
this._loading.set(value);
@Output() readonly abpFocus = new EventEmitter<FocusEvent>(); }
@Output() readonly abpBlur = new EventEmitter<FocusEvent>(); readonly click = output<MouseEvent>();
readonly focus = output<FocusEvent>();
readonly blur = output<FocusEvent>();
readonly abpClick = output<MouseEvent>();
readonly abpFocus = output<FocusEvent>();
readonly abpBlur = output<FocusEvent>();
@ViewChild('button', { static: true }) @ViewChild('button', { static: true })
buttonRef!: ElementRef<HTMLButtonElement>; buttonRef!: ElementRef<HTMLButtonElement>;
get icon(): string { protected readonly icon = computed(() => {
return `${this.loading ? 'fa fa-spinner fa-spin' : this.iconClass() || 'd-none'}`; return this.isLoading() ? 'fa fa-spinner fa-spin' : this.iconClass() || 'd-none';
} });
ngOnInit() { ngOnInit() {
const attributes = this.attributes(); const attributes = this.attributes();
if (attributes) { if (attributes) {
Object.keys(attributes).forEach(key => { Object.keys(attributes).forEach(key => {
const attributesValue = this.attributes(); if (attributes[key]) {
if (attributesValue?.[key]) { this.renderer.setAttribute(this.buttonRef.nativeElement, key, attributes[key]);
this.renderer.setAttribute(this.buttonRef.nativeElement, key, attributesValue[key]);
} }
}); });
} }

20
npm/ng-packs/packages/theme-shared/src/lib/components/checkbox/checkbox.component.ts

@ -1,4 +1,4 @@
import { Component, EventEmitter, forwardRef, Input, Output, input } from '@angular/core'; import { Component, forwardRef, input, output } from '@angular/core';
import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'; import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core'; import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core';
@ -13,12 +13,12 @@ import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core';
[readonly]="checkboxReadonly()" [readonly]="checkboxReadonly()"
[class]="checkboxClass()" [class]="checkboxClass()"
[style]="checkboxStyle()" [style]="checkboxStyle()"
(blur)="checkboxBlur.next()" (blur)="checkboxBlur.emit()"
(focus)="checkboxFocus.next()" (focus)="checkboxFocus.emit()"
/> />
@if (label) { @if (label()) {
<label [class]="labelClass()" [for]="checkboxId()"> <label [class]="labelClass()" [for]="checkboxId()">
{{ label | abpLocalization }} {{ label() | abpLocalization }}
</label> </label>
} }
</div> </div>
@ -33,14 +33,12 @@ import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core';
imports: [FormsModule, LocalizationPipe], imports: [FormsModule, LocalizationPipe],
}) })
export class FormCheckboxComponent extends AbstractNgModelComponent { export class FormCheckboxComponent extends AbstractNgModelComponent {
@Input() label?: string; readonly label = input<string | undefined>(undefined);
readonly labelClass = input('form-check-label'); readonly labelClass = input('form-check-label');
readonly checkboxId = input.required<string>(); readonly checkboxId = input.required<string>();
readonly checkboxStyle = input<{ readonly checkboxStyle = input<{ [klass: string]: any } | null | undefined>(undefined);
[klass: string]: any;
} | null | undefined>(undefined);
readonly checkboxClass = input('form-check-input'); readonly checkboxClass = input('form-check-input');
readonly checkboxReadonly = input(false); readonly checkboxReadonly = input(false);
@Output() checkboxBlur = new EventEmitter<void>(); readonly checkboxBlur = output<void>();
@Output() checkboxFocus = new EventEmitter<void>(); readonly checkboxFocus = output<void>();
} }

20
npm/ng-packs/packages/theme-shared/src/lib/components/form-input/form-input.component.ts

@ -1,4 +1,4 @@
import { Component, EventEmitter, forwardRef, Input, Output, input } from '@angular/core'; import { Component, forwardRef, input, output } from '@angular/core';
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core'; import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core';
@ -6,9 +6,9 @@ import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core';
selector: 'abp-form-input', selector: 'abp-form-input',
template: ` template: `
<div class="mb-3"> <div class="mb-3">
@if (label) { @if (label()) {
<label [class]="labelClass()" [for]="inputId()"> <label [class]="labelClass()" [for]="inputId()">
{{ label | abpLocalization }} {{ label() | abpLocalization }}
</label> </label>
} }
<input <input
@ -18,8 +18,8 @@ import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core';
[readonly]="inputReadonly()" [readonly]="inputReadonly()"
[class]="inputClass()" [class]="inputClass()"
[style]="inputStyle()" [style]="inputStyle()"
(blur)="formBlur.next()" (blur)="formBlur.emit()"
(focus)="formFocus.next()" (focus)="formFocus.emit()"
[(ngModel)]="value" [(ngModel)]="value"
/> />
</div> </div>
@ -36,13 +36,11 @@ import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core';
export class FormInputComponent extends AbstractNgModelComponent { export class FormInputComponent extends AbstractNgModelComponent {
readonly inputId = input.required<string>(); readonly inputId = input.required<string>();
readonly inputReadonly = input(false); readonly inputReadonly = input(false);
@Input() label = ''; readonly label = input('');
readonly labelClass = input('form-label'); readonly labelClass = input('form-label');
readonly inputPlaceholder = input(''); readonly inputPlaceholder = input('');
readonly inputStyle = input<{ readonly inputStyle = input<{ [klass: string]: any } | null | undefined>(undefined);
[klass: string]: any;
} | null | undefined>(undefined);
readonly inputClass = input('form-control'); readonly inputClass = input('form-control');
@Output() formBlur = new EventEmitter<void>(); readonly formBlur = output<void>();
@Output() formFocus = new EventEmitter<void>(); readonly formFocus = output<void>();
} }

37
npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts

@ -1,11 +1,11 @@
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit, inject, input } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit, inject, input, effect, signal } from '@angular/core';
import { combineLatest, Subscription, timer } from 'rxjs'; import { combineLatest, Subscription, timer } from 'rxjs';
import { HttpWaitService, RouterWaitService, SubscriptionService } from '@abp/ng.core'; import { HttpWaitService, RouterWaitService, SubscriptionService } from '@abp/ng.core';
@Component({ @Component({
selector: 'abp-loader-bar', selector: 'abp-loader-bar',
template: ` template: `
<div id="abp-loader-bar" [class]="containerClass()" [class.is-loading]="isLoading"> <div id="abp-loader-bar" [class]="containerClass()" [class.is-loading]="isLoading()">
<div <div
class="abp-progress" class="abp-progress"
[class.progressing]="progressLevel" [class.progressing]="progressLevel"
@ -27,31 +27,26 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
private httpWaitService = inject(HttpWaitService); private httpWaitService = inject(HttpWaitService);
private routerWaitService = inject(RouterWaitService); private routerWaitService = inject(RouterWaitService);
protected _isLoading!: boolean; readonly isLoadingInput = input(false, { alias: 'isLoading' });
@Input()
set isLoading(value: boolean) {
this._isLoading = value;
this.cdRef.detectChanges();
}
get isLoading(): boolean {
return this._isLoading;
}
readonly containerClass = input('abp-loader-bar'); readonly containerClass = input('abp-loader-bar');
readonly color = input('#77b6ff'); readonly color = input('#77b6ff');
progressLevel = 0; protected readonly isLoading = signal(false);
progressLevel = 0;
interval = new Subscription(); interval = new Subscription();
timer = new Subscription(); timer = new Subscription();
intervalPeriod = 350; intervalPeriod = 350;
stopDelay = 800; stopDelay = 800;
constructor() {
effect(() => {
const value = this.isLoadingInput();
this.isLoading.set(value);
this.cdRef.detectChanges();
});
}
private readonly clearProgress = () => { private readonly clearProgress = () => {
this.progressLevel = 0; this.progressLevel = 0;
this.cdRef.detectChanges(); this.cdRef.detectChanges();
@ -93,9 +88,9 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
} }
startLoading() { startLoading() {
if (this.isLoading || !this.interval.closed) return; if (this.isLoading() || !this.interval.closed) return;
this.isLoading = true; this.isLoading.set(true);
this.progressLevel = 0; this.progressLevel = 0;
this.cdRef.detectChanges(); this.cdRef.detectChanges();
this.interval = timer(0, this.intervalPeriod).subscribe(this.reportProgress); this.interval = timer(0, this.intervalPeriod).subscribe(this.reportProgress);
@ -106,7 +101,7 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
this.interval.unsubscribe(); this.interval.unsubscribe();
this.progressLevel = 100; this.progressLevel = 100;
this.isLoading = false; this.isLoading.set(false);
if (!this.timer.closed) return; if (!this.timer.closed) return;

2
npm/ng-packs/packages/theme-shared/src/lib/components/toast-container/toast-container.component.html

@ -1,7 +1,7 @@
<div <div
class="abp-toast-container" class="abp-toast-container"
[style.top]="top() || 'auto'" [style.top]="top() || 'auto'"
[style.right]="right || 'auto'" [style.right]="right() || 'auto'"
[style.bottom]="bottom() || 'auto'" [style.bottom]="bottom() || 'auto'"
[style.left]="left() || 'auto'" [style.left]="left() || 'auto'"
[style.display]="toasts.length ? 'flex' : 'none'" [style.display]="toasts.length ? 'flex' : 'none'"

31
npm/ng-packs/packages/theme-shared/src/lib/components/toast-container/toast-container.component.ts

@ -1,4 +1,4 @@
import { Component, HostListener, Input, OnInit, input } from '@angular/core'; import { Component, OnInit, input, signal, effect } from '@angular/core';
import { ReplaySubject } from 'rxjs'; import { ReplaySubject } from 'rxjs';
import { toastInOut } from '../../animations/toast.animations'; import { toastInOut } from '../../animations/toast.animations';
import { Toaster } from '../../models/toaster'; import { Toaster } from '../../models/toaster';
@ -10,6 +10,9 @@ import { ToastComponent } from '../toast/toast.component';
styleUrls: ['./toast-container.component.scss'], styleUrls: ['./toast-container.component.scss'],
animations: [toastInOut], animations: [toastInOut],
imports: [ToastComponent], imports: [ToastComponent],
host: {
'(window:resize)': 'onWindowResize()'
}
}) })
export class ToastContainerComponent implements OnInit { export class ToastContainerComponent implements OnInit {
toasts$!: ReplaySubject<Toaster.Toast[]>; toasts$!: ReplaySubject<Toaster.Toast[]>;
@ -18,18 +21,21 @@ export class ToastContainerComponent implements OnInit {
toasts = [] as Toaster.Toast[]; toasts = [] as Toaster.Toast[];
readonly top = input<string>(undefined); readonly top = input<string | undefined>(undefined);
readonly rightInput = input('30px', { alias: 'right' });
@Input()
right = '30px';
defaultRight = '30px';
defaultMobileRight = '0';
readonly bottom = input('30px'); readonly bottom = input('30px');
readonly left = input<string | undefined>(undefined);
readonly toastKey = input<string | undefined>(undefined);
readonly left = input<string>(undefined); protected readonly right = signal('30px');
readonly defaultRight = '30px';
readonly defaultMobileRight = '0';
readonly toastKey = input<string>(undefined); constructor() {
effect(() => {
this.right.set(this.rightInput());
});
}
ngOnInit() { ngOnInit() {
this.setDefaultRight(); this.setDefaultRight();
@ -42,15 +48,14 @@ export class ToastContainerComponent implements OnInit {
}); });
} }
@HostListener('window:resize')
onWindowResize() { onWindowResize() {
this.setDefaultRight(); this.setDefaultRight();
} }
setDefaultRight() { setDefaultRight() {
const screenWidth = window.innerWidth; const screenWidth = window.innerWidth;
if (screenWidth < 768 && this.right == this.defaultRight) { if (screenWidth < 768 && this.right() === this.defaultRight) {
this.right = this.defaultMobileRight; this.right.set(this.defaultMobileRight);
} }
} }

Loading…
Cancel
Save