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 {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
Input,
OnChanges,
OnDestroy,
Output,
SimpleChanges,
ViewChild,
inject
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
OnDestroy,
Output,
ViewChild,
effect,
inject,
input,
untracked,
} from '@angular/core';
let Chart: any;
@ -21,13 +21,13 @@ let Chart: any;
template: `
<div
style="position:relative"
[style.width]="responsive && !width ? null : width"
[style.height]="responsive && !height ? null : height"
[style.width]="responsive() && !width() ? null : width()"
[style.height]="responsive() && !height() ? null : height()"
>
<canvas
#canvas
[attr.width]="responsive && !width ? null : width"
[attr.height]="responsive && !height ? null : height"
[attr.width]="responsive() && !width() ? null : width()"
[attr.height]="responsive() && !height() ? null : height()"
(click)="onCanvasClick($event)"
></canvas>
</div>
@ -35,32 +35,39 @@ let Chart: any;
changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'abpChart',
})
export class ChartComponent implements AfterViewInit, OnDestroy, OnChanges {
export class ChartComponent implements AfterViewInit, OnDestroy {
el = inject(ElementRef);
private cdr = inject(ChangeDetectorRef);
@Input() type!: string;
@Input() data: any = {};
@Input() options: any = {};
@Input() plugins: any[] = [];
@Input() width?: string;
@Input() height?: string;
@Input() responsive = true;
readonly type = input.required<string>();
readonly data = input<any>({});
readonly options = input<any>({});
readonly plugins = input<any[]>([]);
readonly width = input<string>();
readonly height = input<string>();
readonly responsive = input<boolean>(true);
@Output() dataSelect = new EventEmitter();
@Output() initialized = new EventEmitter<boolean>();
@ViewChild('canvas') canvas!: ElementRef<HTMLCanvasElement>;
chart: any;
constructor() {
effect(() => {
const data = this.data();
const options = this.options();
untracked(() => {
if (this.chart) {
this.chart.destroy();
this.initChart();
}
});
});
}
ngAfterViewInit() {
import('chart.js/auto').then(module => {
Chart = module.default;
@ -91,19 +98,19 @@ export class ChartComponent implements AfterViewInit, OnDestroy, OnChanges {
}
private initChart = () => {
const opts = this.options || {};
opts.responsive = this.responsive;
const opts = this.options() || {};
opts.responsive = this.responsive();
// allows chart to resize in responsive mode
if (opts.responsive && (this.height || this.width)) {
if (opts.responsive && (this.height() || this.width())) {
opts.maintainAspectRatio = false;
}
this.chart = new Chart(this.canvas.nativeElement, {
type: this.type as any,
data: this.data,
options: this.options,
plugins: this.plugins,
type: this.type() as any,
data: this.data(),
options: this.options(),
plugins: this.plugins(),
});
};
@ -140,13 +147,5 @@ export class ChartComponent implements AfterViewInit, OnDestroy, OnChanges {
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 { ExtensionsService } from '../../services/extensions.service';
import { EXTENSIONS_ACTION_TYPE, EXTENSIONS_IDENTIFIER } from '../../tokens/extensions.token';
@ -14,7 +14,7 @@ export abstract class AbstractActionsComponent<
readonly getInjected: InferredData<L>['getInjected'];
@Input() record!: InferredData<L>['record'];
record = input.required<InferredRecord<L>>();
protected constructor() {
const injector = inject(Injector);
@ -27,3 +27,4 @@ export abstract class AbstractActionsComponent<
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'">
<span class="d-inline-flex align-items-center gap-1 text-nowrap">
@if (prop.displayTextResolver) {
{{ prop.displayTextResolver(data) | abpLocalization }}
{{ prop.displayTextResolver(data()) | abpLocalization }}
} @else {
@if (prop.isExtra) {
{{ '::' + 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,
signal,
effect,
input
} from '@angular/core';
import {
ControlContainer,
@ -94,10 +95,10 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
private injector = inject(Injector);
private readonly form = this.#groupDirective.form;
@Input() data!: PropData;
readonly data = input.required<PropData>();
@Input() prop!: FormProp;
@Input() first?: boolean;
@Input() isFirstGroup?: boolean;
readonly first = input<boolean>(undefined);
readonly isFirstGroup = input<boolean>(undefined);
@ViewChild('field') private fieldRef!: ElementRef<HTMLElement>;
injectorForCustomComponent?: Injector;
@ -113,7 +114,7 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
disabledFn = (data: PropData) => false;
get disabled() {
return this.disabledFn(this.data);
return this.disabledFn(this.data());
}
setTypeaheadValue(selectedOption: ABP.Option<string>) {
@ -130,7 +131,7 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
? text$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(text => this.prop?.options?.(this.data, text) || of([])),
switchMap(text => this.prop?.options?.(this.data(), text) || of([])),
)
: of([]);
@ -158,7 +159,7 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
}
ngAfterViewInit() {
if (this.isFirstGroup && this.first && this.fieldRef) {
if (this.isFirstGroup() && this.first() && this.fieldRef) {
requestAnimationFrame(() => {
this.fieldRef.nativeElement.focus();
});
@ -193,14 +194,14 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
});
}
if (options) this.options$ = options(this.data);
if (readonly) this.readonly = readonly(this.data);
if (options) this.options$ = options(this.data());
if (readonly) this.readonly = readonly(this.data());
if (disabled) {
this.disabledFn = disabled;
}
if (validators) {
this.validators = validators(this.data);
this.validators = validators(this.data());
this.setAsterisk();
}
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) {
<ngx-datatable #table default [rows]="data" [count]="recordsTotal" [list]="list"
[selectionType]="selectable ? _selectionType : undefined" (activate)="tableActivate.emit($event)"
(select)="onSelect($event)" [selected]="selected" (scroll)="onScroll($event)" [scrollbarV]="infiniteScroll"
[style.height]="getTableHeight()" [loadingIndicator]="infiniteScroll && isLoading"
[footerHeight]="infiniteScroll ? false : 50">
<ngx-datatable #table default [rows]="data" [count]="recordsTotal()" [list]="list()"
[selectionType]="selectable() ? _selectionType : undefined" (activate)="tableActivate.emit($event)"
(select)="onSelect($event)" [selected]="selected()" (scroll)="onScroll($event)" [scrollbarV]="infiniteScroll()"
[style.height]="getTableHeight()" [loadingIndicator]="infiniteScroll() && isLoading()"
[footerHeight]="infiniteScroll() ? false : 50">
@if (effectiveRowDetailTemplate) {
<ngx-datatable-row-detail [rowHeight]="effectiveRowDetailHeight">
<ng-template let-row="row" let-expanded="expanded" ngx-datatable-row-detail-template>
@ -22,7 +22,7 @@
</ng-template>
</ngx-datatable-column>
}
@if(selectable) {
@if(selectable()) {
<ngx-datatable-column [width]="50" [sortable]="false" [canAutoResize]="false" [draggable]="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,
TrackByFunction,
ViewChild,
input
} from '@angular/core';
import { AsyncPipe, isPlatformBrowser, NgComponentOutlet, NgTemplateOutlet } from '@angular/common';
@ -109,8 +110,8 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
}
@Input() data!: R[];
@Input() list!: ListService;
@Input() recordsTotal!: number;
readonly list = input.required<ListService>();
readonly recordsTotal = input.required<number>();
@Input() set actionsColumnWidth(width: number) {
this._actionsColumnWidth.set(width ? Number(width) : undefined);
@ -120,25 +121,25 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
@Output() tableActivate = new EventEmitter();
@Input() selectable = false;
readonly selectable = input(false);
@Input() set selectionType(value: SelectionType | string) {
this._selectionType = typeof value === 'string' ? SelectionType[value] : value;
}
_selectionType: SelectionType = SelectionType.multiClick;
@Input() selected: any[] = [];
readonly selected = input<any[]>([]);
@Output() selectionChange = new EventEmitter<any[]>();
// Infinite scroll configuration
@Input() infiniteScroll = false;
@Input() isLoading = false;
@Input() scrollThreshold = 10;
readonly infiniteScroll = input(false);
readonly isLoading = input(false);
readonly scrollThreshold = input(10);
@Output() loadMore = new EventEmitter<void>();
@Input() tableHeight: number;
readonly tableHeight = input<number>(undefined);
@Input() rowDetailTemplate?: TemplateRef<RowDetailContext<R>>;
@Input() rowDetailHeight: string | number = '100%';
readonly rowDetailTemplate = input<TemplateRef<RowDetailContext<R>>>(undefined);
readonly rowDetailHeight = input<string | number>('100%');
@Output() rowDetailToggle = new EventEmitter<R>();
@ContentChild(ExtensibleTableRowDetailComponent)
@ -147,11 +148,11 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
@ViewChild('table', { static: false }) table!: DatatableComponent;
protected get effectiveRowDetailTemplate(): TemplateRef<RowDetailContext<R>> | undefined {
return this.rowDetailComponent?.template() ?? this.rowDetailTemplate;
return this.rowDetailComponent?.template() ?? this.rowDetailTemplate();
}
protected get effectiveRowDetailHeight(): string | number {
return this.rowDetailComponent?.rowHeight() ?? this.rowDetailHeight;
return this.rowDetailComponent?.rowHeight() ?? this.rowDetailHeight();
}
hasAtLeastOnePermittedAction: boolean;
@ -220,7 +221,7 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
if (!data?.currentValue) return;
if (data.currentValue.length < 1) {
this.list.totalCount = this.recordsTotal;
this.list().totalCount = this.recordsTotal();
}
this.data = data.currentValue.map((record: any, index: number) => {
@ -278,8 +279,9 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
}
onSelect({ selected }) {
this.selected.splice(0, this.selected.length);
this.selected.push(...selected);
const selectedValue = this.selected();
this.selected().splice(0, selectedValue.length);
selectedValue.push(...selected);
this.selectionChange.emit(selected);
}
@ -299,12 +301,12 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
}
private shouldHandleScroll(): boolean {
return this.infiniteScroll && !this.isLoading;
return this.infiniteScroll() && !this.isLoading();
}
private isNearScrollBottom(element: HTMLElement): boolean {
const { offsetHeight, scrollTop, scrollHeight } = element;
return offsetHeight + scrollTop >= scrollHeight - this.scrollThreshold;
return offsetHeight + scrollTop >= scrollHeight - this.scrollThreshold();
}
private triggerLoadMore(): void {
@ -312,9 +314,10 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
}
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 {
@ -325,8 +328,8 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
}
ngAfterViewInit(): void {
if (!this.infiniteScroll) {
this.list?.requestStatus$?.pipe(filter(status => status === 'loading')).subscribe(() => {
if (!this.infiniteScroll()) {
this.list()?.requestStatus$?.pipe(filter(status => status === 'loading')).subscribe(() => {
this.data = [];
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) {
<div ngbDropdown container="body" class="d-inline-block">
<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>
<div ngbDropdownMenu>
@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,
Input,
TrackByFunction,
input
} from '@angular/core';
import { EntityAction, EntityActionList } from '../../models/entity-actions';
import { EXTENSIONS_ACTION_TYPE } from '../../tokens/extensions.token';
@ -33,11 +34,11 @@ import { NgTemplateOutlet } from '@angular/common';
changeDetection: ChangeDetectionStrategy.OnPush,
})
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() text = '';
readonly text = input('');
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 */
import {
Directive,
Injector,
Input,
OnChanges,
OnDestroy,
TemplateRef,
ViewContainerRef,
inject
import {
Directive,
Injector,
Input,
OnChanges,
OnDestroy,
TemplateRef,
ViewContainerRef,
inject,
input
} from '@angular/core';
import { PropData, PropList } from '../models/props';
@ -22,7 +23,7 @@ export class PropDataDirective<L extends PropList<any>>
private tempRef = inject<TemplateRef<any>>(TemplateRef);
private vcRef = inject(ViewContainerRef);
@Input('abpPropDataFromList') propList?: L;
readonly propList = input<L>(undefined, { alias: "abpPropDataFromList" });
@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';
export abstract class ActionList<R = any, A = Action<R>> extends LinkedList<A> {}
@ -10,18 +10,26 @@ export abstract class ActionData<R = any> {
flags?: InjectOptions,
) => T;
index?: number;
abstract record: R;
abstract record: R | InputSignal<R>;
get data(): ReadonlyActionData<R> {
return {
getInjected: this.getInjected,
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> {
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 ActionPredicate<T> = (data?: Omit<ActionData<T>, 'data'>) => boolean;
export type ActionCallback<T, R = any> = (data: ReadonlyActionData<T>) => R;
export type ActionPredicate<T> = (data?: ReadonlyActionData<T>) => boolean;
export abstract class ActionsFactory<C extends Actions<any>> {
protected abstract _ctor: Type<C>;

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

@ -1,17 +1,18 @@
import {
Directive,
TemplateRef,
ViewContainerRef,
Input,
InjectionToken,
OnInit,
OnDestroy,
Injector,
OnChanges,
SimpleChanges,
SimpleChange,
inject
} from '@angular/core';
import {
Directive,
TemplateRef,
ViewContainerRef,
Input,
InjectionToken,
OnInit,
OnDestroy,
Injector,
OnChanges,
SimpleChanges,
SimpleChange,
inject,
input
} from '@angular/core';
import { Observable, Subscription, of } from 'rxjs';
export interface PageRenderStrategy {
@ -36,7 +37,7 @@ export class PagePartDirective implements OnInit, OnDestroy, OnChanges {
type!: string;
subscription!: Subscription;
@Input('abpPagePartContext') context: any;
readonly context = input<any>(undefined, { alias: "abpPagePartContext" });
@Input() set abpPagePart(type: string) {
this.type = type;
this.createRenderStream(type);
@ -60,7 +61,7 @@ export class PagePartDirective implements OnInit, OnDestroy, OnChanges {
ngOnInit() {
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();
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) {
<ng-content select="abp-page-breadcrumb-container"></ng-content>
} @else {
@if (breadcrumb) {
@if (breadcrumb()) {
<div class="col-lg-auto ps-lg-0" *abpPagePart="pageParts.breadcrumb">
<abp-breadcrumb></abp-breadcrumb>
</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 {
PageTitleContainerComponent,
PageBreadcrumbContainerComponent,
@ -29,7 +29,7 @@ export class PageComponent {
return this._toolbarData;
}
@Input() breadcrumb = true;
readonly breadcrumb = input(true);
pageParts = {
title: PageParts.title,
@ -46,7 +46,7 @@ export class PageComponent {
return !!(
this.title ||
this.toolbarVisible ||
this.breadcrumb ||
this.breadcrumb() ||
this.customTitle ||
this.customBreadcrumb ||
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';
// Not an abstract class on purpose. Do not change!
@ -14,20 +14,17 @@ export class AbstractNgModelComponent<T = any, U = T> implements ControlValueAcc
@Input()
disabled?: boolean;
@Input()
readonly?: boolean;
readonly readonly = input<boolean>(undefined);
@Input()
valueFn: (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);
@Input()
valueLimitFn: (value: T, previousValue?: T) => any = value => false;
readonly valueLimitFn = input<(value: T, previousValue?: T) => any>(value => false);
@Input()
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.notifyValueChange();
@ -48,7 +45,7 @@ export class AbstractNgModelComponent<T = any, U = T> implements ControlValueAcc
}
writeValue(value: T): void {
this._value = this.valueLimitFn(value, this._value) || value;
this._value = this.valueLimitFn()(value, this._value) || value;
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 { debounceTime } from 'rxjs/operators';
import { SubscriptionService } from '../services/subscription.service';
@ -11,13 +11,13 @@ export class InputEventDebounceDirective implements OnInit {
private el = inject(ElementRef);
private subscription = inject(SubscriptionService);
@Input() debounce = 300;
readonly debounce = input(300);
@Output('input.debounce') readonly debounceEvent = new EventEmitter<Event>();
ngOnInit(): void {
const input$ = fromEvent<InputEvent>(this.el.nativeElement, 'input').pipe(
debounceTime(this.debounce),
debounceTime(this.debounce()),
);
this.subscription.addOne(input$, (event: Event) => {

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

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

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

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

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

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

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

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

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

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

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

@ -30,7 +30,7 @@
</a>
<div #routeContainer class="dropdown-menu border-0 shadow-sm"
(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 }" />
</div>
</li>
@ -60,7 +60,7 @@
<div class="dropdown-submenu" ngbDropdown #dropdownSubmenu="ngbDropdown" placement="right-top" [autoClose]="true"
*abpPermission="child.requiredPolicy">
<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">
@if (child.iconClass) {
<i [class]="child.iconClass"></i>
@ -69,7 +69,7 @@
</a>
</div>
<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 }" />
</div>
</div>

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

@ -10,11 +10,11 @@ import {
Component,
ElementRef,
inject,
Input,
QueryList,
Renderer2,
TrackByFunction,
ViewChildren,
input
} from '@angular/core';
import { NgTemplateOutlet, AsyncPipe } from '@angular/common';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
@ -39,7 +39,7 @@ export class RoutesComponent {
public readonly routesService = inject(RoutesService);
protected renderer = inject(Renderer2);
@Input() smallScreen?: boolean;
readonly smallScreen = input<boolean>(undefined);
@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,
ViewChild,
inject,
input
} from '@angular/core';
import { ABP, StopPropagationDirective } from '@abp/ng.core';
@ -17,11 +18,11 @@ import { ABP, StopPropagationDirective } from '@abp/ng.core';
template: `
<button
#button
[id]="buttonId"
[attr.type]="buttonType"
[attr.form]="formName"
[class]="buttonClass"
[disabled]="loading || disabled"
[id]="buttonId()"
[attr.type]="buttonType()"
[attr.form]="formName()"
[class]="buttonClass()"
[disabled]="loading || disabled()"
(click.stop)="click.next($event); abpClick.next($event)"
(focus)="focus.next($event); abpFocus.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 {
private renderer = inject(Renderer2);
@Input()
buttonId = '';
readonly buttonId = input('');
@Input()
buttonClass = 'btn btn-primary';
readonly buttonClass = input('btn btn-primary');
@Input()
buttonType = 'button';
readonly buttonType = input('button');
@Input()
formName?: string = undefined;
readonly formName = input<string>(undefined);
@Input()
iconClass?: string;
readonly iconClass = input<string>(undefined);
@Input()
loading = false;
@Input()
disabled: boolean | undefined = false;
readonly disabled = input<boolean | undefined>(false);
@Input()
attributes?: ABP.Dictionary<string>;
readonly attributes = input<ABP.Dictionary<string>>(undefined);
@Output() readonly click = new EventEmitter<MouseEvent>();
@ -74,14 +68,16 @@ export class ButtonComponent implements OnInit {
buttonRef!: ElementRef<HTMLButtonElement>;
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() {
if (this.attributes) {
Object.keys(this.attributes).forEach(key => {
if (this.attributes?.[key]) {
this.renderer.setAttribute(this.buttonRef.nativeElement, key, this.attributes[key]);
const attributes = this.attributes();
if (attributes) {
Object.keys(attributes).forEach(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({
selector: 'abp-card-body',
template: ` <div [class]="cardBodyClass" [style]="cardBodyStyle">
template: ` <div [class]="cardBodyClass()" [style]="cardBodyStyle()">
<ng-content></ng-content>
</div>`,
})
export class CardBodyComponent {
@HostBinding('class') componentClass = 'card-body';
@Input() cardBodyClass: string;
@Input() cardBodyStyle: string;
readonly cardBodyClass = input<string>(undefined);
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({
selector: 'abp-card-footer',
template: `
<div [style]="cardFooterStyle" [class]="cardFooterClass">
<div [style]="cardFooterStyle()" [class]="cardFooterClass()">
<ng-content></ng-content>
</div>
`,
@ -12,6 +12,6 @@ import { Component, HostBinding, Input } from '@angular/core';
})
export class CardFooterComponent {
@HostBinding('class') componentClass = 'card-footer';
@Input() cardFooterStyle: string;
@Input() cardFooterClass: string;
readonly cardFooterStyle = input<string>(undefined);
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({
selector: 'abp-card-header',
template: `
<div [class]="cardHeaderClass" [style]="cardHeaderStyle">
<div [class]="cardHeaderClass()" [style]="cardHeaderStyle()">
<ng-content></ng-content>
</div>
`,
@ -12,6 +12,6 @@ import { Component, HostBinding, Input } from '@angular/core';
})
export class CardHeaderComponent {
@HostBinding('class') componentClass = 'card-header';
@Input() cardHeaderClass: string;
@Input() cardHeaderStyle: string;
readonly cardHeaderClass = input<string>(undefined);
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({
selector: 'abp-card',
template: ` <div class="card" [class]="cardClass" [style]="cardStyle">
template: ` <div class="card" [class]="cardClass()" [style]="cardStyle()">
<ng-content></ng-content>
</div>`,
imports: [],
})
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 { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core';
@ -9,15 +9,15 @@ import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core';
<input
type="checkbox"
[(ngModel)]="value"
[id]="checkboxId"
[readonly]="checkboxReadonly"
[class]="checkboxClass"
[style]="checkboxStyle"
[id]="checkboxId()"
[readonly]="checkboxReadonly()"
[class]="checkboxClass()"
[style]="checkboxStyle()"
(blur)="checkboxBlur.next()"
(focus)="checkboxFocus.next()"
/>
@if (label) {
<label [class]="labelClass" [for]="checkboxId">
<label [class]="labelClass()" [for]="checkboxId()">
{{ label | abpLocalization }}
</label>
}
@ -34,16 +34,13 @@ import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core';
})
export class FormCheckboxComponent extends AbstractNgModelComponent {
@Input() label?: string;
@Input() labelClass = 'form-check-label';
@Input() checkboxId!: string;
@Input() checkboxStyle:
| {
[klass: string]: any;
}
| null
| undefined;
@Input() checkboxClass = 'form-check-input';
@Input() checkboxReadonly = false;
readonly labelClass = input('form-check-label');
readonly checkboxId = input.required<string>();
readonly checkboxStyle = input<{
[klass: string]: any;
} | null | undefined>(undefined);
readonly checkboxClass = input('form-check-input');
readonly checkboxReadonly = input(false);
@Output() checkboxBlur = 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 { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core';
@ -7,17 +7,17 @@ import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core';
template: `
<div class="mb-3">
@if (label) {
<label [class]="labelClass" [for]="inputId">
<label [class]="labelClass()" [for]="inputId()">
{{ label | abpLocalization }}
</label>
}
<input
type="text"
[id]="inputId"
[placeholder]="inputPlaceholder"
[readonly]="inputReadonly"
[class]="inputClass"
[style]="inputStyle"
[id]="inputId()"
[placeholder]="inputPlaceholder()"
[readonly]="inputReadonly()"
[class]="inputClass()"
[style]="inputStyle()"
(blur)="formBlur.next()"
(focus)="formFocus.next()"
[(ngModel)]="value"
@ -34,18 +34,15 @@ import { AbstractNgModelComponent, LocalizationPipe } from '@abp/ng.core';
imports: [LocalizationPipe, FormsModule],
})
export class FormInputComponent extends AbstractNgModelComponent {
@Input() inputId!: string;
@Input() inputReadonly = false;
readonly inputId = input.required<string>();
readonly inputReadonly = input(false);
@Input() label = '';
@Input() labelClass = 'form-label';
@Input() inputPlaceholder = '';
@Input() inputStyle:
| {
[klass: string]: any;
}
| null
| undefined;
@Input() inputClass = 'form-control';
readonly labelClass = input('form-label');
readonly inputPlaceholder = input('');
readonly inputStyle = input<{
[klass: string]: any;
} | null | undefined>(undefined);
readonly inputClass = input('form-control');
@Output() formBlur = 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 { HttpWaitService, RouterWaitService, SubscriptionService } from '@abp/ng.core';
@Component({
selector: 'abp-loader-bar',
template: `
<div id="abp-loader-bar" [class]="containerClass" [class.is-loading]="isLoading">
<div id="abp-loader-bar" [class]="containerClass()" [class.is-loading]="isLoading">
<div
class="abp-progress"
[class.progressing]="progressLevel"
[style.width.vw]="progressLevel"
[style]="{
'background-color': color,
'background-color': color(),
'box-shadow': boxShadow,
}"
></div>
@ -38,11 +38,9 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
return this._isLoading;
}
@Input()
containerClass = 'abp-loader-bar';
readonly containerClass = input('abp-loader-bar');
@Input()
color = '#77b6ff';
readonly color = input('#77b6ff');
progressLevel = 0;
@ -73,7 +71,7 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
};
get boxShadow(): string {
return `0 0 10px rgba(${this.color}, 0.5)`;
return `0 0 10px rgba(${this.color()}, 0.5)`;
}
ngOnInit() {

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

@ -2,7 +2,7 @@
<input
[type]="fieldTextType ? 'text' : 'password'"
class="form-control"
[id]="inputId"
[id]="inputId()"
[(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 { AbstractNgModelComponent } from '@abp/ng.core';
@ -21,8 +21,8 @@ import { NgxValidateCoreModule } from '@ngx-validate/core';
],
})
export class PasswordComponent extends AbstractNgModelComponent {
@Input() inputId!: string;
@Input() formControlName!: string;
readonly inputId = input.required<string>();
readonly formControlName = input.required<string>();
fieldTextType?: boolean;
toggleFieldTextType() {

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

@ -1,9 +1,9 @@
<div
class="abp-toast-container"
[style.top]="top || 'auto'"
[style.top]="top() || 'auto'"
[style.right]="right || 'auto'"
[style.bottom]="bottom || 'auto'"
[style.left]="left || 'auto'"
[style.bottom]="bottom() || 'auto'"
[style.left]="left() || 'auto'"
[style.display]="toasts.length ? 'flex' : 'none'"
[@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 { toastInOut } from '../../animations/toast.animations';
import { Toaster } from '../../models/toaster';
@ -18,29 +18,25 @@ export class ToastContainerComponent implements OnInit {
toasts = [] as Toaster.Toast[];
@Input()
top?: string;
readonly top = input<string>(undefined);
@Input()
right = '30px';
defaultRight = '30px';
defaultMobileRight = '0';
@Input()
bottom = '30px';
readonly bottom = input('30px');
@Input()
left?: string;
readonly left = input<string>(undefined);
@Input()
toastKey?: string;
readonly toastKey = input<string>(undefined);
ngOnInit() {
this.setDefaultRight();
this.toasts$.subscribe(toasts => {
this.toasts = this.toastKey
this.toasts = this.toastKey()
? toasts.filter(t => {
return t.options && t.options.containerKey !== this.toastKey;
return t.options && t.options.containerKey !== this.toastKey();
})
: 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>
</div>
<div class="abp-toast-content">
@if (toast.options?.closable) {
@if (toast().options?.closable) {
<button class="abp-toast-close-button" (click)="close()">
<i class="bi bi-x fs-4" aria-hidden="true"></i>
</button>
}
<div class="abp-toast-title">
{{ toast.title | abpLocalization: toast.options?.titleLocalizationParams }}
{{ toast().title | abpLocalization: toast().options?.titleLocalizationParams }}
</div>
<p class="abp-toast-message"
[innerHTML]="toast.message | abpLocalization: toast.options?.messageLocalizationParams"></p>
[innerHTML]="toast().message | abpLocalization: toast().options?.messageLocalizationParams"></p>
</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 { LocalizationPipe } from '@abp/ng.core';
@ -9,24 +9,24 @@ import { LocalizationPipe } from '@abp/ng.core';
imports: [LocalizationPipe],
})
export class ToastComponent implements OnInit {
@Input()
toast!: Toaster.Toast;
readonly toast = input.required<Toaster.Toast>();
@Output() remove = new EventEmitter<number>();
get severityClass(): string {
if (!this.toast || !this.toast.severity) return '';
return `abp-toast-${this.toast.severity}`;
const toast = this.toast();
if (!toast || !toast.severity) return '';
return `abp-toast-${toast.severity}`;
}
get iconClass(): string {
const { iconClass } = this.toast.options || {};
const { iconClass } = this.toast().options || {};
if (iconClass) {
return iconClass;
}
switch (this.toast.severity) {
switch (this.toast().severity) {
case 'success':
return 'bi-check';
case 'info':
@ -41,7 +41,7 @@ export class ToastComponent implements OnInit {
}
ngOnInit() {
const { sticky, life } = this.toast.options || {};
const { sticky, life } = this.toast().options || {};
if (sticky) return;
const timeout = life || 5000;
@ -51,10 +51,10 @@ export class ToastComponent implements OnInit {
}
close() {
this.remove.emit(this.toast.options?.id);
this.remove.emit(this.toast().options?.id);
}
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';
@Directive({
@ -7,8 +7,7 @@ import { NgControl } from '@angular/forms';
export class DisabledDirective implements OnChanges {
private ngControl = inject(NgControl, { host: true });
@Input()
abpDisabled = false;
readonly abpDisabled = input(false);
// Related issue: https://github.com/angular/angular/issues/35330
ngOnChanges({ abpDisabled }: SimpleChanges) {

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

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

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

@ -1,16 +1,17 @@
import {
ComponentFactoryResolver,
ComponentRef,
Directive,
ElementRef,
EmbeddedViewRef,
HostBinding,
Injector,
Input,
OnDestroy,
OnInit,
Renderer2,
inject
import {
ComponentFactoryResolver,
ComponentRef,
Directive,
ElementRef,
EmbeddedViewRef,
HostBinding,
Injector,
Input,
OnDestroy,
OnInit,
Renderer2,
inject,
input
} from '@angular/core';
import { Subscription, timer } from 'rxjs';
import { take } from 'rxjs/operators';
@ -49,7 +50,7 @@ export class LoadingDirective implements OnInit, OnDestroy {
return;
}
this.timerSubscription = timer(this.delay)
this.timerSubscription = timer(this.delay())
.pipe(take(1))
.subscribe(() => {
if (!this.componentRef) {
@ -75,8 +76,7 @@ export class LoadingDirective implements OnInit, OnDestroy {
@Input('abpLoadingTargetElement')
targetElement: HTMLElement | undefined;
@Input('abpLoadingDelay')
delay = 0;
readonly delay = input(0, { alias: "abpLoadingDelay" });
componentRef!: ComponentRef<LoadingComponent>;
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,
Directive,
HostBinding,
Input,
OnDestroy,
inject,
PLATFORM_ID,
input
} from '@angular/core';
import { ColumnMode, DatatableComponent } from '@swimlane/ngx-datatable';
import { fromEvent, Subscription } from 'rxjs';
@ -25,11 +25,11 @@ export class NgxDatatableDefaultDirective implements AfterViewInit, OnDestroy {
private subscription = new Subscription();
private resizeDiff = 0;
@Input() class = 'material bordered';
readonly class = input('material bordered');
@HostBinding('class')
get classes(): string {
return `ngx-datatable ${this.class}`;
return `ngx-datatable ${this.class()}`;
}
constructor() {

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

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

Loading…
Cancel
Save