@switch (feature.valueType?.name) {
diff --git a/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts b/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts
index 200e39736b..0ba6880036 100644
--- a/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts
+++ b/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts
@@ -1,4 +1,4 @@
-import { Component, Input, inject, DOCUMENT, output } from '@angular/core';
+import { Component, inject, DOCUMENT, input, output, signal, effect } from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ConfigStateService, LocalizationPipe, TrackByService } from '@abp/ng.core';
@@ -20,7 +20,6 @@ import {
import { Tabs, TabList, Tab, TabPanel, TabContent } from '@angular/aria/tabs';
import { finalize } from 'rxjs/operators';
import { FreeTextInputDirective } from '../../directives';
-import { FeatureManagement } from '../../models';
enum ValueTypes {
ToggleStringValueType = 'ToggleStringValueType',
@@ -49,11 +48,7 @@ const DEFAULT_PROVIDER_NAME = 'D';
ModalCloseDirective,
],
})
-export class FeatureManagementComponent
- implements
- FeatureManagement.FeatureManagementComponentInputs,
- FeatureManagement.FeatureManagementComponentOutputs
-{
+export class FeatureManagementComponent {
protected readonly track = inject(TrackByService);
protected readonly toasterService = inject(ToasterService);
protected readonly service = inject(FeaturesService);
@@ -61,14 +56,17 @@ export class FeatureManagementComponent
protected readonly confirmationService = inject(ConfirmationService);
private document = inject(DOCUMENT);
- @Input()
- providerKey: string;
+ // Signal inputs
+ readonly providerKey = input
(undefined);
+ readonly providerName = input(undefined);
+ readonly providerTitle = input(undefined);
+ readonly visibleInput = input(false, { alias: 'visible' });
- @Input()
- providerName: string;
+ // Output signals
+ readonly visibleChange = output();
- @Input({ required: false })
- providerTitle: string;
+ // Internal state
+ protected readonly _visible = signal(false);
selectedGroupDisplayName: string;
@@ -82,33 +80,41 @@ export class FeatureManagementComponent
defaultProviderName = DEFAULT_PROVIDER_NAME;
- protected _visible;
+ modalBusy = false;
- @Input()
+ // Getter/setter for backward compatibility
get visible(): boolean {
- return this._visible;
+ return this._visible();
}
set visible(value: boolean) {
- if (this._visible === value) {
+ if (this._visible() === value) {
return;
}
- this._visible = value;
+ this._visible.set(value);
this.visibleChange.emit(value);
if (value) {
this.openModal();
- return;
}
}
- readonly visibleChange = output();
-
- modalBusy = false;
+ constructor() {
+ // Sync visible input to internal signal
+ effect(() => {
+ const inputValue = this.visibleInput();
+ if (this._visible() !== inputValue) {
+ this._visible.set(inputValue);
+ if (inputValue) {
+ this.openModal();
+ }
+ }
+ });
+ }
openModal() {
- if (!this.providerName) {
+ if (!this.providerName()) {
throw new Error('providerName is required.');
}
@@ -116,7 +122,7 @@ export class FeatureManagementComponent
}
getFeatures() {
- this.service.get(this.providerName, this.providerKey).subscribe(res => {
+ this.service.get(this.providerName()!, this.providerKey()).subscribe(res => {
if (!res.groups?.length) return;
this.groups = res.groups.map(({ name, displayName }) => ({ name, displayName }));
this.selectedGroupDisplayName = this.groups[0].displayName;
@@ -149,13 +155,13 @@ export class FeatureManagementComponent
this.modalBusy = true;
this.service
- .update(this.providerName, this.providerKey, { features: changedFeatures })
+ .update(this.providerName()!, this.providerKey(), { features: changedFeatures })
.pipe(finalize(() => (this.modalBusy = false)))
.subscribe(() => {
this.visible = false;
this.toasterService.success('AbpUi::SavedSuccessfully');
- if (!this.providerKey) {
+ if (!this.providerKey()) {
// to refresh host's features
this.configState.refreshAppState().subscribe();
}
@@ -167,11 +173,11 @@ export class FeatureManagementComponent
.warn('AbpFeatureManagement::AreYouSureToResetToDefault', 'AbpFeatureManagement::AreYouSure')
.subscribe((status: Confirmation.Status) => {
if (status === Confirmation.Status.confirm) {
- this.service.delete(this.providerName, this.providerKey).subscribe(() => {
+ this.service.delete(this.providerName()!, this.providerKey()).subscribe(() => {
this.toasterService.success('AbpFeatureManagement::ResetedToDefault');
this.visible = false;
- if (!this.providerKey) {
+ if (!this.providerKey()) {
// to refresh host's features
this.configState.refreshAppState().subscribe();
}
@@ -190,17 +196,18 @@ export class FeatureManagementComponent
isParentDisabled(parentName: string, groupName: string, provider: string): boolean {
const children = this.features[groupName]?.filter(f => f.parentName === parentName);
+ const providerNameValue = this.providerName();
if (children?.length) {
return children.some(child => {
const childProvider = child.provider?.name;
return (
- (childProvider !== this.providerName && childProvider !== this.defaultProviderName) ||
- (provider !== this.providerName && provider !== this.defaultProviderName)
+ (childProvider !== providerNameValue && childProvider !== this.defaultProviderName) ||
+ (provider !== providerNameValue && provider !== this.defaultProviderName)
);
});
} else {
- return provider !== this.providerName && provider !== this.defaultProviderName;
+ return provider !== providerNameValue && provider !== this.defaultProviderName;
}
}
diff --git a/npm/ng-packs/packages/feature-management/src/lib/directives/free-text-input.directive.ts b/npm/ng-packs/packages/feature-management/src/lib/directives/free-text-input.directive.ts
index 6b6556dcfa..b341879995 100644
--- a/npm/ng-packs/packages/feature-management/src/lib/directives/free-text-input.directive.ts
+++ b/npm/ng-packs/packages/feature-management/src/lib/directives/free-text-input.directive.ts
@@ -1,4 +1,4 @@
-import { Directive, HostBinding, Input } from '@angular/core';
+import { Directive, effect, inject, input, ElementRef, Renderer2 } from '@angular/core';
// TODO: improve this type
export interface FreeTextType {
@@ -9,7 +9,7 @@ export interface FreeTextType {
};
}
-export const INPUT_TYPES = {
+export const INPUT_TYPES: Record = {
numeric: 'number',
default: 'text',
};
@@ -19,21 +19,25 @@ export const INPUT_TYPES = {
exportAs: 'inputAbpFeatureManagementFreeText',
})
export class FreeTextInputDirective {
- _feature: FreeTextType;
- // eslint-disable-next-line @angular-eslint/no-input-rename
- @Input('abpFeatureManagementFreeText') set feature(val: FreeTextType) {
- this._feature = val;
- this.setInputType();
- }
+ private readonly elRef = inject(ElementRef);
+ private readonly renderer = inject(Renderer2);
- get feature() {
- return this._feature;
- }
+ readonly feature = input(undefined, {
+ alias: 'abpFeatureManagementFreeText',
+ });
- @HostBinding('type') type: string;
+ constructor() {
+ effect(() => {
+ const feature = this.feature();
+ if (feature) {
+ this.setInputType(feature);
+ }
+ });
+ }
- private setInputType() {
- const validatorType = this.feature?.valueType?.validator?.name.toLowerCase();
- this.type = INPUT_TYPES[validatorType] ?? INPUT_TYPES.default;
+ private setInputType(feature: FreeTextType) {
+ const validatorType = feature?.valueType?.validator?.name?.toLowerCase();
+ const type = INPUT_TYPES[validatorType] ?? INPUT_TYPES['default'];
+ this.renderer.setAttribute(this.elRef.nativeElement, 'type', type);
}
}
diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html
index 0224f3d45a..3190d5216c 100644
--- a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html
+++ b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html
@@ -1,9 +1,9 @@
- @if (data.entityDisplayName || entityDisplayName) {
+ @if (data.entityDisplayName || entityDisplayName()) {
{{ 'AbpPermissionManagement::Permissions' | abpLocalization }} -
- {{ entityDisplayName || data.entityDisplayName }}
+ {{ entityDisplayName() || data.entityDisplayName }}
diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts
index bc0e98414e..26b9a49349 100644
--- a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts
+++ b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts
@@ -19,15 +19,16 @@ import {
Component,
computed,
DOCUMENT,
+ effect,
ElementRef,
inject,
Injector,
- Input,
- QueryList,
+ input,
+ output,
signal,
TrackByFunction,
- output,
- viewChildren
+ untracked,
+ viewChildren,
} from '@angular/core';
import { of } from 'rxjs';
import { finalize, switchMap, tap } from 'rxjs/operators';
@@ -109,58 +110,50 @@ type PermissionWithGroupName = PermissionGrantInfoDto & {
TabContent,
],
})
-export class PermissionManagementComponent
- implements
- PermissionManagement.PermissionManagementComponentInputs,
- PermissionManagement.PermissionManagementComponentOutputs
-{
+export class PermissionManagementComponent {
protected readonly service = inject(PermissionsService);
protected readonly configState = inject(ConfigStateService);
protected readonly toasterService = inject(ToasterService);
private readonly injector = inject(Injector);
private document = inject(DOCUMENT);
- @Input()
- readonly providerName!: string;
- @Input()
- readonly providerKey!: string;
+ readonly providerNameInput = input('', { alias: 'providerName' });
+ readonly providerKeyInput = input('', { alias: 'providerKey' });
+ readonly hideBadgesInput = input(false, { alias: 'hideBadges' });
+ readonly entityDisplayName = input(undefined);
+ readonly visibleInput = input(false, { alias: 'visible' });
- @Input()
- readonly hideBadges = false;
-
- protected _visible = false;
+ // Output signals
+ readonly visibleChange = output();
- @Input()
- entityDisplayName: string | undefined;
+ // Internal state
+ protected readonly _visible = signal(false);
- @Input()
- get visible(): boolean {
- return this._visible;
+ // Backward-compatible getters/setters for ReplaceableTemplateDirective.
+ private _providerNameOverride?: string;
+ get providerName(): string {
+ return this._providerNameOverride ?? this.providerNameInput();
+ }
+ set providerName(value: string) {
+ this._providerNameOverride = value;
}
- set visible(value: boolean) {
- if (value === this._visible) {
- return;
- }
-
- if (value) {
- this.openModal().subscribe(() => {
- this._visible = true;
- this.visibleChange.emit(true);
- afterNextRender(() => {
- this.initModal();
- }, { injector: this.injector });
- });
- } else {
- this.setSelectedGroup(null);
- this._visible = false;
- this.visibleChange.emit(false);
- this.filter.set('');
- }
+ private _providerKeyOverride?: string;
+ get providerKey(): string {
+ return this._providerKeyOverride ?? this.providerKeyInput();
+ }
+ set providerKey(value: string) {
+ this._providerKeyOverride = value;
}
- readonly visibleChange = output();
+ private _hideBadgesOverride?: boolean;
+ get hideBadges(): boolean {
+ return this._hideBadgesOverride ?? this.hideBadgesInput();
+ }
+ set hideBadges(value: boolean) {
+ this._hideBadgesOverride = value;
+ }
selectAllInThisTabsRef = viewChildren>('selectAllInThisTabsRef');
selectAllInAllTabsRef = viewChildren>('selectAllInAllTabsRef');
@@ -214,6 +207,54 @@ export class PermissionManagementComponent
trackByFn: TrackByFunction = (_, item) => item.name;
+ // Getter/setter for visible - used by ReplaceableTemplateDirective and internal code
+ get visible(): boolean {
+ return this._visible();
+ }
+
+ set visible(value: boolean) {
+ if (value === this._visible()) {
+ return;
+ }
+
+ if (value) {
+ this.openModal().subscribe(() => {
+ this._visible.set(true);
+ this.visibleChange.emit(true);
+ afterNextRender(() => {
+ this.initModal();
+ }, { injector: this.injector });
+ });
+ } else {
+ this.setSelectedGroup(null);
+ this._visible.set(false);
+ this.visibleChange.emit(false);
+ this.filter.set('');
+ }
+ }
+
+ constructor() {
+ effect(() => {
+ const inputValue = this.visibleInput();
+ untracked(() => {
+ if (this._visible() !== inputValue) {
+ if (inputValue) {
+ this.openModal().subscribe(() => {
+ this._visible.set(true);
+ afterNextRender(() => {
+ this.initModal();
+ }, { injector: this.injector });
+ });
+ } else {
+ this.setSelectedGroup(null);
+ this._visible.set(false);
+ this.filter.set('');
+ }
+ }
+ });
+ });
+ }
+
getChecked(name: string) {
return (this.permissions.find(per => per.name === name) || { isGranted: false }).isGranted;
}
@@ -345,8 +386,9 @@ export class PermissionManagementComponent
}
setTabCheckboxState() {
+ const providerName = this.providerName;
const selectablePermissions = this.selectedGroupPermissions.filter(per =>
- per.grantedProviders.every(p => p.providerName === this.providerName),
+ per.grantedProviders.every(p => p.providerName === providerName),
);
const selectedPermissions = selectablePermissions.filter(per => per.isGranted);
@@ -367,8 +409,9 @@ export class PermissionManagementComponent
}
setGrantCheckboxState() {
+ const providerName = this.providerName;
const selectablePermissions = this.permissions.filter(per =>
- per.grantedProviders.every(p => p.providerName === this.providerName),
+ per.grantedProviders.every(p => p.providerName === providerName),
);
const selectedAllPermissions = selectablePermissions.filter(per => per.isGranted);
const checkboxElement = this.document.querySelector('#select-all-in-all-tabs') as any;
@@ -468,11 +511,14 @@ export class PermissionManagementComponent
}
openModal() {
- if (!this.providerKey || !this.providerName) {
+ const providerName = this.providerName;
+ const providerKey = this.providerKey;
+
+ if (!providerKey || !providerName) {
throw new Error('Provider Key and Provider Name are required.');
}
- return this.service.get(this.providerName, this.providerKey).pipe(
+ return this.service.get(providerName, providerKey).pipe(
tap((permissionRes: GetPermissionListResultDto) => {
const { groups } = permissionRes || {};
@@ -484,7 +530,7 @@ export class PermissionManagementComponent
this.disabledSelectAllInAllTabs = this.permissions.every(
per =>
per.isGranted &&
- per.grantedProviders.every(provider => provider.providerName !== this.providerName),
+ per.grantedProviders.every(provider => provider.providerName !== providerName),
);
}),
);
@@ -508,10 +554,12 @@ export class PermissionManagementComponent
shouldFetchAppConfig() {
const currentUser = this.configState.getOne('currentUser') as CurrentUserDto;
+ const providerName = this.providerName;
+ const providerKey = this.providerKey;
- if (this.providerName === 'R') return currentUser.roles.some(role => role === this.providerKey);
+ if (providerName === 'R') return currentUser.roles.some(role => role === providerKey);
- if (this.providerName === 'U') return currentUser.id === this.providerKey;
+ if (providerName === 'U') return currentUser.id === providerKey;
return false;
}
diff --git a/npm/ng-packs/packages/setting-management/package.json b/npm/ng-packs/packages/setting-management/package.json
index ded243c31f..af2db6fc98 100644
--- a/npm/ng-packs/packages/setting-management/package.json
+++ b/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"
diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.html b/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.html
index a29fb3323e..afc498d5ac 100644
--- a/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.html
+++ b/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.html
@@ -30,7 +30,7 @@
+ [class.d-block]="smallScreen() && rootDropdownExpand[route.name]">
@@ -60,7 +60,7 @@
diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts b/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts
index 40782d113c..8ec0681e52 100644
--- a/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts
+++ b/npm/ng-packs/packages/theme-basic/src/lib/components/routes/routes.component.ts
@@ -10,9 +10,9 @@ import {
Component,
ElementRef,
inject,
- Input,
Renderer2,
TrackByFunction,
+ input,
viewChildren
} from '@angular/core';
import { NgTemplateOutlet, AsyncPipe } from '@angular/common';
@@ -38,7 +38,7 @@ export class RoutesComponent {
public readonly routesService = inject(RoutesService);
protected renderer = inject(Renderer2);
- @Input() smallScreen?: boolean;
+ readonly smallScreen = input(undefined);
readonly childrenContainers = viewChildren>('childrenContainer');
diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.html b/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.html
index 4b2dbf611d..4480e34c96 100644
--- a/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.html
+++ b/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.html
@@ -1,9 +1,9 @@
-@if (items.length) {
+@if (items().length) {
-
- @for (item of items; track $index; let last = $last) {
+ @for (item of items(); track $index; let last = $last) {
-
[] = [];
+ readonly items = input[]>([]);
}
diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts
index 9a11747b44..010e66f8cb 100644
--- a/npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts
+++ b/npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts
@@ -2,12 +2,14 @@
import {
Component,
ElementRef,
- Input,
OnInit,
Renderer2,
+ computed,
inject,
+ input,
output,
- viewChild
+ signal,
+ viewChild,
} from '@angular/core';
import { ABP, StopPropagationDirective } from '@abp/ng.core';
@@ -16,16 +18,16 @@ import { ABP, StopPropagationDirective } from '@abp/ng.core';
template: `
`,
imports: [StopPropagationDirective],
@@ -33,53 +35,48 @@ import { ABP, StopPropagationDirective } from '@abp/ng.core';
export class ButtonComponent implements OnInit {
private renderer = inject(Renderer2);
- @Input()
- buttonId = '';
+ readonly buttonId = input('');
+ readonly buttonClass = input('btn btn-primary');
+ readonly buttonType = input('button');
+ readonly formName = input(undefined);
+ readonly iconClass = input(undefined);
+ readonly loadingInput = input(false, { alias: 'loading' });
+ readonly disabled = input(false);
+ readonly attributes = input | undefined>(undefined);
- @Input()
- buttonClass = 'btn btn-primary';
+ // Internal writable signal for loading state - can be set programmatically
+ private readonly _loading = signal(false);
- @Input()
- buttonType = 'button';
+ // Computed that combines input and internal state
+ readonly isLoading = computed(() => this.loadingInput() || this._loading());
- @Input()
- formName?: string = undefined;
-
- @Input()
- iconClass?: string;
-
- @Input()
- loading = false;
-
- @Input()
- disabled: boolean | undefined = false;
-
- @Input()
- attributes?: ABP.Dictionary;
+ // Getter/setter for backward compatibility (used by ModalComponent)
+ get loading(): boolean {
+ return this._loading();
+ }
+ set loading(value: boolean) {
+ this._loading.set(value);
+ }
readonly click = output();
-
readonly focus = output();
-
readonly blur = output();
-
readonly abpClick = output();
-
readonly abpFocus = output();
-
readonly abpBlur = output();
readonly buttonRef = viewChild.required>('button');
- get icon(): string {
- return `${this.loading ? 'fa fa-spinner fa-spin' : this.iconClass || 'd-none'}`;
- }
+ protected readonly icon = computed(() => {
+ return this.isLoading() ? '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 => {
+ if (attributes[key]) {
+ this.renderer.setAttribute(this.buttonRef().nativeElement, key, attributes[key]);
}
});
}
diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-body.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-body.component.ts
index 6f8eade8d2..617271ca50 100644
--- a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-body.component.ts
+++ b/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: `
+ template: `
`,
})
export class CardBodyComponent {
@HostBinding('class') componentClass = 'card-body';
- @Input() cardBodyClass: string;
- @Input() cardBodyStyle: string;
+ readonly cardBodyClass = input
(undefined);
+ readonly cardBodyStyle = input(undefined);
}
diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-footer.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-footer.component.ts
index 3b96418fbd..95bcb91b88 100644
--- a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-footer.component.ts
+++ b/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: `
-
+
`,
@@ -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
(undefined);
+ readonly cardFooterClass = input(undefined);
}
diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-header.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-header.component.ts
index 3c31d16d74..692a261195 100644
--- a/npm/ng-packs/packages/theme-shared/src/lib/components/card/card-header.component.ts
+++ b/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: `
-
+