Browse Source

Refactor to use Angular input signals across components

Replaces traditional @Input() properties with Angular's new input() signal API in multiple components, directives, and templates. Updates all usages to call input signal functions, ensuring compatibility and reactivity. This change modernizes the codebase, improves type safety, and prepares for future Angular features.
pull/24777/head
Fahri Gedik 9 months ago
parent
commit
c0d2b57f01
  1. 97
      npm/ng-packs/packages/components/chart.js/src/chart.component.ts
  2. 5
      npm/ng-packs/packages/components/extensible/src/lib/components/abstract-actions/abstract-actions.component.ts
  3. 2
      npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.html
  4. 19
      npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts
  5. 12
      npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.html
  6. 45
      npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts
  7. 2
      npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.html
  8. 5
      npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.ts
  9. 21
      npm/ng-packs/packages/components/extensible/src/lib/directives/prop-data.directive.ts
  10. 20
      npm/ng-packs/packages/components/extensible/src/lib/models/actions.ts
  11. 35
      npm/ng-packs/packages/components/page/src/page-part.directive.ts
  12. 2
      npm/ng-packs/packages/components/page/src/page.component.html
  13. 6
      npm/ng-packs/packages/components/page/src/page.component.ts
  14. 17
      npm/ng-packs/packages/core/src/lib/abstracts/ng-model.component.ts
  15. 6
      npm/ng-packs/packages/core/src/lib/directives/debounce.directive.ts
  16. 63
      npm/ng-packs/packages/core/src/lib/directives/for.directive.ts
  17. 31
      npm/ng-packs/packages/core/src/lib/directives/form-submit.directive.ts
  18. 10
      npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts
  19. 70
      npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts
  20. 2
      npm/ng-packs/packages/setting-management/package.json
  21. 6
      npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.html
  22. 4
      npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts
  23. 44
      npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts
  24. 8
      npm/ng-packs/packages/theme-shared/src/lib/components/card/card-body.component.ts
  25. 8
      npm/ng-packs/packages/theme-shared/src/lib/components/card/card-footer.component.ts
  26. 8
      npm/ng-packs/packages/theme-shared/src/lib/components/card/card-header.component.ts
  27. 8
      npm/ng-packs/packages/theme-shared/src/lib/components/card/card.component.ts
  28. 29
      npm/ng-packs/packages/theme-shared/src/lib/components/checkbox/checkbox.component.ts
  29. 33
      npm/ng-packs/packages/theme-shared/src/lib/components/form-input/form-input.component.ts
  30. 14
      npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts
  31. 2
      npm/ng-packs/packages/theme-shared/src/lib/components/password/password.component.html
  32. 6
      npm/ng-packs/packages/theme-shared/src/lib/components/password/password.component.ts
  33. 6
      npm/ng-packs/packages/theme-shared/src/lib/components/toast-container/toast-container.component.html
  34. 18
      npm/ng-packs/packages/theme-shared/src/lib/components/toast-container/toast-container.component.ts
  35. 6
      npm/ng-packs/packages/theme-shared/src/lib/components/toast/toast.component.html
  36. 20
      npm/ng-packs/packages/theme-shared/src/lib/components/toast/toast.component.ts
  37. 5
      npm/ng-packs/packages/theme-shared/src/lib/directives/disabled.directive.ts
  38. 30
      npm/ng-packs/packages/theme-shared/src/lib/directives/ellipsis.directive.ts
  39. 32
      npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts
  40. 6
      npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-default.directive.ts
  41. 33
      npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-list.directive.ts

97
npm/ng-packs/packages/components/chart.js/src/chart.component.ts

@ -1,17 +1,17 @@
import { import {
AfterViewInit, AfterViewInit,
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
ElementRef, ElementRef,
EventEmitter, EventEmitter,
Input, OnDestroy,
OnChanges, Output,
OnDestroy, ViewChild,
Output, effect,
SimpleChanges, inject,
ViewChild, input,
inject untracked,
} from '@angular/core'; } from '@angular/core';
let Chart: any; let Chart: any;
@ -21,13 +21,13 @@ let Chart: any;
template: ` template: `
<div <div
style="position:relative" style="position:relative"
[style.width]="responsive && !width ? null : width" [style.width]="responsive() && !width() ? null : width()"
[style.height]="responsive && !height ? null : height" [style.height]="responsive() && !height() ? null : height()"
> >
<canvas <canvas
#canvas #canvas
[attr.width]="responsive && !width ? null : width" [attr.width]="responsive() && !width() ? null : width()"
[attr.height]="responsive && !height ? null : height" [attr.height]="responsive() && !height() ? null : height()"
(click)="onCanvasClick($event)" (click)="onCanvasClick($event)"
></canvas> ></canvas>
</div> </div>
@ -35,32 +35,39 @@ let Chart: any;
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'abpChart', exportAs: 'abpChart',
}) })
export class ChartComponent implements AfterViewInit, OnDestroy, OnChanges { export class ChartComponent implements AfterViewInit, OnDestroy {
el = inject(ElementRef); el = inject(ElementRef);
private cdr = inject(ChangeDetectorRef); private cdr = inject(ChangeDetectorRef);
@Input() type!: string; readonly type = input.required<string>();
readonly data = input<any>({});
@Input() data: any = {}; readonly options = input<any>({});
readonly plugins = input<any[]>([]);
@Input() options: any = {}; readonly width = input<string>();
readonly height = input<string>();
@Input() plugins: any[] = []; readonly responsive = input<boolean>(true);
@Input() width?: string;
@Input() height?: string;
@Input() responsive = true;
@Output() dataSelect = new EventEmitter(); @Output() dataSelect = new EventEmitter();
@Output() initialized = new EventEmitter<boolean>(); @Output() initialized = new EventEmitter<boolean>();
@ViewChild('canvas') canvas!: ElementRef<HTMLCanvasElement>; @ViewChild('canvas') canvas!: ElementRef<HTMLCanvasElement>;
chart: any; chart: any;
constructor() {
effect(() => {
const data = this.data();
const options = this.options();
untracked(() => {
if (this.chart) {
this.chart.destroy();
this.initChart();
}
});
});
}
ngAfterViewInit() { ngAfterViewInit() {
import('chart.js/auto').then(module => { import('chart.js/auto').then(module => {
Chart = module.default; Chart = module.default;
@ -91,19 +98,19 @@ export class ChartComponent implements AfterViewInit, OnDestroy, OnChanges {
} }
private initChart = () => { private initChart = () => {
const opts = this.options || {}; const opts = this.options() || {};
opts.responsive = this.responsive; opts.responsive = this.responsive();
// allows chart to resize in responsive mode // allows chart to resize in responsive mode
if (opts.responsive && (this.height || this.width)) { if (opts.responsive && (this.height() || this.width())) {
opts.maintainAspectRatio = false; opts.maintainAspectRatio = false;
} }
this.chart = new Chart(this.canvas.nativeElement, { this.chart = new Chart(this.canvas.nativeElement, {
type: this.type as any, type: this.type() as any,
data: this.data, data: this.data(),
options: this.options, options: this.options(),
plugins: this.plugins, plugins: this.plugins(),
}); });
}; };
@ -140,13 +147,5 @@ export class ChartComponent implements AfterViewInit, OnDestroy, OnChanges {
this.chart = null; this.chart = null;
} }
} }
ngOnChanges(changes: SimpleChanges) {
if (!this.chart) return;
if (changes.data?.currentValue || changes.options?.currentValue) {
this.chart.destroy();
this.initChart();
}
}
} }

5
npm/ng-packs/packages/components/extensible/src/lib/components/abstract-actions/abstract-actions.component.ts

@ -1,4 +1,4 @@
import { Directive, Injector, Input, inject } from '@angular/core'; import { Directive, Injector, inject, input } from '@angular/core';
import { ActionData, ActionList, InferredAction } from '../../models/actions'; import { ActionData, ActionList, InferredAction } from '../../models/actions';
import { ExtensionsService } from '../../services/extensions.service'; import { ExtensionsService } from '../../services/extensions.service';
import { EXTENSIONS_ACTION_TYPE, EXTENSIONS_IDENTIFIER } from '../../tokens/extensions.token'; import { EXTENSIONS_ACTION_TYPE, EXTENSIONS_IDENTIFIER } from '../../tokens/extensions.token';
@ -14,7 +14,7 @@ export abstract class AbstractActionsComponent<
readonly getInjected: InferredData<L>['getInjected']; readonly getInjected: InferredData<L>['getInjected'];
@Input() record!: InferredData<L>['record']; record = input.required<InferredRecord<L>>();
protected constructor() { protected constructor() {
const injector = inject(Injector); const injector = inject(Injector);
@ -27,3 +27,4 @@ export abstract class AbstractActionsComponent<
this.actionList = extensions[type].get(name).actions as unknown as L; this.actionList = extensions[type].get(name).actions as unknown as L;
} }
} }

2
npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.html

@ -95,7 +95,7 @@
<label [htmlFor]="prop.id" [class]="classes || 'form-label d-inline-block'"> <label [htmlFor]="prop.id" [class]="classes || 'form-label d-inline-block'">
<span class="d-inline-flex align-items-center gap-1 text-nowrap"> <span class="d-inline-flex align-items-center gap-1 text-nowrap">
@if (prop.displayTextResolver) { @if (prop.displayTextResolver) {
{{ prop.displayTextResolver(data) | abpLocalization }} {{ prop.displayTextResolver(data()) | abpLocalization }}
} @else { } @else {
@if (prop.isExtra) { @if (prop.isExtra) {
{{ '::' + prop.displayName | abpLocalization }} {{ '::' + prop.displayName | abpLocalization }}

19
npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts

@ -22,6 +22,7 @@ import {
ViewChild, ViewChild,
signal, signal,
effect, effect,
input
} from '@angular/core'; } from '@angular/core';
import { import {
ControlContainer, ControlContainer,
@ -94,10 +95,10 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
private injector = inject(Injector); private injector = inject(Injector);
private readonly form = this.#groupDirective.form; private readonly form = this.#groupDirective.form;
@Input() data!: PropData; readonly data = input.required<PropData>();
@Input() prop!: FormProp; @Input() prop!: FormProp;
@Input() first?: boolean; readonly first = input<boolean>(undefined);
@Input() isFirstGroup?: boolean; readonly isFirstGroup = input<boolean>(undefined);
@ViewChild('field') private fieldRef!: ElementRef<HTMLElement>; @ViewChild('field') private fieldRef!: ElementRef<HTMLElement>;
injectorForCustomComponent?: Injector; injectorForCustomComponent?: Injector;
@ -113,7 +114,7 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
disabledFn = (data: PropData) => false; disabledFn = (data: PropData) => false;
get disabled() { get disabled() {
return this.disabledFn(this.data); return this.disabledFn(this.data());
} }
setTypeaheadValue(selectedOption: ABP.Option<string>) { setTypeaheadValue(selectedOption: ABP.Option<string>) {
@ -130,7 +131,7 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
? text$.pipe( ? text$.pipe(
debounceTime(300), debounceTime(300),
distinctUntilChanged(), distinctUntilChanged(),
switchMap(text => this.prop?.options?.(this.data, text) || of([])), switchMap(text => this.prop?.options?.(this.data(), text) || of([])),
) )
: of([]); : of([]);
@ -158,7 +159,7 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
} }
ngAfterViewInit() { ngAfterViewInit() {
if (this.isFirstGroup && this.first && this.fieldRef) { if (this.isFirstGroup() && this.first() && this.fieldRef) {
requestAnimationFrame(() => { requestAnimationFrame(() => {
this.fieldRef.nativeElement.focus(); this.fieldRef.nativeElement.focus();
}); });
@ -193,14 +194,14 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
}); });
} }
if (options) this.options$ = options(this.data); if (options) this.options$ = options(this.data());
if (readonly) this.readonly = readonly(this.data); if (readonly) this.readonly = readonly(this.data());
if (disabled) { if (disabled) {
this.disabledFn = disabled; this.disabledFn = disabled;
} }
if (validators) { if (validators) {
this.validators = validators(this.data); this.validators = validators(this.data());
this.setAsterisk(); this.setAsterisk();
} }
if (className !== undefined) { if (className !== undefined) {

12
npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.html

@ -1,9 +1,9 @@
@if (isBrowser) { @if (isBrowser) {
<ngx-datatable #table default [rows]="data" [count]="recordsTotal" [list]="list" <ngx-datatable #table default [rows]="data" [count]="recordsTotal()" [list]="list()"
[selectionType]="selectable ? _selectionType : undefined" (activate)="tableActivate.emit($event)" [selectionType]="selectable() ? _selectionType : undefined" (activate)="tableActivate.emit($event)"
(select)="onSelect($event)" [selected]="selected" (scroll)="onScroll($event)" [scrollbarV]="infiniteScroll" (select)="onSelect($event)" [selected]="selected()" (scroll)="onScroll($event)" [scrollbarV]="infiniteScroll()"
[style.height]="getTableHeight()" [loadingIndicator]="infiniteScroll && isLoading" [style.height]="getTableHeight()" [loadingIndicator]="infiniteScroll() && isLoading()"
[footerHeight]="infiniteScroll ? false : 50"> [footerHeight]="infiniteScroll() ? false : 50">
@if (effectiveRowDetailTemplate) { @if (effectiveRowDetailTemplate) {
<ngx-datatable-row-detail [rowHeight]="effectiveRowDetailHeight"> <ngx-datatable-row-detail [rowHeight]="effectiveRowDetailHeight">
<ng-template let-row="row" let-expanded="expanded" ngx-datatable-row-detail-template> <ng-template let-row="row" let-expanded="expanded" ngx-datatable-row-detail-template>
@ -22,7 +22,7 @@
</ng-template> </ng-template>
</ngx-datatable-column> </ngx-datatable-column>
} }
@if(selectable) { @if(selectable()) {
<ngx-datatable-column [width]="50" [sortable]="false" [canAutoResize]="false" [draggable]="false" <ngx-datatable-column [width]="50" [sortable]="false" [canAutoResize]="false" [draggable]="false"
[resizeable]="false"> [resizeable]="false">

45
npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts

@ -19,6 +19,7 @@ import {
TemplateRef, TemplateRef,
TrackByFunction, TrackByFunction,
ViewChild, ViewChild,
input
} from '@angular/core'; } from '@angular/core';
import { AsyncPipe, isPlatformBrowser, NgComponentOutlet, NgTemplateOutlet } from '@angular/common'; import { AsyncPipe, isPlatformBrowser, NgComponentOutlet, NgTemplateOutlet } from '@angular/common';
@ -109,8 +110,8 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
} }
@Input() data!: R[]; @Input() data!: R[];
@Input() list!: ListService; readonly list = input.required<ListService>();
@Input() recordsTotal!: number; readonly recordsTotal = input.required<number>();
@Input() set actionsColumnWidth(width: number) { @Input() set actionsColumnWidth(width: number) {
this._actionsColumnWidth.set(width ? Number(width) : undefined); this._actionsColumnWidth.set(width ? Number(width) : undefined);
@ -120,25 +121,25 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
@Output() tableActivate = new EventEmitter(); @Output() tableActivate = new EventEmitter();
@Input() selectable = false; readonly selectable = input(false);
@Input() set selectionType(value: SelectionType | string) { @Input() set selectionType(value: SelectionType | string) {
this._selectionType = typeof value === 'string' ? SelectionType[value] : value; this._selectionType = typeof value === 'string' ? SelectionType[value] : value;
} }
_selectionType: SelectionType = SelectionType.multiClick; _selectionType: SelectionType = SelectionType.multiClick;
@Input() selected: any[] = []; readonly selected = input<any[]>([]);
@Output() selectionChange = new EventEmitter<any[]>(); @Output() selectionChange = new EventEmitter<any[]>();
// Infinite scroll configuration // Infinite scroll configuration
@Input() infiniteScroll = false; readonly infiniteScroll = input(false);
@Input() isLoading = false; readonly isLoading = input(false);
@Input() scrollThreshold = 10; readonly scrollThreshold = input(10);
@Output() loadMore = new EventEmitter<void>(); @Output() loadMore = new EventEmitter<void>();
@Input() tableHeight: number; readonly tableHeight = input<number>(undefined);
@Input() rowDetailTemplate?: TemplateRef<RowDetailContext<R>>; readonly rowDetailTemplate = input<TemplateRef<RowDetailContext<R>>>(undefined);
@Input() rowDetailHeight: string | number = '100%'; readonly rowDetailHeight = input<string | number>('100%');
@Output() rowDetailToggle = new EventEmitter<R>(); @Output() rowDetailToggle = new EventEmitter<R>();
@ContentChild(ExtensibleTableRowDetailComponent) @ContentChild(ExtensibleTableRowDetailComponent)
@ -147,11 +148,11 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
@ViewChild('table', { static: false }) table!: DatatableComponent; @ViewChild('table', { static: false }) table!: DatatableComponent;
protected get effectiveRowDetailTemplate(): TemplateRef<RowDetailContext<R>> | undefined { protected get effectiveRowDetailTemplate(): TemplateRef<RowDetailContext<R>> | undefined {
return this.rowDetailComponent?.template() ?? this.rowDetailTemplate; return this.rowDetailComponent?.template() ?? this.rowDetailTemplate();
} }
protected get effectiveRowDetailHeight(): string | number { protected get effectiveRowDetailHeight(): string | number {
return this.rowDetailComponent?.rowHeight() ?? this.rowDetailHeight; return this.rowDetailComponent?.rowHeight() ?? this.rowDetailHeight();
} }
hasAtLeastOnePermittedAction: boolean; hasAtLeastOnePermittedAction: boolean;
@ -220,7 +221,7 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
if (!data?.currentValue) return; if (!data?.currentValue) return;
if (data.currentValue.length < 1) { if (data.currentValue.length < 1) {
this.list.totalCount = this.recordsTotal; this.list().totalCount = this.recordsTotal();
} }
this.data = data.currentValue.map((record: any, index: number) => { this.data = data.currentValue.map((record: any, index: number) => {
@ -278,8 +279,9 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
} }
onSelect({ selected }) { onSelect({ selected }) {
this.selected.splice(0, this.selected.length); const selectedValue = this.selected();
this.selected.push(...selected); this.selected().splice(0, selectedValue.length);
selectedValue.push(...selected);
this.selectionChange.emit(selected); this.selectionChange.emit(selected);
} }
@ -299,12 +301,12 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
} }
private shouldHandleScroll(): boolean { private shouldHandleScroll(): boolean {
return this.infiniteScroll && !this.isLoading; return this.infiniteScroll() && !this.isLoading();
} }
private isNearScrollBottom(element: HTMLElement): boolean { private isNearScrollBottom(element: HTMLElement): boolean {
const { offsetHeight, scrollTop, scrollHeight } = element; const { offsetHeight, scrollTop, scrollHeight } = element;
return offsetHeight + scrollTop >= scrollHeight - this.scrollThreshold; return offsetHeight + scrollTop >= scrollHeight - this.scrollThreshold();
} }
private triggerLoadMore(): void { private triggerLoadMore(): void {
@ -312,9 +314,10 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
} }
getTableHeight() { getTableHeight() {
if (!this.infiniteScroll) return 'auto'; if (!this.infiniteScroll()) return 'auto';
return this.tableHeight ? `${this.tableHeight}px` : 'auto'; const tableHeight = this.tableHeight();
return tableHeight ? `${tableHeight}px` : 'auto';
} }
toggleExpandRow(row: R): void { toggleExpandRow(row: R): void {
@ -325,8 +328,8 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
} }
ngAfterViewInit(): void { ngAfterViewInit(): void {
if (!this.infiniteScroll) { if (!this.infiniteScroll()) {
this.list?.requestStatus$?.pipe(filter(status => status === 'loading')).subscribe(() => { this.list()?.requestStatus$?.pipe(filter(status => status === 'loading')).subscribe(() => {
this.data = []; this.data = [];
this.cdr.markForCheck(); this.cdr.markForCheck();
}); });

2
npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.html

@ -1,7 +1,7 @@
@if (actionList.length > 1) { @if (actionList.length > 1) {
<div ngbDropdown container="body" class="d-inline-block"> <div ngbDropdown container="body" class="d-inline-block">
<button class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" ngbDropdownToggle> <button class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" ngbDropdownToggle>
<i [class]="icon" [class.me-1]="icon"></i>{{ text | abpLocalization }} <i [class]="icon()" [class.me-1]="icon()"></i>{{ text() | abpLocalization }}
</button> </button>
<div ngbDropdownMenu> <div ngbDropdownMenu>
@for (action of actionList; track action.text) { @for (action of actionList; track action.text) {

5
npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.ts

@ -3,6 +3,7 @@ import {
Component, Component,
Input, Input,
TrackByFunction, TrackByFunction,
input
} from '@angular/core'; } from '@angular/core';
import { EntityAction, EntityActionList } from '../../models/entity-actions'; import { EntityAction, EntityActionList } from '../../models/entity-actions';
import { EXTENSIONS_ACTION_TYPE } from '../../tokens/extensions.token'; import { EXTENSIONS_ACTION_TYPE } from '../../tokens/extensions.token';
@ -33,11 +34,11 @@ import { NgTemplateOutlet } from '@angular/common';
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class GridActionsComponent<R = any> extends AbstractActionsComponent<EntityActionList<R>> { export class GridActionsComponent<R = any> extends AbstractActionsComponent<EntityActionList<R>> {
@Input() icon = 'fa fa-cog'; readonly icon = input('fa fa-cog');
@Input() readonly index?: number; @Input() readonly index?: number;
@Input() text = ''; readonly text = input('');
readonly trackByFn: TrackByFunction<EntityAction<R>> = (_, item) => item.text; readonly trackByFn: TrackByFunction<EntityAction<R>> = (_, item) => item.text;

21
npm/ng-packs/packages/components/extensible/src/lib/directives/prop-data.directive.ts

@ -1,13 +1,14 @@
/* eslint-disable @angular-eslint/no-input-rename */ /* eslint-disable @angular-eslint/no-input-rename */
import { import {
Directive, Directive,
Injector, Injector,
Input, Input,
OnChanges, OnChanges,
OnDestroy, OnDestroy,
TemplateRef, TemplateRef,
ViewContainerRef, ViewContainerRef,
inject inject,
input
} from '@angular/core'; } from '@angular/core';
import { PropData, PropList } from '../models/props'; import { PropData, PropList } from '../models/props';
@ -22,7 +23,7 @@ export class PropDataDirective<L extends PropList<any>>
private tempRef = inject<TemplateRef<any>>(TemplateRef); private tempRef = inject<TemplateRef<any>>(TemplateRef);
private vcRef = inject(ViewContainerRef); private vcRef = inject(ViewContainerRef);
@Input('abpPropDataFromList') propList?: L; readonly propList = input<L>(undefined, { alias: "abpPropDataFromList" });
@Input('abpPropDataWithRecord') record!: InferredData<L>['record']; @Input('abpPropDataWithRecord') record!: InferredData<L>['record'];

20
npm/ng-packs/packages/components/extensible/src/lib/models/actions.ts

@ -1,4 +1,4 @@
import { InjectionToken, InjectOptions, Type } from '@angular/core'; import { InjectionToken, InjectOptions, InputSignal, isSignal, Type } from '@angular/core';
import { LinkedList } from '@abp/utils'; import { LinkedList } from '@abp/utils';
export abstract class ActionList<R = any, A = Action<R>> extends LinkedList<A> {} export abstract class ActionList<R = any, A = Action<R>> extends LinkedList<A> {}
@ -10,18 +10,26 @@ export abstract class ActionData<R = any> {
flags?: InjectOptions, flags?: InjectOptions,
) => T; ) => T;
index?: number; index?: number;
abstract record: R; abstract record: R | InputSignal<R>;
get data(): ReadonlyActionData<R> { get data(): ReadonlyActionData<R> {
return { return {
getInjected: this.getInjected, getInjected: this.getInjected,
index: this.index, index: this.index,
record: this.record, record: isSignal(this.record) ? this.record() : this.record,
}; };
} }
} }
export type ReadonlyActionData<R = any> = Readonly<Omit<ActionData<R>, 'data'>>; export type ReadonlyActionData<R = any> = Readonly<{
getInjected: <T>(
token: Type<T> | InjectionToken<T>,
notFoundValue?: T,
flags?: InjectOptions,
) => T;
index?: number;
record: R;
}>;
export abstract class Action<R = any> { export abstract class Action<R = any> {
constructor( constructor(
@ -33,8 +41,8 @@ export abstract class Action<R = any> {
) {} ) {}
} }
export type ActionCallback<T, R = any> = (data: Omit<ActionData<T>, 'data'>) => R; export type ActionCallback<T, R = any> = (data: ReadonlyActionData<T>) => R;
export type ActionPredicate<T> = (data?: Omit<ActionData<T>, 'data'>) => boolean; export type ActionPredicate<T> = (data?: ReadonlyActionData<T>) => boolean;
export abstract class ActionsFactory<C extends Actions<any>> { export abstract class ActionsFactory<C extends Actions<any>> {
protected abstract _ctor: Type<C>; protected abstract _ctor: Type<C>;

35
npm/ng-packs/packages/components/page/src/page-part.directive.ts

@ -1,17 +1,18 @@
import { import {
Directive, Directive,
TemplateRef, TemplateRef,
ViewContainerRef, ViewContainerRef,
Input, Input,
InjectionToken, InjectionToken,
OnInit, OnInit,
OnDestroy, OnDestroy,
Injector, Injector,
OnChanges, OnChanges,
SimpleChanges, SimpleChanges,
SimpleChange, SimpleChange,
inject inject,
} from '@angular/core'; input
} from '@angular/core';
import { Observable, Subscription, of } from 'rxjs'; import { Observable, Subscription, of } from 'rxjs';
export interface PageRenderStrategy { export interface PageRenderStrategy {
@ -36,7 +37,7 @@ export class PagePartDirective implements OnInit, OnDestroy, OnChanges {
type!: string; type!: string;
subscription!: Subscription; subscription!: Subscription;
@Input('abpPagePartContext') context: any; readonly context = input<any>(undefined, { alias: "abpPagePartContext" });
@Input() set abpPagePart(type: string) { @Input() set abpPagePart(type: string) {
this.type = type; this.type = type;
this.createRenderStream(type); this.createRenderStream(type);
@ -60,7 +61,7 @@ export class PagePartDirective implements OnInit, OnDestroy, OnChanges {
ngOnInit() { ngOnInit() {
if (this.renderLogic?.onInit) { if (this.renderLogic?.onInit) {
this.renderLogic.onInit(this.type, this.injector, this.context); this.renderLogic.onInit(this.type, this.injector, this.context());
} }
} }
@ -68,7 +69,7 @@ export class PagePartDirective implements OnInit, OnDestroy, OnChanges {
this.clearSubscription(); this.clearSubscription();
if (this.renderLogic?.onDestroy) { if (this.renderLogic?.onDestroy) {
this.renderLogic.onDestroy(this.type, this.injector, this.context); this.renderLogic.onDestroy(this.type, this.injector, this.context());
} }
} }

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

@ -15,7 +15,7 @@
@if (customBreadcrumb) { @if (customBreadcrumb) {
<ng-content select="abp-page-breadcrumb-container"></ng-content> <ng-content select="abp-page-breadcrumb-container"></ng-content>
} @else { } @else {
@if (breadcrumb) { @if (breadcrumb()) {
<div class="col-lg-auto ps-lg-0" *abpPagePart="pageParts.breadcrumb"> <div class="col-lg-auto ps-lg-0" *abpPagePart="pageParts.breadcrumb">
<abp-breadcrumb></abp-breadcrumb> <abp-breadcrumb></abp-breadcrumb>
</div> </div>

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

@ -1,4 +1,4 @@
import { Component, Input, ViewEncapsulation, ContentChild } from '@angular/core'; import { Component, Input, ViewEncapsulation, ContentChild, input } from '@angular/core';
import { import {
PageTitleContainerComponent, PageTitleContainerComponent,
PageBreadcrumbContainerComponent, PageBreadcrumbContainerComponent,
@ -29,7 +29,7 @@ export class PageComponent {
return this._toolbarData; return this._toolbarData;
} }
@Input() breadcrumb = true; readonly breadcrumb = input(true);
pageParts = { pageParts = {
title: PageParts.title, title: PageParts.title,
@ -46,7 +46,7 @@ export class PageComponent {
return !!( return !!(
this.title || this.title ||
this.toolbarVisible || this.toolbarVisible ||
this.breadcrumb || this.breadcrumb() ||
this.customTitle || this.customTitle ||
this.customBreadcrumb || this.customBreadcrumb ||
this.customToolbar || this.customToolbar ||

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

@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, inject, Input } from '@angular/core'; import { ChangeDetectorRef, Component, inject, Input, 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!
@ -14,20 +14,17 @@ export class AbstractNgModelComponent<T = any, U = T> implements ControlValueAcc
@Input() @Input()
disabled?: boolean; disabled?: boolean;
@Input() readonly readonly = input<boolean>(undefined);
readonly?: boolean;
@Input() readonly valueFn = input<(value: U, previousValue?: T) => T>(value => value as any as T);
valueFn: (value: U, previousValue?: T) => T = value => value as any as T;
@Input() readonly valueLimitFn = input<(value: T, previousValue?: T) => any>(value => false);
valueLimitFn: (value: T, previousValue?: T) => any = value => false;
@Input() @Input()
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);
if (this.valueLimitFn(value, this._value) !== false || this.readonly) return; if (this.valueLimitFn()(value, this._value) !== false || this.readonly()) return;
this._value = value; this._value = value;
this.notifyValueChange(); this.notifyValueChange();
@ -48,7 +45,7 @@ export class AbstractNgModelComponent<T = any, U = T> implements ControlValueAcc
} }
writeValue(value: T): void { writeValue(value: T): void {
this._value = this.valueLimitFn(value, this._value) || value; this._value = this.valueLimitFn()(value, this._value) || value;
this.cdRef.markForCheck(); this.cdRef.markForCheck();
} }

6
npm/ng-packs/packages/core/src/lib/directives/debounce.directive.ts

@ -1,4 +1,4 @@
import { Directive, ElementRef, EventEmitter, Input, OnInit, Output, inject } from '@angular/core'; import { Directive, ElementRef, EventEmitter, OnInit, Output, inject, input } from '@angular/core';
import { fromEvent } from 'rxjs'; import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators'; import { debounceTime } from 'rxjs/operators';
import { SubscriptionService } from '../services/subscription.service'; import { SubscriptionService } from '../services/subscription.service';
@ -11,13 +11,13 @@ export class InputEventDebounceDirective implements OnInit {
private el = inject(ElementRef); private el = inject(ElementRef);
private subscription = inject(SubscriptionService); private subscription = inject(SubscriptionService);
@Input() debounce = 300; readonly debounce = input(300);
@Output('input.debounce') readonly debounceEvent = new EventEmitter<Event>(); @Output('input.debounce') readonly debounceEvent = new EventEmitter<Event>();
ngOnInit(): void { ngOnInit(): void {
const input$ = fromEvent<InputEvent>(this.el.nativeElement, 'input').pipe( const input$ = fromEvent<InputEvent>(this.el.nativeElement, 'input').pipe(
debounceTime(this.debounce), debounceTime(this.debounce()),
); );
this.subscription.addOne(input$, (event: Event) => { this.subscription.addOne(input$, (event: Event) => {

63
npm/ng-packs/packages/core/src/lib/directives/for.directive.ts

@ -1,7 +1,6 @@
import { import {
Directive, Directive,
EmbeddedViewRef, EmbeddedViewRef,
Input,
IterableChangeRecord, IterableChangeRecord,
IterableChanges, IterableChanges,
IterableDiffer, IterableDiffer,
@ -11,6 +10,7 @@ import {
TrackByFunction, TrackByFunction,
ViewContainerRef, ViewContainerRef,
inject, inject,
input
} from '@angular/core'; } from '@angular/core';
import clone from 'just-clone'; import clone from 'just-clone';
import compare from 'just-compare'; import compare from 'just-compare';
@ -42,29 +42,21 @@ export class ForDirective implements OnChanges {
private differs = inject(IterableDiffers); private differs = inject(IterableDiffers);
// eslint-disable-next-line @angular-eslint/no-input-rename // eslint-disable-next-line @angular-eslint/no-input-rename
@Input('abpForOf') readonly items = input.required<any[]>({ alias: "abpForOf" });
items!: any[];
@Input('abpForOrderBy') readonly orderBy = input<string>(undefined, { alias: "abpForOrderBy" });
orderBy?: string;
@Input('abpForOrderDir') readonly orderDir = input<'ASC' | 'DESC'>(undefined, { alias: "abpForOrderDir" });
orderDir?: 'ASC' | 'DESC';
@Input('abpForFilterBy') readonly filterBy = input<string>(undefined, { alias: "abpForFilterBy" });
filterBy?: string;
@Input('abpForFilterVal') readonly filterVal = input<any>(undefined, { alias: "abpForFilterVal" });
filterVal: any;
@Input('abpForTrackBy') readonly trackBy = input<TrackByFunction<any>>(undefined, { alias: "abpForTrackBy" });
trackBy?: TrackByFunction<any>;
@Input('abpForCompareBy') readonly compareBy = input<CompareFn>(undefined, { alias: "abpForCompareBy" });
compareBy?: CompareFn;
@Input('abpForEmptyRef') readonly emptyRef = input<TemplateRef<any>>(undefined, { alias: "abpForEmptyRef" });
emptyRef?: TemplateRef<any>;
private differ!: IterableDiffer<any> | null; private differ!: IterableDiffer<any> | null;
private lastItemsRef: any[] | null = null; private lastItemsRef: any[] | null = null;
@ -72,11 +64,11 @@ export class ForDirective implements OnChanges {
private isShowEmptyRef!: boolean; private isShowEmptyRef!: boolean;
get compareFn(): CompareFn { get compareFn(): CompareFn {
return this.compareBy || compare; return this.compareBy() || compare;
} }
get trackByFn(): TrackByFunction<any> { get trackByFn(): TrackByFunction<any> {
return this.trackBy || ((index: number, item: any) => (item as any).id || index); return this.trackBy() || ((index: number, item: any) => (item as any).id || index);
} }
private iterateOverAppliedOperations(changes: IterableChanges<any>) { private iterateOverAppliedOperations(changes: IterableChanges<any>) {
@ -91,7 +83,7 @@ export class ForDirective implements OnChanges {
if (record.previousIndex == null) { if (record.previousIndex == null) {
const view = this.vcRef.createEmbeddedView( const view = this.vcRef.createEmbeddedView(
this.tempRef, this.tempRef,
new AbpForContext(null, -1, -1, this.items), new AbpForContext(null, -1, -1, this.items()),
currentIndex || 0, currentIndex || 0,
); );
@ -120,7 +112,7 @@ export class ForDirective implements OnChanges {
const viewRef = this.vcRef.get(i) as EmbeddedViewRef<AbpForContext>; const viewRef = this.vcRef.get(i) as EmbeddedViewRef<AbpForContext>;
viewRef.context.index = i; viewRef.context.index = i;
viewRef.context.count = l; viewRef.context.count = l;
viewRef.context.list = this.items; viewRef.context.list = this.items();
} }
changes.forEachIdentityChange((record: IterableChangeRecord<any>) => { changes.forEachIdentityChange((record: IterableChangeRecord<any>) => {
@ -132,9 +124,10 @@ export class ForDirective implements OnChanges {
} }
private projectItems(items: any[]): void { private projectItems(items: any[]): void {
if (!items.length && this.emptyRef) { const emptyRef = this.emptyRef();
if (!items.length && emptyRef) {
this.vcRef.clear(); this.vcRef.clear();
this.vcRef.createEmbeddedView(this.emptyRef).rootNodes; this.vcRef.createEmbeddedView(emptyRef).rootNodes;
this.isShowEmptyRef = true; this.isShowEmptyRef = true;
this.differ = null; this.differ = null;
this.lastItemsRef = null; this.lastItemsRef = null;
@ -142,7 +135,7 @@ export class ForDirective implements OnChanges {
return; return;
} }
if (this.emptyRef && this.isShowEmptyRef) { if (emptyRef && this.isShowEmptyRef) {
this.vcRef.clear(); this.vcRef.clear();
this.isShowEmptyRef = false; this.isShowEmptyRef = false;
} }
@ -162,7 +155,7 @@ export class ForDirective implements OnChanges {
} }
private sortItems(items: any[]) { private sortItems(items: any[]) {
const orderBy = this.orderBy; const orderBy = this.orderBy();
if (orderBy) { if (orderBy) {
items.sort((a, b) => (a[orderBy] > b[orderBy] ? 1 : a[orderBy] < b[orderBy] ? -1 : 0)); items.sort((a, b) => (a[orderBy] > b[orderBy] ? 1 : a[orderBy] < b[orderBy] ? -1 : 0));
} else { } else {
@ -171,28 +164,30 @@ export class ForDirective implements OnChanges {
} }
ngOnChanges() { ngOnChanges() {
if (!this.items) return; const itemsValue = this.items();
if (!itemsValue) return;
// Recreate differ if items array reference changed // Recreate differ if items array reference changed
if (this.lastItemsRef !== this.items) { if (this.lastItemsRef !== itemsValue) {
this.differ = null; this.differ = null;
this.lastItemsRef = this.items; this.lastItemsRef = itemsValue;
} }
let items = clone(this.items) as any[]; let items = clone(itemsValue) as any[];
if (!Array.isArray(items)) return; if (!Array.isArray(items)) return;
const compareFn = this.compareFn; const compareFn = this.compareFn;
const filterBy = this.filterBy; const filterBy = this.filterBy();
const filterVal = this.filterVal();
if ( if (
typeof filterBy !== 'undefined' && typeof filterBy !== 'undefined' &&
typeof this.filterVal !== 'undefined' && typeof filterVal !== 'undefined' &&
this.filterVal !== '' filterVal !== ''
) { ) {
items = items.filter(item => compareFn(item[filterBy], this.filterVal)); items = items.filter(item => compareFn(item[filterBy], this.filterVal()));
} }
switch (this.orderDir) { switch (this.orderDir()) {
case 'ASC': case 'ASC':
this.sortItems(items); this.sortItems(items);
this.projectItems(items); this.projectItems(items);

31
npm/ng-packs/packages/core/src/lib/directives/form-submit.directive.ts

@ -1,12 +1,12 @@
import { import {
ChangeDetectorRef, ChangeDetectorRef,
Directive, Directive,
ElementRef, ElementRef,
EventEmitter, EventEmitter,
Input, OnInit,
OnInit, Output,
Output, inject,
inject input
} from '@angular/core'; } from '@angular/core';
import { FormGroupDirective, UntypedFormControl, UntypedFormGroup } from '@angular/forms'; import { FormGroupDirective, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { fromEvent } from 'rxjs'; import { fromEvent } from 'rxjs';
@ -27,15 +27,12 @@ export class FormSubmitDirective implements OnInit {
private cdRef = inject(ChangeDetectorRef); private cdRef = inject(ChangeDetectorRef);
private subscription = inject(SubscriptionService); private subscription = inject(SubscriptionService);
@Input() readonly debounce = input(200);
debounce = 200;
// TODO: Remove unused input // TODO: Remove unused input
@Input() readonly notValidateOnSubmit = input<string | boolean>(undefined);
notValidateOnSubmit?: string | boolean;
@Input() readonly markAsDirtyWhenSubmit = input(true);
markAsDirtyWhenSubmit = true;
@Output() readonly ngSubmit = new EventEmitter(); @Output() readonly ngSubmit = new EventEmitter();
@ -43,7 +40,7 @@ export class FormSubmitDirective implements OnInit {
ngOnInit() { ngOnInit() {
this.subscription.addOne(this.formGroupDirective.ngSubmit, () => { this.subscription.addOne(this.formGroupDirective.ngSubmit, () => {
if (this.markAsDirtyWhenSubmit) { if (this.markAsDirtyWhenSubmit()) {
this.markAsDirty(); this.markAsDirty();
} }
@ -51,7 +48,7 @@ export class FormSubmitDirective implements OnInit {
}); });
const keyup$ = fromEvent<KeyboardEvent>(this.host.nativeElement as HTMLElement, 'keyup').pipe( const keyup$ = fromEvent<KeyboardEvent>(this.host.nativeElement as HTMLElement, 'keyup').pipe(
debounceTime(this.debounce), debounceTime(this.debounce()),
filter(event => !(event.target instanceof HTMLTextAreaElement)), filter(event => !(event.target instanceof HTMLTextAreaElement)),
filter(event => event && event.key === 'Enter'), filter(event => event && event.key === 'Enter'),
); );

10
npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts

@ -2,12 +2,12 @@ import {
AfterViewInit, AfterViewInit,
ChangeDetectorRef, ChangeDetectorRef,
Directive, Directive,
Input,
OnChanges, OnChanges,
OnDestroy, OnDestroy,
TemplateRef, TemplateRef,
ViewContainerRef, ViewContainerRef,
inject, inject,
input
} from '@angular/core'; } from '@angular/core';
import { ReplaySubject, Subscription } from 'rxjs'; import { ReplaySubject, Subscription } from 'rxjs';
import { distinctUntilChanged, take } from 'rxjs/operators'; import { distinctUntilChanged, take } from 'rxjs/operators';
@ -25,9 +25,9 @@ export class PermissionDirective implements OnDestroy, OnChanges, AfterViewInit
private cdRef = inject(ChangeDetectorRef); private cdRef = inject(ChangeDetectorRef);
queue = inject<QueueManager>(QUEUE_MANAGER); queue = inject<QueueManager>(QUEUE_MANAGER);
@Input('abpPermission') condition: string | undefined; readonly condition = input<string | undefined>(undefined, { alias: "abpPermission" });
@Input('abpPermissionRunChangeDetection') runChangeDetection = true; readonly runChangeDetection = input(true, { alias: "abpPermissionRunChangeDetection" });
subscription!: Subscription; subscription!: Subscription;
@ -41,14 +41,14 @@ export class PermissionDirective implements OnDestroy, OnChanges, AfterViewInit
} }
this.subscription = this.permissionService this.subscription = this.permissionService
.getGrantedPolicy$(this.condition || '') .getGrantedPolicy$(this.condition() || '')
.pipe(distinctUntilChanged()) .pipe(distinctUntilChanged())
.subscribe(isGranted => { .subscribe(isGranted => {
this.vcRef.clear(); this.vcRef.clear();
if (isGranted && this.templateRef) { if (isGranted && this.templateRef) {
this.vcRef.createEmbeddedView(this.templateRef); this.vcRef.createEmbeddedView(this.templateRef);
} }
if (this.runChangeDetection) { if (this.runChangeDetection()) {
if (!this.rendered) { if (!this.rendered) {
this.cdrSubject.next(); this.cdrSubject.next();
} else { } else {

70
npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts

@ -1,14 +1,14 @@
import { import {
Directive, Directive,
Injector, Injector,
Input, OnChanges,
OnChanges, OnInit,
OnInit, SimpleChanges,
SimpleChanges, TemplateRef,
TemplateRef, Type,
Type, ViewContainerRef,
ViewContainerRef, inject,
inject input
} from '@angular/core'; } from '@angular/core';
import compare from 'just-compare'; import compare from 'just-compare';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
@ -29,8 +29,7 @@ export class ReplaceableTemplateDirective implements OnInit, OnChanges {
private replaceableComponents = inject(ReplaceableComponentsService); private replaceableComponents = inject(ReplaceableComponentsService);
private subscription = inject(SubscriptionService); private subscription = inject(SubscriptionService);
@Input('abpReplaceableTemplate') readonly data = input.required<ReplaceableComponents.ReplaceableTemplateDirectiveInput<any, any>>({ alias: "abpReplaceableTemplate" });
data!: ReplaceableComponents.ReplaceableTemplateDirectiveInput<any, any>;
providedData = { providedData = {
inputs: {}, inputs: {},
@ -59,7 +58,7 @@ export class ReplaceableTemplateDirective implements OnInit, OnChanges {
ngOnInit() { ngOnInit() {
const component$ = this.replaceableComponents const component$ = this.replaceableComponents
.get$(this.data.componentKey) .get$(this.data().componentKey)
.pipe( .pipe(
filter( filter(
(res = {} as ReplaceableComponents.ReplaceableComponent) => (res = {} as ReplaceableComponents.ReplaceableComponent) =>
@ -102,25 +101,26 @@ export class ReplaceableTemplateDirective implements OnInit, OnChanges {
} }
setDefaultComponentInputs() { setDefaultComponentInputs() {
if (!this.defaultComponentRef || (!this.data.inputs && !this.data.outputs)) return; const data = this.data();
if (!this.defaultComponentRef || (!data.inputs && !data.outputs)) return;
if (this.data.inputs) {
for (const key in this.data.inputs) { if (data.inputs) {
if (Object.prototype.hasOwnProperty.call(this.data.inputs, key)) { for (const key in data.inputs) {
if (!compare(this.defaultComponentRef[key], this.data.inputs[key].value)) { if (Object.prototype.hasOwnProperty.call(data.inputs, key)) {
this.defaultComponentRef[key] = this.data.inputs[key].value; if (!compare(this.defaultComponentRef[key], data.inputs[key].value)) {
this.defaultComponentRef[key] = data.inputs[key].value;
} }
} }
} }
} }
if (this.data.outputs) { if (data.outputs) {
for (const key in this.data.outputs) { for (const key in data.outputs) {
if (Object.prototype.hasOwnProperty.call(this.data.outputs, key)) { if (Object.prototype.hasOwnProperty.call(data.outputs, key)) {
if (!this.defaultComponentSubscriptions[key]) { if (!this.defaultComponentSubscriptions[key]) {
this.defaultComponentSubscriptions[key] = this.defaultComponentRef[key].subscribe( this.defaultComponentSubscriptions[key] = this.defaultComponentRef[key].subscribe(
(value: any) => { (value: any) => {
this.data.outputs?.[key](value); this.data().outputs?.[key](value);
}, },
); );
} }
@ -130,24 +130,26 @@ export class ReplaceableTemplateDirective implements OnInit, OnChanges {
} }
setProvidedData() { setProvidedData() {
this.providedData = { outputs: {}, ...this.data, inputs: {} }; this.providedData = { outputs: {}, ...this.data(), inputs: {} };
if (!this.data.inputs) return; const data = this.data();
if (!data.inputs) return;
Object.defineProperties(this.providedData.inputs, { Object.defineProperties(this.providedData.inputs, {
...Object.keys(this.data.inputs).reduce( ...Object.keys(data.inputs).reduce(
(acc, key) => ({ (acc, key) => ({
...acc, ...acc,
[key]: { [key]: {
enumerable: true, enumerable: true,
configurable: true, configurable: true,
get: () => this.data.inputs?.[key]?.value, get: () => this.data().inputs?.[key]?.value,
...(this.data.inputs?.[key]?.twoWay && { ...(this.data().inputs?.[key]?.twoWay && {
set: (newValue: any) => { set: (newValue: any) => {
if (this.data.inputs?.[key]) { const dataValue = this.data();
this.data.inputs[key].value = newValue; if (dataValue.inputs?.[key]) {
dataValue.inputs[key].value = newValue;
} }
if (this.data.outputs?.[`${key}Change`]) { if (dataValue.outputs?.[`${key}Change`]) {
this.data.outputs[`${key}Change`](newValue); dataValue.outputs[`${key}Change`](newValue);
} }
}, },
}), }),

2
npm/ng-packs/packages/setting-management/package.json

@ -12,7 +12,7 @@
"tslib": "^2.0.0" "tslib": "^2.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/aria": "~21.1.0" "@angular/aria": "~21.0.0"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

6
npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.html

@ -30,7 +30,7 @@
</a> </a>
<div #routeContainer class="dropdown-menu border-0 shadow-sm" <div #routeContainer class="dropdown-menu border-0 shadow-sm"
(click)="$event.preventDefault(); $event.stopPropagation()" (click)="$event.preventDefault(); $event.stopPropagation()"
[class.d-block]="smallScreen && rootDropdownExpand[route.name]"> [class.d-block]="smallScreen() && rootDropdownExpand[route.name]">
<ng-container *ngTemplateOutlet="forTemplate; context: { $implicit: route }" /> <ng-container *ngTemplateOutlet="forTemplate; context: { $implicit: route }" />
</div> </div>
</li> </li>
@ -60,7 +60,7 @@
<div class="dropdown-submenu" ngbDropdown #dropdownSubmenu="ngbDropdown" placement="right-top" [autoClose]="true" <div class="dropdown-submenu" ngbDropdown #dropdownSubmenu="ngbDropdown" placement="right-top" [autoClose]="true"
*abpPermission="child.requiredPolicy"> *abpPermission="child.requiredPolicy">
<div ngbDropdownToggle [class.dropdown-toggle]="false"> <div ngbDropdownToggle [class.dropdown-toggle]="false">
<a abpEllipsis="210px" [abpEllipsisEnabled]="!smallScreen" role="button" <a abpEllipsis="210px" [abpEllipsisEnabled]="!smallScreen()" role="button"
class="btn d-block text-start dropdown-toggle"> class="btn d-block text-start dropdown-toggle">
@if (child.iconClass) { @if (child.iconClass) {
<i [class]="child.iconClass"></i> <i [class]="child.iconClass"></i>
@ -69,7 +69,7 @@
</a> </a>
</div> </div>
<div #childrenContainer class="dropdown-menu dropdown-menu-start border-0 shadow-sm" <div #childrenContainer class="dropdown-menu dropdown-menu-start border-0 shadow-sm"
[class.d-block]="smallScreen && dropdownSubmenu.isOpen()"> [class.d-block]="smallScreen() && dropdownSubmenu.isOpen()">
<ng-container *ngTemplateOutlet="forTemplate; context: { $implicit: child }" /> <ng-container *ngTemplateOutlet="forTemplate; context: { $implicit: child }" />
</div> </div>
</div> </div>

4
npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts

@ -10,11 +10,11 @@ import {
Component, Component,
ElementRef, ElementRef,
inject, inject,
Input,
QueryList, QueryList,
Renderer2, Renderer2,
TrackByFunction, TrackByFunction,
ViewChildren, ViewChildren,
input
} from '@angular/core'; } from '@angular/core';
import { NgTemplateOutlet, AsyncPipe } from '@angular/common'; import { NgTemplateOutlet, AsyncPipe } from '@angular/common';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
@ -39,7 +39,7 @@ export class RoutesComponent {
public readonly routesService = inject(RoutesService); public readonly routesService = inject(RoutesService);
protected renderer = inject(Renderer2); protected renderer = inject(Renderer2);
@Input() smallScreen?: boolean; readonly smallScreen = input<boolean>(undefined);
@ViewChildren('childrenContainer') childrenContainers!: QueryList<ElementRef<HTMLDivElement>>; @ViewChildren('childrenContainer') childrenContainers!: QueryList<ElementRef<HTMLDivElement>>;

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

@ -9,6 +9,7 @@ import {
Renderer2, Renderer2,
ViewChild, ViewChild,
inject, inject,
input
} from '@angular/core'; } from '@angular/core';
import { ABP, StopPropagationDirective } from '@abp/ng.core'; import { ABP, StopPropagationDirective } from '@abp/ng.core';
@ -17,11 +18,11 @@ import { ABP, StopPropagationDirective } from '@abp/ng.core';
template: ` template: `
<button <button
#button #button
[id]="buttonId" [id]="buttonId()"
[attr.type]="buttonType" [attr.type]="buttonType()"
[attr.form]="formName" [attr.form]="formName()"
[class]="buttonClass" [class]="buttonClass()"
[disabled]="loading || disabled" [disabled]="loading || disabled()"
(click.stop)="click.next($event); abpClick.next($event)" (click.stop)="click.next($event); abpClick.next($event)"
(focus)="focus.next($event); abpFocus.next($event)" (focus)="focus.next($event); abpFocus.next($event)"
(blur)="blur.next($event); abpBlur.next($event)" (blur)="blur.next($event); abpBlur.next($event)"
@ -34,29 +35,22 @@ import { ABP, StopPropagationDirective } from '@abp/ng.core';
export class ButtonComponent implements OnInit { export class ButtonComponent implements OnInit {
private renderer = inject(Renderer2); private renderer = inject(Renderer2);
@Input() readonly buttonId = input('');
buttonId = '';
@Input() readonly buttonClass = input('btn btn-primary');
buttonClass = 'btn btn-primary';
@Input() readonly buttonType = input('button');
buttonType = 'button';
@Input() readonly formName = input<string>(undefined);
formName?: string = undefined;
@Input() readonly iconClass = input<string>(undefined);
iconClass?: string;
@Input() @Input()
loading = false; loading = false;
@Input() readonly disabled = input<boolean | undefined>(false);
disabled: boolean | undefined = false;
@Input() readonly attributes = input<ABP.Dictionary<string>>(undefined);
attributes?: ABP.Dictionary<string>;
@Output() readonly click = new EventEmitter<MouseEvent>(); @Output() readonly click = new EventEmitter<MouseEvent>();
@ -74,14 +68,16 @@ export class ButtonComponent implements OnInit {
buttonRef!: ElementRef<HTMLButtonElement>; buttonRef!: ElementRef<HTMLButtonElement>;
get icon(): string { get icon(): string {
return `${this.loading ? 'fa fa-spinner fa-spin' : this.iconClass || 'd-none'}`; return `${this.loading ? 'fa fa-spinner fa-spin' : this.iconClass() || 'd-none'}`;
} }
ngOnInit() { ngOnInit() {
if (this.attributes) { const attributes = this.attributes();
Object.keys(this.attributes).forEach(key => { if (attributes) {
if (this.attributes?.[key]) { Object.keys(attributes).forEach(key => {
this.renderer.setAttribute(this.buttonRef.nativeElement, key, this.attributes[key]); const attributesValue = this.attributes();
if (attributesValue?.[key]) {
this.renderer.setAttribute(this.buttonRef.nativeElement, key, attributesValue[key]);
} }
}); });
} }

8
npm/ng-packs/packages/theme-shared/src/lib/components/card/card-body.component.ts

@ -1,13 +1,13 @@
import { Component, HostBinding, Input } from '@angular/core'; import { Component, HostBinding, input } from '@angular/core';
@Component({ @Component({
selector: 'abp-card-body', selector: 'abp-card-body',
template: ` <div [class]="cardBodyClass" [style]="cardBodyStyle"> template: ` <div [class]="cardBodyClass()" [style]="cardBodyStyle()">
<ng-content></ng-content> <ng-content></ng-content>
</div>`, </div>`,
}) })
export class CardBodyComponent { export class CardBodyComponent {
@HostBinding('class') componentClass = 'card-body'; @HostBinding('class') componentClass = 'card-body';
@Input() cardBodyClass: string; readonly cardBodyClass = input<string>(undefined);
@Input() cardBodyStyle: string; readonly cardBodyStyle = input<string>(undefined);
} }

8
npm/ng-packs/packages/theme-shared/src/lib/components/card/card-footer.component.ts

@ -1,9 +1,9 @@
import { Component, HostBinding, Input } from '@angular/core'; import { Component, HostBinding, input } from '@angular/core';
@Component({ @Component({
selector: 'abp-card-footer', selector: 'abp-card-footer',
template: ` template: `
<div [style]="cardFooterStyle" [class]="cardFooterClass"> <div [style]="cardFooterStyle()" [class]="cardFooterClass()">
<ng-content></ng-content> <ng-content></ng-content>
</div> </div>
`, `,
@ -12,6 +12,6 @@ import { Component, HostBinding, Input } from '@angular/core';
}) })
export class CardFooterComponent { export class CardFooterComponent {
@HostBinding('class') componentClass = 'card-footer'; @HostBinding('class') componentClass = 'card-footer';
@Input() cardFooterStyle: string; readonly cardFooterStyle = input<string>(undefined);
@Input() cardFooterClass: string; readonly cardFooterClass = input<string>(undefined);
} }

8
npm/ng-packs/packages/theme-shared/src/lib/components/card/card-header.component.ts

@ -1,9 +1,9 @@
import { Component, HostBinding, Input } from '@angular/core'; import { Component, HostBinding, input } from '@angular/core';
@Component({ @Component({
selector: 'abp-card-header', selector: 'abp-card-header',
template: ` template: `
<div [class]="cardHeaderClass" [style]="cardHeaderStyle"> <div [class]="cardHeaderClass()" [style]="cardHeaderStyle()">
<ng-content></ng-content> <ng-content></ng-content>
</div> </div>
`, `,
@ -12,6 +12,6 @@ import { Component, HostBinding, Input } from '@angular/core';
}) })
export class CardHeaderComponent { export class CardHeaderComponent {
@HostBinding('class') componentClass = 'card-header'; @HostBinding('class') componentClass = 'card-header';
@Input() cardHeaderClass: string; readonly cardHeaderClass = input<string>(undefined);
@Input() cardHeaderStyle: string; readonly cardHeaderStyle = input<string>(undefined);
} }

8
npm/ng-packs/packages/theme-shared/src/lib/components/card/card.component.ts

@ -1,14 +1,14 @@
import { Component, Input } from '@angular/core'; import { Component, input } from '@angular/core';
@Component({ @Component({
selector: 'abp-card', selector: 'abp-card',
template: ` <div class="card" [class]="cardClass" [style]="cardStyle"> template: ` <div class="card" [class]="cardClass()" [style]="cardStyle()">
<ng-content></ng-content> <ng-content></ng-content>
</div>`, </div>`,
imports: [], imports: [],
}) })
export class CardComponent { export class CardComponent {
@Input() cardClass: string; readonly cardClass = input<string>(undefined);
@Input() cardStyle: string; readonly cardStyle = input<string>(undefined);
} }

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

@ -1,4 +1,4 @@
import { Component, EventEmitter, forwardRef, Input, Output } from '@angular/core'; import { Component, EventEmitter, forwardRef, Input, Output, input } 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';
@ -9,15 +9,15 @@ import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core';
<input <input
type="checkbox" type="checkbox"
[(ngModel)]="value" [(ngModel)]="value"
[id]="checkboxId" [id]="checkboxId()"
[readonly]="checkboxReadonly" [readonly]="checkboxReadonly()"
[class]="checkboxClass" [class]="checkboxClass()"
[style]="checkboxStyle" [style]="checkboxStyle()"
(blur)="checkboxBlur.next()" (blur)="checkboxBlur.next()"
(focus)="checkboxFocus.next()" (focus)="checkboxFocus.next()"
/> />
@if (label) { @if (label) {
<label [class]="labelClass" [for]="checkboxId"> <label [class]="labelClass()" [for]="checkboxId()">
{{ label | abpLocalization }} {{ label | abpLocalization }}
</label> </label>
} }
@ -34,16 +34,13 @@ import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core';
}) })
export class FormCheckboxComponent extends AbstractNgModelComponent { export class FormCheckboxComponent extends AbstractNgModelComponent {
@Input() label?: string; @Input() label?: string;
@Input() labelClass = 'form-check-label'; readonly labelClass = input('form-check-label');
@Input() checkboxId!: string; readonly checkboxId = input.required<string>();
@Input() checkboxStyle: readonly checkboxStyle = input<{
| { [klass: string]: any;
[klass: string]: any; } | null | undefined>(undefined);
} readonly checkboxClass = input('form-check-input');
| null readonly checkboxReadonly = input(false);
| undefined;
@Input() checkboxClass = 'form-check-input';
@Input() checkboxReadonly = false;
@Output() checkboxBlur = new EventEmitter<void>(); @Output() checkboxBlur = new EventEmitter<void>();
@Output() checkboxFocus = new EventEmitter<void>(); @Output() checkboxFocus = new EventEmitter<void>();
} }

33
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 } from '@angular/core'; import { Component, EventEmitter, forwardRef, Input, Output, input } 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';
@ -7,17 +7,17 @@ import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core';
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
type="text" type="text"
[id]="inputId" [id]="inputId()"
[placeholder]="inputPlaceholder" [placeholder]="inputPlaceholder()"
[readonly]="inputReadonly" [readonly]="inputReadonly()"
[class]="inputClass" [class]="inputClass()"
[style]="inputStyle" [style]="inputStyle()"
(blur)="formBlur.next()" (blur)="formBlur.next()"
(focus)="formFocus.next()" (focus)="formFocus.next()"
[(ngModel)]="value" [(ngModel)]="value"
@ -34,18 +34,15 @@ import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core';
imports: [LocalizationPipe, FormsModule], imports: [LocalizationPipe, FormsModule],
}) })
export class FormInputComponent extends AbstractNgModelComponent { export class FormInputComponent extends AbstractNgModelComponent {
@Input() inputId!: string; readonly inputId = input.required<string>();
@Input() inputReadonly = false; readonly inputReadonly = input(false);
@Input() label = ''; @Input() label = '';
@Input() labelClass = 'form-label'; readonly labelClass = input('form-label');
@Input() inputPlaceholder = ''; readonly inputPlaceholder = input('');
@Input() inputStyle: readonly inputStyle = input<{
| { [klass: string]: any;
[klass: string]: any; } | null | undefined>(undefined);
} readonly inputClass = input('form-control');
| null
| undefined;
@Input() inputClass = 'form-control';
@Output() formBlur = new EventEmitter<void>(); @Output() formBlur = new EventEmitter<void>();
@Output() formFocus = new EventEmitter<void>(); @Output() formFocus = new EventEmitter<void>();
} }

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

@ -1,17 +1,17 @@
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit, inject } from '@angular/core'; import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit, inject, input } 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"
[style.width.vw]="progressLevel" [style.width.vw]="progressLevel"
[style]="{ [style]="{
'background-color': color, 'background-color': color(),
'box-shadow': boxShadow, 'box-shadow': boxShadow,
}" }"
></div> ></div>
@ -38,11 +38,9 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
return this._isLoading; return this._isLoading;
} }
@Input() readonly containerClass = input('abp-loader-bar');
containerClass = 'abp-loader-bar';
@Input() readonly color = input('#77b6ff');
color = '#77b6ff';
progressLevel = 0; progressLevel = 0;
@ -73,7 +71,7 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
}; };
get boxShadow(): string { get boxShadow(): string {
return `0 0 10px rgba(${this.color}, 0.5)`; return `0 0 10px rgba(${this.color()}, 0.5)`;
} }
ngOnInit() { ngOnInit() {

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

@ -2,7 +2,7 @@
<input <input
[type]="fieldTextType ? 'text' : 'password'" [type]="fieldTextType ? 'text' : 'password'"
class="form-control" class="form-control"
[id]="inputId" [id]="inputId()"
[(ngModel)]="value" [(ngModel)]="value"
/> />

6
npm/ng-packs/packages/theme-shared/src/lib/components/password/password.component.ts

@ -1,4 +1,4 @@
import { Component, forwardRef, Input } from '@angular/core'; import { Component, forwardRef, input } from '@angular/core';
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
import { AbstractNgModelComponent } from '@abp/ng.core'; import { AbstractNgModelComponent } from '@abp/ng.core';
@ -21,8 +21,8 @@ import { NgxValidateCoreModule } from '@ngx-validate/core';
], ],
}) })
export class PasswordComponent extends AbstractNgModelComponent { export class PasswordComponent extends AbstractNgModelComponent {
@Input() inputId!: string; readonly inputId = input.required<string>();
@Input() formControlName!: string; readonly formControlName = input.required<string>();
fieldTextType?: boolean; fieldTextType?: boolean;
toggleFieldTextType() { toggleFieldTextType() {

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

@ -1,9 +1,9 @@
<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'"
[@toastInOut]="toasts.length" [@toastInOut]="toasts.length"
> >

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

@ -1,4 +1,4 @@
import { Component, HostListener, Input, OnInit } from '@angular/core'; import { Component, HostListener, Input, OnInit, input } 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';
@ -18,29 +18,25 @@ export class ToastContainerComponent implements OnInit {
toasts = [] as Toaster.Toast[]; toasts = [] as Toaster.Toast[];
@Input() readonly top = input<string>(undefined);
top?: string;
@Input() @Input()
right = '30px'; right = '30px';
defaultRight = '30px'; defaultRight = '30px';
defaultMobileRight = '0'; defaultMobileRight = '0';
@Input() readonly bottom = input('30px');
bottom = '30px';
@Input() readonly left = input<string>(undefined);
left?: string;
@Input() readonly toastKey = input<string>(undefined);
toastKey?: string;
ngOnInit() { ngOnInit() {
this.setDefaultRight(); this.setDefaultRight();
this.toasts$.subscribe(toasts => { this.toasts$.subscribe(toasts => {
this.toasts = this.toastKey this.toasts = this.toastKey()
? toasts.filter(t => { ? toasts.filter(t => {
return t.options && t.options.containerKey !== this.toastKey; return t.options && t.options.containerKey !== this.toastKey();
}) })
: toasts; : toasts;
}); });

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

@ -3,15 +3,15 @@
<i class="bi icon" [class]="iconClass" aria-hidden="true"></i> <i class="bi icon" [class]="iconClass" aria-hidden="true"></i>
</div> </div>
<div class="abp-toast-content"> <div class="abp-toast-content">
@if (toast.options?.closable) { @if (toast().options?.closable) {
<button class="abp-toast-close-button" (click)="close()"> <button class="abp-toast-close-button" (click)="close()">
<i class="bi bi-x fs-4" aria-hidden="true"></i> <i class="bi bi-x fs-4" aria-hidden="true"></i>
</button> </button>
} }
<div class="abp-toast-title"> <div class="abp-toast-title">
{{ toast.title | abpLocalization: toast.options?.titleLocalizationParams }} {{ toast().title | abpLocalization: toast().options?.titleLocalizationParams }}
</div> </div>
<p class="abp-toast-message" <p class="abp-toast-message"
[innerHTML]="toast.message | abpLocalization: toast.options?.messageLocalizationParams"></p> [innerHTML]="toast().message | abpLocalization: toast().options?.messageLocalizationParams"></p>
</div> </div>
</div> </div>

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

@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, OnInit, Output, input } from '@angular/core';
import { Toaster } from '../../models/toaster'; import { Toaster } from '../../models/toaster';
import { LocalizationPipe } from '@abp/ng.core'; import { LocalizationPipe } from '@abp/ng.core';
@ -9,24 +9,24 @@ import { LocalizationPipe } from '@abp/ng.core';
imports: [LocalizationPipe], imports: [LocalizationPipe],
}) })
export class ToastComponent implements OnInit { export class ToastComponent implements OnInit {
@Input() readonly toast = input.required<Toaster.Toast>();
toast!: Toaster.Toast;
@Output() remove = new EventEmitter<number>(); @Output() remove = new EventEmitter<number>();
get severityClass(): string { get severityClass(): string {
if (!this.toast || !this.toast.severity) return ''; const toast = this.toast();
return `abp-toast-${this.toast.severity}`; if (!toast || !toast.severity) return '';
return `abp-toast-${toast.severity}`;
} }
get iconClass(): string { get iconClass(): string {
const { iconClass } = this.toast.options || {}; const { iconClass } = this.toast().options || {};
if (iconClass) { if (iconClass) {
return iconClass; return iconClass;
} }
switch (this.toast.severity) { switch (this.toast().severity) {
case 'success': case 'success':
return 'bi-check'; return 'bi-check';
case 'info': case 'info':
@ -41,7 +41,7 @@ export class ToastComponent implements OnInit {
} }
ngOnInit() { ngOnInit() {
const { sticky, life } = this.toast.options || {}; const { sticky, life } = this.toast().options || {};
if (sticky) return; if (sticky) return;
const timeout = life || 5000; const timeout = life || 5000;
@ -51,10 +51,10 @@ export class ToastComponent implements OnInit {
} }
close() { close() {
this.remove.emit(this.toast.options?.id); this.remove.emit(this.toast().options?.id);
} }
tap() { tap() {
if (this.toast.options?.tapToDismiss) this.close(); if (this.toast().options?.tapToDismiss) this.close();
} }
} }

5
npm/ng-packs/packages/theme-shared/src/lib/directives/disabled.directive.ts

@ -1,4 +1,4 @@
import { Directive, Input, OnChanges, SimpleChanges, inject } from '@angular/core'; import { Directive, OnChanges, SimpleChanges, inject, input } from '@angular/core';
import { NgControl } from '@angular/forms'; import { NgControl } from '@angular/forms';
@Directive({ @Directive({
@ -7,8 +7,7 @@ import { NgControl } from '@angular/forms';
export class DisabledDirective implements OnChanges { export class DisabledDirective implements OnChanges {
private ngControl = inject(NgControl, { host: true }); private ngControl = inject(NgControl, { host: true });
@Input() readonly abpDisabled = input(false);
abpDisabled = false;
// Related issue: https://github.com/angular/angular/issues/35330 // Related issue: https://github.com/angular/angular/issues/35330
ngOnChanges({ abpDisabled }: SimpleChanges) { ngOnChanges({ abpDisabled }: SimpleChanges) {

30
npm/ng-packs/packages/theme-shared/src/lib/directives/ellipsis.directive.ts

@ -1,11 +1,12 @@
import { import {
AfterViewInit, AfterViewInit,
ChangeDetectorRef, ChangeDetectorRef,
Directive, Directive,
ElementRef, ElementRef,
HostBinding, HostBinding,
Input, Input,
inject inject,
input
} from '@angular/core'; } from '@angular/core';
@Directive({ @Directive({
@ -15,29 +16,28 @@ export class EllipsisDirective implements AfterViewInit {
private cdRef = inject(ChangeDetectorRef); private cdRef = inject(ChangeDetectorRef);
private elRef = inject(ElementRef); private elRef = inject(ElementRef);
@Input('abpEllipsis') readonly width = input<string>(undefined, { alias: "abpEllipsis" });
width?: string;
@HostBinding('title') @HostBinding('title')
@Input() @Input()
title?: string; title?: string;
@Input('abpEllipsisEnabled') readonly enabled = input(true, { alias: "abpEllipsisEnabled" });
enabled = true;
@HostBinding('class.abp-ellipsis-inline') @HostBinding('class.abp-ellipsis-inline')
get inlineClass() { get inlineClass() {
return this.enabled && this.width; return this.enabled() && this.width();
} }
@HostBinding('class.abp-ellipsis') @HostBinding('class.abp-ellipsis')
get class() { get class() {
return this.enabled && !this.width; return this.enabled() && !this.width();
} }
@HostBinding('style.max-width') @HostBinding('style.max-width')
get maxWidth() { get maxWidth() {
return this.enabled && this.width ? this.width || '170px' : undefined; const width = this.width();
return this.enabled() && width ? width || '170px' : undefined;
} }
ngAfterViewInit() { ngAfterViewInit() {

32
npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts

@ -1,16 +1,17 @@
import { import {
ComponentFactoryResolver, ComponentFactoryResolver,
ComponentRef, ComponentRef,
Directive, Directive,
ElementRef, ElementRef,
EmbeddedViewRef, EmbeddedViewRef,
HostBinding, HostBinding,
Injector, Injector,
Input, Input,
OnDestroy, OnDestroy,
OnInit, OnInit,
Renderer2, Renderer2,
inject inject,
input
} from '@angular/core'; } from '@angular/core';
import { Subscription, timer } from 'rxjs'; import { Subscription, timer } from 'rxjs';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
@ -49,7 +50,7 @@ export class LoadingDirective implements OnInit, OnDestroy {
return; return;
} }
this.timerSubscription = timer(this.delay) this.timerSubscription = timer(this.delay())
.pipe(take(1)) .pipe(take(1))
.subscribe(() => { .subscribe(() => {
if (!this.componentRef) { if (!this.componentRef) {
@ -75,8 +76,7 @@ export class LoadingDirective implements OnInit, OnDestroy {
@Input('abpLoadingTargetElement') @Input('abpLoadingTargetElement')
targetElement: HTMLElement | undefined; targetElement: HTMLElement | undefined;
@Input('abpLoadingDelay') readonly delay = input(0, { alias: "abpLoadingDelay" });
delay = 0;
componentRef!: ComponentRef<LoadingComponent>; componentRef!: ComponentRef<LoadingComponent>;
rootNode: HTMLDivElement | null = null; rootNode: HTMLDivElement | null = null;

6
npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-default.directive.ts

@ -3,10 +3,10 @@ import {
AfterViewInit, AfterViewInit,
Directive, Directive,
HostBinding, HostBinding,
Input,
OnDestroy, OnDestroy,
inject, inject,
PLATFORM_ID, PLATFORM_ID,
input
} from '@angular/core'; } from '@angular/core';
import { ColumnMode, DatatableComponent } from '@swimlane/ngx-datatable'; import { ColumnMode, DatatableComponent } from '@swimlane/ngx-datatable';
import { fromEvent, Subscription } from 'rxjs'; import { fromEvent, Subscription } from 'rxjs';
@ -25,11 +25,11 @@ export class NgxDatatableDefaultDirective implements AfterViewInit, OnDestroy {
private subscription = new Subscription(); private subscription = new Subscription();
private resizeDiff = 0; private resizeDiff = 0;
@Input() class = 'material bordered'; readonly class = input('material bordered');
@HostBinding('class') @HostBinding('class')
get classes(): string { get classes(): string {
return `ngx-datatable ${this.class}`; return `ngx-datatable ${this.class()}`;
} }
constructor() { constructor() {

33
npm/ng-packs/packages/theme-shared/src/lib/directives/ngx-datatable-list.directive.ts

@ -1,7 +1,6 @@
import { import {
ChangeDetectorRef, ChangeDetectorRef,
Directive, Directive,
Input,
OnChanges, OnChanges,
OnInit, OnInit,
DoCheck, DoCheck,
@ -10,6 +9,7 @@ import {
DestroyRef, DestroyRef,
ViewContainerRef, ViewContainerRef,
Renderer2, Renderer2,
input
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { distinctUntilChanged } from 'rxjs'; import { distinctUntilChanged } from 'rxjs';
@ -35,7 +35,7 @@ export class NgxDatatableListDirective implements OnChanges, OnInit, DoCheck {
protected readonly viewContainerRef = inject(ViewContainerRef); protected readonly viewContainerRef = inject(ViewContainerRef);
protected readonly renderer = inject(Renderer2); protected readonly renderer = inject(Renderer2);
@Input() list!: ListService; readonly list = input.required<ListService>();
constructor() { constructor() {
this.setInitialValues(); this.setInitialValues();
@ -62,7 +62,7 @@ export class NgxDatatableListDirective implements OnChanges, OnInit, DoCheck {
} }
protected subscribeToRequestStatus() { protected subscribeToRequestStatus() {
const requestStatus$ = this.list.requestStatus$.pipe(distinctUntilChanged()); const requestStatus$ = this.list().requestStatus$.pipe(distinctUntilChanged());
const { emptyMessage, errorMessage } = this.ngxDatatableMessages || defaultNgxDatatableMessages; const { emptyMessage, errorMessage } = this.ngxDatatableMessages || defaultNgxDatatableMessages;
requestStatus$.subscribe(status => { requestStatus$.subscribe(status => {
@ -137,14 +137,15 @@ export class NgxDatatableListDirective implements OnChanges, OnInit, DoCheck {
this.table.sort this.table.sort
.pipe(takeUntilDestroyed(this.destroyRef)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ sorts: [{ prop, dir }] }) => { .subscribe(({ sorts: [{ prop, dir }] }) => {
if (prop === this.list.sortKey && this.list.sortOrder === 'desc') { const list = this.list();
this.list.sortKey = ''; if (prop === list.sortKey && list.sortOrder === 'desc') {
this.list.sortOrder = ''; list.sortKey = '';
list.sortOrder = '';
this.table.sorts = []; this.table.sorts = [];
this.cdRef.detectChanges(); this.cdRef.detectChanges();
} else { } else {
this.list.sortKey = prop; list.sortKey = prop;
this.list.sortOrder = dir; list.sortOrder = dir;
} }
}); });
} }
@ -156,32 +157,32 @@ export class NgxDatatableListDirective implements OnChanges, OnInit, DoCheck {
} }
protected subscribeToQuery() { protected subscribeToQuery() {
this.list.query$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => { this.list().query$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
const offset = this.list.page; const offset = this.list().page;
if (this.table.offset !== offset) this.table.offset = offset; if (this.table.offset !== offset) this.table.offset = offset;
}); });
} }
protected setTablePage(pageNum: number) { protected setTablePage(pageNum: number) {
this.list.page = pageNum; this.list().page = pageNum;
this.table.offset = pageNum; this.table.offset = pageNum;
} }
protected refreshPageIfDataExist() { protected refreshPageIfDataExist() {
if (this.table.rows?.length < 1 && this.table.count > 0) { if (this.table.rows?.length < 1 && this.table.count > 0) {
let maxPage = Math.floor(Number(this.table.count / this.list.maxResultCount)); let maxPage = Math.floor(Number(this.table.count / this.list().maxResultCount));
if (this.table.count < this.list.maxResultCount) { if (this.table.count < this.list().maxResultCount) {
this.setTablePage(0); this.setTablePage(0);
return; return;
} }
if (this.table.count % this.list.maxResultCount === 0) { if (this.table.count % this.list().maxResultCount === 0) {
maxPage -= 1; maxPage -= 1;
} }
if (this.list.page < maxPage) { if (this.list().page < maxPage) {
this.setTablePage(this.list.page); this.setTablePage(this.list().page);
return; return;
} }

Loading…
Cancel
Save