Browse Source

update: some upgrade enhancements for the components

pull/25690/head
sumeyye 2 months ago
parent
commit
00e63d833f
  1. 4
      npm/ng-packs/packages/components/chart.js/src/chart.component.ts
  2. 2
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-array/dynamic-form-array.component.html
  3. 14
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-array/dynamic-form-array.component.ts
  4. 6
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field-host.component.ts
  5. 711
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field.component.html
  6. 97
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field.component.ts
  7. 20
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.ts
  8. 43
      npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.html
  9. 99
      npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts
  10. 2
      npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form.component.ts
  11. 3
      npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table-row-detail/extensible-table-row-detail.component.ts
  12. 6
      npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.html
  13. 3
      npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts
  14. 3
      npm/ng-packs/packages/components/page/src/page-parts.component.ts
  15. 11
      npm/ng-packs/packages/components/page/src/page.component.ts
  16. 41
      npm/ng-packs/packages/components/tree/src/lib/components/tree.component.ts

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

@ -1,7 +1,6 @@
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
OnDestroy,
@ -36,7 +35,6 @@ let Chart: any;
})
export class ChartComponent implements AfterViewInit, OnDestroy {
el = inject(ElementRef);
private cdr = inject(ChangeDetectorRef);
readonly type = input.required<string>();
readonly data = input<any>({});
@ -129,7 +127,6 @@ export class ChartComponent implements AfterViewInit, OnDestroy {
refresh = () => {
if (this.chart) {
this.chart.update();
this.cdr.detectChanges();
}
};
@ -146,4 +143,3 @@ export class ChartComponent implements AfterViewInit, OnDestroy {
}
}
}

2
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-array/dynamic-form-array.component.html

@ -22,7 +22,7 @@
<!-- Array Items -->
<div class="array-items" role="list">
@for (item of formArray.controls; track trackByIndex($index)) {
@for (item of formArray.controls; track $index + '-' + formArrayVersion()) {
<div class="array-item border rounded p-3 mb-3" role="listitem" [formGroup]="getItemFormGroup($index)">
<!-- Item Header -->

14
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-array/dynamic-form-array.component.ts

@ -3,7 +3,7 @@ import {
Component,
input,
inject,
ChangeDetectorRef,
signal,
forwardRef,
} from '@angular/core';
import { FormGroup, FormArray, FormBuilder, ReactiveFormsModule } from '@angular/forms';
@ -35,7 +35,7 @@ export class DynamicFormArrayComponent {
private fb = inject(FormBuilder);
private dynamicFormService = inject(DynamicFormService);
private cdr = inject(ChangeDetectorRef);
readonly formArrayVersion = signal(0);
get formArray(): FormArray {
return this.formGroup().get(this.arrayConfig().key) as FormArray;
@ -58,20 +58,20 @@ export class DynamicFormArrayComponent {
addItem() {
if (!this.canAddItem) return;
const itemGroup = this.dynamicFormService.createFormGroup(
this.arrayConfig().children || []
);
this.formArray.push(itemGroup);
this.cdr.markForCheck();
this.formArrayVersion.update(version => version + 1);
}
removeItem(index: number) {
if (!this.canRemoveItem) return;
this.formArray.removeAt(index);
this.cdr.markForCheck();
this.formArrayVersion.update(version => version + 1);
}
getItemFormGroup(index: number): FormGroup {

6
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field-host.component.ts

@ -1,5 +1,4 @@
import {
Component,
import {Component,
ViewContainerRef,
ChangeDetectionStrategy,
forwardRef,
@ -8,8 +7,7 @@ import {
DestroyRef,
inject,
input,
viewChild
} from '@angular/core';
viewChild,} from '@angular/core';
import {
ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl, ReactiveFormsModule
} from '@angular/forms';

711
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field.component.html

@ -1,356 +1,368 @@
@if (visible()) {
<div [formGroup]="fieldFormGroup" role="group" [attr.aria-labelledby]="fieldId + '-label'">
<!-- NOTE: Group and Array types are NOT rendered here, they should be rendered at parent level -->
<!-- This component only handles leaf/primitive field types -->
@if (field().type === 'text') {
<!-- Text Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
[id]="fieldId"
[placeholder]="(field().placeholder || '') | abpLocalization"
formControlName="value"
[class.is-invalid]="isInvalid"
class="form-control"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid || null"
[attr.aria-describedby]="isInvalid ? errorId : null"
[attr.aria-label]="field().label | abpLocalization">
@if (isInvalid) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'select') {
<!-- Select Dropdown -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<select
[id]="fieldId"
formControlName="value"
[class.is-invalid]="isInvalid"
class="form-control"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid || null"
[attr.aria-describedby]="isInvalid ? errorId : null"
[attr.aria-label]="field().label | abpLocalization">
<option value="">{{ '::Select' | abpLocalization }}</option>
@for (option of options$ | async; track option.key) {
<option [value]="option.key">
{{ option.value | abpLocalization }}
</option>
}
</select>
@if (isInvalid) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'checkbox') {
<!-- Checkbox -->
<div class="form-group form-check mb-3" role="group">
<abp-checkbox
[label]="field().label | abpLocalization"
formControlName="value"
[checkboxId]="fieldId"
[id]="fieldId"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid || null"
[attr.aria-describedby]="isInvalid ? errorId : null" />
@if (isInvalid) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'email') {
<!-- Email Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
type="email"
[id]="fieldId"
formControlName="value"
[placeholder]="(field().placeholder || '') | abpLocalization"
[class.is-invalid]="isInvalid"
class="form-control"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid || null"
[attr.aria-describedby]="isInvalid ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
autocomplete="email">
@if (isInvalid) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'textarea') {
<!-- Textarea -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<textarea
[id]="fieldId"
formControlName="value"
[placeholder]="(field().placeholder || '') | abpLocalization"
[class.is-invalid]="isInvalid"
rows="4"
class="form-control"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid || null"
[attr.aria-describedby]="isInvalid ? errorId : null"
[attr.aria-label]="field().label | abpLocalization">
<div [formGroup]="fieldFormGroup" role="group" [attr.aria-labelledby]="fieldId + '-label'">
<!-- NOTE: Group and Array types are NOT rendered here, they should be rendered at parent level -->
<!-- This component only handles leaf/primitive field types -->
@if (field().type === 'text') {
<!-- Text Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
[id]="fieldId"
[placeholder]="field().placeholder || '' | abpLocalization"
formControlName="value"
[class.is-invalid]="isInvalid()"
class="form-control"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid() || null"
[attr.aria-describedby]="isInvalid() ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
/>
@if (isInvalid()) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'select') {
<!-- Select Dropdown -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<select
[id]="fieldId"
formControlName="value"
[class.is-invalid]="isInvalid()"
class="form-control"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid() || null"
[attr.aria-describedby]="isInvalid() ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
>
<option value="">{{ '::Select' | abpLocalization }}</option>
@for (option of options$ | async; track option.key) {
<option [value]="option.key">
{{ option.value | abpLocalization }}
</option>
}
</select>
@if (isInvalid()) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'checkbox') {
<!-- Checkbox -->
<div class="form-group form-check mb-3" role="group">
<abp-checkbox
[label]="field().label | abpLocalization"
formControlName="value"
[checkboxId]="fieldId"
[id]="fieldId"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid() || null"
[attr.aria-describedby]="isInvalid() ? errorId : null"
/>
@if (isInvalid()) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'email') {
<!-- Email Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
type="email"
[id]="fieldId"
formControlName="value"
[placeholder]="field().placeholder || '' | abpLocalization"
[class.is-invalid]="isInvalid()"
class="form-control"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid() || null"
[attr.aria-describedby]="isInvalid() ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
autocomplete="email"
/>
@if (isInvalid()) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'textarea') {
<!-- Textarea -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<textarea
[id]="fieldId"
formControlName="value"
[placeholder]="field().placeholder || '' | abpLocalization"
[class.is-invalid]="isInvalid()"
rows="4"
class="form-control"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid() || null"
[attr.aria-describedby]="isInvalid() ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
>
</textarea>
@if (isInvalid) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'number') {
<!-- Number Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
type="number"
[id]="fieldId"
formControlName="value"
[placeholder]="(field().placeholder || '') | abpLocalization"
[class.is-invalid]="isInvalid"
class="form-control"
[attr.min]="field().min || null"
[attr.max]="field().max || null"
[attr.step]="field().step || null"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid || null"
[attr.aria-describedby]="isInvalid ? errorId : null"
[attr.aria-label]="field().label | abpLocalization">
@if (isInvalid) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'date') {
<!-- Date Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
type="date"
[id]="fieldId"
formControlName="value"
[class.is-invalid]="isInvalid"
class="form-control"
[attr.min]="field().min || null"
[attr.max]="field().max || null"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid || null"
[attr.aria-describedby]="isInvalid ? errorId : null"
[attr.aria-label]="field().label | abpLocalization">
@if (isInvalid) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'datetime-local') {
<!-- DateTime Local Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
type="datetime-local"
[id]="fieldId"
formControlName="value"
[class.is-invalid]="isInvalid"
class="form-control"
[attr.min]="field().min || null"
[attr.max]="field().max || null"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid || null"
[attr.aria-describedby]="isInvalid ? errorId : null"
[attr.aria-label]="field().label | abpLocalization">
@if (isInvalid) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'time') {
<!-- Time Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
type="time"
[id]="fieldId"
formControlName="value"
[class.is-invalid]="isInvalid"
class="form-control"
[attr.min]="field().min || null"
[attr.max]="field().max || null"
[attr.step]="field().step || null"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid || null"
[attr.aria-describedby]="isInvalid ? errorId : null"
[attr.aria-label]="field().label | abpLocalization">
@if (isInvalid) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'password') {
<!-- Password Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
type="password"
[id]="fieldId"
formControlName="value"
[placeholder]="(field().placeholder || '') | abpLocalization"
[class.is-invalid]="isInvalid"
class="form-control"
[attr.minlength]="field().minLength || null"
[attr.maxlength]="field().maxLength || null"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid || null"
[attr.aria-describedby]="isInvalid ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
autocomplete="new-password">
@if (isInvalid) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'tel') {
<!-- Tel Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
type="tel"
[id]="fieldId"
formControlName="value"
[placeholder]="(field().placeholder || '') | abpLocalization"
[class.is-invalid]="isInvalid"
class="form-control"
[attr.pattern]="field().pattern || null"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid || null"
[attr.aria-describedby]="isInvalid ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
autocomplete="tel">
@if (isInvalid) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'url') {
<!-- URL Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
type="url"
[id]="fieldId"
formControlName="value"
[placeholder]="(field().placeholder || '') | abpLocalization"
[class.is-invalid]="isInvalid"
class="form-control"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid || null"
[attr.aria-describedby]="isInvalid ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
autocomplete="url">
@if (isInvalid) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'radio') {
<!-- Radio Group -->
<div class="form-group mb-3" role="radiogroup" [attr.aria-labelledby]="fieldId + '-label'">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<div class="radio-group">
@for (option of options$ | async; track option.key) {
<div class="form-check">
<input
type="radio"
[id]="fieldId + '-' + option.key"
[value]="option.key"
@if (isInvalid()) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'number') {
<!-- Number Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
type="number"
[id]="fieldId"
formControlName="value"
[class.is-invalid]="isInvalid"
class="form-check-input"
[placeholder]="field().placeholder || '' | abpLocalization"
[class.is-invalid]="isInvalid()"
class="form-control"
[attr.min]="field().min || null"
[attr.max]="field().max || null"
[attr.step]="field().step || null"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid || null"
[attr.aria-describedby]="isInvalid ? errorId : null">
<label [for]="fieldId + '-' + option.key" class="form-check-label">
{{ option.value | abpLocalization }}
</label>
[attr.aria-invalid]="isInvalid() || null"
[attr.aria-describedby]="isInvalid() ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
/>
@if (isInvalid()) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'date') {
<!-- Date Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
type="date"
[id]="fieldId"
formControlName="value"
[class.is-invalid]="isInvalid()"
class="form-control"
[attr.min]="field().min || null"
[attr.max]="field().max || null"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid() || null"
[attr.aria-describedby]="isInvalid() ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
/>
@if (isInvalid()) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'datetime-local') {
<!-- DateTime Local Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
type="datetime-local"
[id]="fieldId"
formControlName="value"
[class.is-invalid]="isInvalid()"
class="form-control"
[attr.min]="field().min || null"
[attr.max]="field().max || null"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid() || null"
[attr.aria-describedby]="isInvalid() ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
/>
@if (isInvalid()) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'time') {
<!-- Time Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
type="time"
[id]="fieldId"
formControlName="value"
[class.is-invalid]="isInvalid()"
class="form-control"
[attr.min]="field().min || null"
[attr.max]="field().max || null"
[attr.step]="field().step || null"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid() || null"
[attr.aria-describedby]="isInvalid() ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
/>
@if (isInvalid()) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'password') {
<!-- Password Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
type="password"
[id]="fieldId"
formControlName="value"
[placeholder]="field().placeholder || '' | abpLocalization"
[class.is-invalid]="isInvalid()"
class="form-control"
[attr.minlength]="field().minLength || null"
[attr.maxlength]="field().maxLength || null"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid() || null"
[attr.aria-describedby]="isInvalid() ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
autocomplete="new-password"
/>
@if (isInvalid()) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'tel') {
<!-- Tel Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
type="tel"
[id]="fieldId"
formControlName="value"
[placeholder]="field().placeholder || '' | abpLocalization"
[class.is-invalid]="isInvalid()"
class="form-control"
[attr.pattern]="field().pattern || null"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid() || null"
[attr.aria-describedby]="isInvalid() ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
autocomplete="tel"
/>
@if (isInvalid()) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'url') {
<!-- URL Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
type="url"
[id]="fieldId"
formControlName="value"
[placeholder]="field().placeholder || '' | abpLocalization"
[class.is-invalid]="isInvalid()"
class="form-control"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid() || null"
[attr.aria-describedby]="isInvalid() ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
autocomplete="url"
/>
@if (isInvalid()) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'radio') {
<!-- Radio Group -->
<div class="form-group mb-3" role="radiogroup" [attr.aria-labelledby]="fieldId + '-label'">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<div class="radio-group">
@for (option of options$ | async; track option.key) {
<div class="form-check">
<input
type="radio"
[id]="fieldId + '-' + option.key"
[value]="option.key"
formControlName="value"
[class.is-invalid]="isInvalid()"
class="form-check-input"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid() || null"
[attr.aria-describedby]="isInvalid() ? errorId : null"
/>
<label [for]="fieldId + '-' + option.key" class="form-check-label">
{{ option.value | abpLocalization }}
</label>
</div>
}
</div>
@if (isInvalid()) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'file') {
<!-- File Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
type="file"
[id]="fieldId"
(change)="onFileChange($event)"
[class.is-invalid]="isInvalid()"
class="form-control"
[attr.accept]="field().accept || null"
[attr.multiple]="field().multiple || null"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid() || null"
[attr.aria-describedby]="isInvalid() ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
/>
@if (isInvalid()) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'range') {
<!-- Range Slider -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<div class="d-flex align-items-center gap-3">
<input
type="range"
[id]="fieldId"
formControlName="value"
[class.is-invalid]="isInvalid()"
class="form-range flex-grow-1"
[attr.min]="field().min || 0"
[attr.max]="field().max || 100"
[attr.step]="field().step || 1"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid() || null"
[attr.aria-describedby]="isInvalid() ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
[attr.aria-valuenow]="value.value"
[attr.aria-valuemin]="field().min || 0"
[attr.aria-valuemax]="field().max || 100"
/>
<output [for]="fieldId" class="badge bg-primary">{{ value.value }}</output>
</div>
@if (isInvalid()) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'color') {
<!-- Color Picker -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<div class="d-flex align-items-center gap-3">
<input
type="color"
[id]="fieldId"
formControlName="value"
[class.is-invalid]="isInvalid()"
class="form-control form-control-color"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid() || null"
[attr.aria-describedby]="isInvalid() ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
/>
<code class="text-muted">{{ value.value || '#000000' }}</code>
</div>
@if (isInvalid()) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
}
</div>
@if (isInvalid) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'file') {
<!-- File Input -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<input
type="file"
[id]="fieldId"
(change)="onFileChange($event)"
[class.is-invalid]="isInvalid"
class="form-control"
[attr.accept]="field().accept || null"
[attr.multiple]="field().multiple || null"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid || null"
[attr.aria-describedby]="isInvalid ? errorId : null"
[attr.aria-label]="field().label | abpLocalization">
@if (isInvalid) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'range') {
<!-- Range Slider -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<div class="d-flex align-items-center gap-3">
<input
type="range"
[id]="fieldId"
formControlName="value"
[class.is-invalid]="isInvalid"
class="form-range flex-grow-1"
[attr.min]="field().min || 0"
[attr.max]="field().max || 100"
[attr.step]="field().step || 1"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid || null"
[attr.aria-describedby]="isInvalid ? errorId : null"
[attr.aria-label]="field().label | abpLocalization"
[attr.aria-valuenow]="value.value"
[attr.aria-valuemin]="field().min || 0"
[attr.aria-valuemax]="field().max || 100">
<output [for]="fieldId" class="badge bg-primary">{{ value.value }}</output>
</div>
@if (isInvalid) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
} @else if (field().type === 'color') {
<!-- Color Picker -->
<div class="form-group mb-3">
<ng-container [ngTemplateOutlet]="labelTemplate" />
<div class="d-flex align-items-center gap-3">
<input
type="color"
[id]="fieldId"
formControlName="value"
[class.is-invalid]="isInvalid"
class="form-control form-control-color"
[attr.aria-required]="field().required || null"
[attr.aria-invalid]="isInvalid || null"
[attr.aria-describedby]="isInvalid ? errorId : null"
[attr.aria-label]="field().label | abpLocalization">
<code class="text-muted">{{ value.value || '#000000' }}</code>
</div>
@if (isInvalid) {
<ng-container [ngTemplateOutlet]="errorTemplate" />
}
</div>
}
</div>
}
<ng-template #labelTemplate>
<label
[for]="fieldId"
[id]="fieldId + '-label'"
[class.required]="field().required">
<label [for]="fieldId" [id]="fieldId + '-label'" [class.required]="field().required">
{{ field().label | abpLocalization }}
@if (field().required) {
<span class="text-danger" aria-label="required">*</span>
@ -359,14 +371,9 @@
</ng-template>
<ng-template #errorTemplate>
<div
class="invalid-feedback"
[id]="errorId"
role="alert"
aria-live="polite"
aria-atomic="true">
@for (error of errors; track error) {
<div>{{ error | abpLocalization }}</div>
<div class="invalid-feedback" [id]="errorId" role="alert" aria-live="polite" aria-atomic="true">
@for (error of errors(); track error) {
<div>{{ error | abpLocalization }}</div>
}
</div>
</ng-template>
</ng-template>

97
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field.component.ts

@ -1,13 +1,15 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
computed,
DestroyRef,
forwardRef,
inject,
InjectionToken, Injector,
InjectionToken,
Injector,
input,
OnInit,
signal,
} from '@angular/core';
import { FormFieldConfig } from '../dynamic-form.models';
import {
@ -21,6 +23,7 @@ import {
FormGroup,
ReactiveFormsModule,
} from '@angular/forms';
import { merge } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { NgTemplateOutlet, AsyncPipe } from '@angular/common';
import { LocalizationPipe } from '@abp/ng.core';
@ -28,7 +31,9 @@ import { FormCheckboxComponent } from '@abp/ng.theme.shared';
import { Observable, of } from 'rxjs';
import { DynamicFormService } from '../dynamic-form.service';
export const ABP_DYNAMIC_FORM_FIELD = new InjectionToken<DynamicFormFieldComponent>('AbpDynamicFormField');
export const ABP_DYNAMIC_FORM_FIELD = new InjectionToken<DynamicFormFieldComponent>(
'AbpDynamicFormField',
);
const DYNAMIC_FORM_FIELD_CONTROL_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
@ -47,18 +52,24 @@ const DYNAMIC_FORM_FIELD_CONTROL_VALUE_ACCESSOR = {
host: { class: 'abp-dynamic-form-field' },
exportAs: 'abpDynamicFormField',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgTemplateOutlet, LocalizationPipe, ReactiveFormsModule, FormCheckboxComponent, AsyncPipe],
imports: [
NgTemplateOutlet,
LocalizationPipe,
ReactiveFormsModule,
FormCheckboxComponent,
AsyncPipe,
],
})
export class DynamicFormFieldComponent implements OnInit, ControlValueAccessor {
field = input.required<FormFieldConfig>();
visible = input<boolean>(true);
control!: FormControl;
fieldFormGroup: FormGroup;
readonly changeDetectorRef = inject(ChangeDetectorRef);
readonly destroyRef = inject(DestroyRef);
private injector = inject(Injector);
private formBuilder = inject(FormBuilder);
private dynamicFormService = inject(DynamicFormService);
private readonly controlStateTick = signal(0);
options$: Observable<{ key: string; value: any }[]> = of([]);
@ -75,6 +86,38 @@ export class DynamicFormFieldComponent implements OnInit, ControlValueAccessor {
return `${this.fieldId}-help`;
}
readonly isInvalid = computed(() => {
this.controlStateTick();
if (this.control) {
return this.control.invalid && (this.control.dirty || this.control.touched);
}
return false;
});
readonly errors = computed(() => {
this.controlStateTick();
if (!this.control?.errors) return [];
if (this.control && this.control.errors) {
const errorKeys = Object.keys(this.control.errors);
const validators = this.field().validators || [];
return errorKeys.map(key => {
const validator = validators.find(v => v.type.toLowerCase() === key.toLowerCase());
if (validator && validator.message) {
return validator.message;
}
// Fallback error messages
if (key === 'required') return `${this.field().label} is required`;
if (key === 'email') return 'Please enter a valid email address';
if (key === 'minlength')
return `Minimum length is ${this.control.errors[key].requiredLength}`;
if (key === 'maxlength')
return `Maximum length is ${this.control.errors[key].requiredLength}`;
return `${this.field().label} is invalid due to ${key} validation.`;
});
}
return [];
});
constructor() {
this.fieldFormGroup = this.formBuilder.group({
value: [{ value: '' }],
@ -85,6 +128,9 @@ export class DynamicFormFieldComponent implements OnInit, ControlValueAccessor {
const ngControl = this.injector.get(NgControl, null);
if (ngControl) {
this.control = this.injector.get(FormGroupDirective).getControl(ngControl as FormControlName);
merge(this.control.statusChanges, this.control.valueChanges)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => this.controlStateTick.update(tick => tick + 1));
}
this.value.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => {
this.onChange(value);
@ -99,9 +145,9 @@ export class DynamicFormFieldComponent implements OnInit, ControlValueAccessor {
options.defaultValues.map(item => {
return {
key: item[options.valueProp || 'key'] || item,
value: item[options.labelProp || 'value'] || item
value: item[options.labelProp || 'value'] || item,
};
})
}),
);
} else {
this.options$ = of([]);
@ -110,7 +156,6 @@ export class DynamicFormFieldComponent implements OnInit, ControlValueAccessor {
writeValue(value: any[]): void {
this.value.setValue(value || '');
this.changeDetectorRef.markForCheck();
}
registerOnChange(fn: any): void {
@ -127,40 +172,8 @@ export class DynamicFormFieldComponent implements OnInit, ControlValueAccessor {
} else {
this.value.enable();
}
this.changeDetectorRef.markForCheck();
}
get isInvalid(): boolean {
if (this.control) {
return this.control.invalid && (this.control.dirty || this.control.touched);
}
return false;
}
get errors(): string[] {
if (!this.control?.errors) return [];
if (this.control && this.control.errors) {
const errorKeys = Object.keys(this.control.errors);
const validators = this.field().validators || [];
return errorKeys.map(key => {
const validator = validators.find(
v => v.type.toLowerCase() === key.toLowerCase(),
);
if (validator && validator.message) {
return validator.message;
}
// Fallback error messages
if (key === 'required') return `${this.field().label} is required`;
if (key === 'email') return 'Please enter a valid email address';
if (key === 'minlength')
return `Minimum length is ${this.control.errors[key].requiredLength}`;
if (key === 'maxlength')
return `Maximum length is ${this.control.errors[key].requiredLength}`;
return `${this.field().label} is invalid due to ${key} validation.`;
});
}
return [];
}
get value() {
return this.fieldFormGroup.get('value');
}
@ -175,6 +188,6 @@ export class DynamicFormFieldComponent implements OnInit, ControlValueAccessor {
}
}
private onChange: (value: any) => void = () => { };
private onTouched: () => void = () => { };
private onChange: (value: any) => void = () => {};
private onTouched: () => void = () => {};
}

20
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.ts

@ -6,7 +6,7 @@ import {
inject,
OnInit,
DestroyRef,
ChangeDetectorRef,
signal,
} from '@angular/core';
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
@ -43,10 +43,9 @@ export class DynamicFormComponent implements OnInit {
formCancel = output<void>();
private dynamicFormService = inject(DynamicFormService);
readonly destroyRef = inject(DestroyRef);
readonly changeDetectorRef = inject(ChangeDetectorRef);
dynamicForm!: FormGroup;
fieldVisibility: { [key: string]: boolean } = {};
readonly fieldVisibility = signal<Record<string, boolean>>({});
ngOnInit() {
this.setupFormAndLogic();
@ -74,7 +73,7 @@ export class DynamicFormComponent implements OnInit {
}
isFieldVisible(field: FormFieldConfig): boolean {
return this.fieldVisibility[field.key] !== false;
return this.fieldVisibility()[field.key] !== false;
}
getChildFormGroup(key: string): FormGroup {
@ -88,16 +87,14 @@ export class DynamicFormComponent implements OnInit {
this.dynamicForm.reset({ ...initialValues });
this.dynamicForm.markAsUntouched();
this.dynamicForm.markAsPristine();
this.changeDetectorRef.markForCheck();
}
private initializeFieldVisibility() {
const visibility: Record<string, boolean> = {};
this.fields().forEach(field => {
this.fieldVisibility = {
...this.fieldVisibility,
[field.key]: !field.conditionalLogic?.length,
};
visibility[field.key] = !field.conditionalLogic?.length;
});
this.fieldVisibility.set(visibility);
}
private setupConditionalLogic() {
@ -152,10 +149,10 @@ export class DynamicFormComponent implements OnInit {
switch (action) {
case ConditionalAction.SHOW:
this.fieldVisibility = { ...this.fieldVisibility, [fieldKey]: shouldApply };
this.fieldVisibility.update(visibility => ({ ...visibility, [fieldKey]: shouldApply }));
break;
case ConditionalAction.HIDE:
this.fieldVisibility = { ...this.fieldVisibility, [fieldKey]: !shouldApply };
this.fieldVisibility.update(visibility => ({ ...visibility, [fieldKey]: !shouldApply }));
break;
case ConditionalAction.ENABLE:
if (control) {
@ -174,7 +171,6 @@ export class DynamicFormComponent implements OnInit {
this.dynamicForm = this.dynamicFormService.createFormGroup(this.fields());
this.initializeFieldVisibility();
this.setupConditionalLogic();
this.changeDetectorRef.markForCheck();
}
private markAllFieldsAsTouched() {

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

@ -1,11 +1,11 @@
<ng-container *abpPermission="prop().permission; runChangeDetection: false">
@switch (getComponent(prop())) {
@case ('template') {
<ng-container *ngComponentOutlet="prop().template; injector: injectorForCustomComponent" />
<ng-container *ngComponentOutlet="prop().template; injector: injectorForCustomComponent()" />
}
}
<div [class]="containerClassName" class="mb-2">
<div [class]="containerClassName()" class="mb-2">
@switch (getComponent(prop())) {
@case ('input') {
<ng-template [ngTemplateOutlet]="label" />
@ -15,8 +15,8 @@
[formControlName]="prop().name"
[autocomplete]="prop().autocomplete"
[type]="getType(prop())"
[abpDisabled]="disabled"
[readonly]="readonly"
[abpDisabled]="disabled()"
[readonly]="isReadonly()"
class="form-control"
/>
}
@ -29,7 +29,7 @@
#field
[id]="prop().id"
[formControlName]="prop().name"
[abpDisabled]="disabled"
[abpDisabled]="disabled()"
type="checkbox"
class="form-check-input"
/>
@ -45,10 +45,10 @@
#field
[id]="prop().id"
[formControlName]="prop().name"
[abpDisabled]="disabled"
[abpDisabled]="disabled()"
class="form-select form-control"
>
@for (option of options$ | async; track option.value) {
@for (option of options$() | async; track option.value) {
<option [ngValue]="option.value">
@if (prop().isExtra) {
{{ '::' + option.key | abpLocalization }}
@ -63,9 +63,9 @@
<ng-template [ngTemplateOutlet]="label"></ng-template>
<abp-extensible-form-multi-select
[prop]="prop()"
[options]="options$ | async"
[options]="options$() | async"
[formControlName]="prop().name"
[abpDisabled]="disabled"
[abpDisabled]="disabled()"
/>
}
@case ('typeahead') {
@ -75,15 +75,16 @@
#field
[id]="prop().id"
[autocomplete]="prop().autocomplete"
[abpDisabled]="disabled"
[abpDisabled]="disabled()"
[ngbTypeahead]="search"
[editable]="false"
[inputFormatter]="typeaheadFormatter"
[resultFormatter]="typeaheadFormatter"
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="typeaheadModel"
[ngModel]="typeaheadModel()"
(ngModelChange)="typeaheadModel.set($event)"
(selectItem)="setTypeaheadValue($event.item)"
(blur)="setTypeaheadValue(typeaheadModel)"
(blur)="setTypeaheadValue(typeaheadModel())"
[class.is-invalid]="typeahead.classList.contains('is-invalid')"
class="form-control"
/>
@ -117,8 +118,8 @@
#field
[id]="prop().id"
[formControlName]="prop().name"
[abpDisabled]="disabled"
[readonly]="readonly"
[abpDisabled]="disabled()"
[readonly]="isReadonly()"
class="form-control"
></textarea>
}
@ -129,15 +130,19 @@
class="form-control"
[id]="prop().id"
[formControlName]="prop().name"
[abpShowPassword]="showPassword"
[abpShowPassword]="showPassword()"
/>
<button class="btn btn-secondary" type="button" (click)="showPassword = !showPassword">
<button
class="btn btn-secondary"
type="button"
(click)="showPassword.update(value => !value)"
>
<i
class="fa"
aria-hidden="true"
[class]="{
'fa-eye-slash': !showPassword,
'fa-eye': showPassword,
'fa-eye-slash': !showPassword(),
'fa-eye': showPassword(),
}"
></i>
</button>
@ -163,7 +168,7 @@
{{ prop().displayName | abpLocalization }}
}
}
{{ asterisk }}
{{ asterisk() }}
@if (prop().tooltip) {
<i
[ngbTooltip]="prop().tooltip.text | abpLocalization"

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

@ -9,8 +9,8 @@ import {
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
computed,
ElementRef,
inject,
Injector,
@ -19,6 +19,7 @@ import {
viewChild,
effect,
input,
signal,
} from '@angular/core';
import {
ControlContainer,
@ -85,7 +86,6 @@ import { ExtensibleFormMultiselectComponent } from '../multi-select/extensible-f
})
export class ExtensibleFormPropComponent implements AfterViewInit {
protected service = inject(ExtensibleFormPropService);
public readonly cdRef = inject(ChangeDetectorRef);
public readonly track = inject(TrackByService);
#groupDirective = inject(FormGroupDirective);
private injector = inject(Injector);
@ -97,26 +97,24 @@ export class ExtensibleFormPropComponent implements AfterViewInit {
readonly isFirstGroup = input<boolean | undefined>(undefined);
private readonly fieldRef = viewChild.required<ElementRef<HTMLElement>>('field');
injectorForCustomComponent?: Injector;
asterisk = '';
containerClassName = 'mb-2';
showPassword = false;
options$: Observable<ABP.Option<any>[]> = of([]);
validators: ValidatorFn[] = [];
readonly!: boolean;
typeaheadModel: any;
readonly injectorForCustomComponent = signal<Injector | undefined>(undefined);
readonly asterisk = signal('');
readonly containerClassName = signal('mb-2');
readonly showPassword = signal(false);
readonly options$ = signal<Observable<ABP.Option<any>[]>>(of([]));
readonly validators = signal<ValidatorFn[]>([]);
readonly isReadonly = signal(false);
readonly typeaheadModel = signal<any>(undefined);
passwordKey = eExtensibleComponents.PasswordComponent;
readonly disabledFn = signal<(data: ReadonlyPropData) => boolean>(() => false);
disabledFn = (data: ReadonlyPropData) => false;
get disabled() {
readonly disabled = computed(() => {
const data = this.data();
if (!data) return false;
return this.disabledFn(data);
}
return this.disabledFn()(data);
});
constructor() {
// Watch prop changes and update state
effect(() => {
const currentProp = this.prop();
const data = this.data();
@ -125,47 +123,60 @@ export class ExtensibleFormPropComponent implements AfterViewInit {
const { options, readonly, disabled, validators, className, template } = currentProp;
if (template) {
this.injectorForCustomComponent = Injector.create({
providers: [
{
provide: EXTENSIONS_FORM_PROP,
useValue: currentProp,
},
{
provide: EXTENSIONS_FORM_PROP_DATA,
useValue: data?.record,
},
{ provide: ControlContainer, useExisting: FormGroupDirective },
],
parent: this.injector,
});
this.injectorForCustomComponent.set(
Injector.create({
providers: [
{
provide: EXTENSIONS_FORM_PROP,
useValue: currentProp,
},
{
provide: EXTENSIONS_FORM_PROP_DATA,
useValue: data?.record,
},
{ provide: ControlContainer, useExisting: FormGroupDirective },
],
parent: this.injector,
}),
);
} else {
this.injectorForCustomComponent.set(undefined);
}
if (options) this.options$ = options(data);
if (readonly) this.readonly = readonly(data);
if (options) this.options$.set(options(data));
if (readonly) this.isReadonly.set(readonly(data));
if (disabled) {
this.disabledFn = disabled;
this.disabledFn.set(disabled);
} else {
this.disabledFn.set(() => false);
}
if (validators) {
this.validators = validators(data);
this.setAsterisk();
this.validators.set(validators(data));
this.asterisk.set(this.service.calcAsterisks(validators(data)));
} else {
this.validators.set([]);
this.asterisk.set('');
}
if (className !== undefined) {
this.containerClassName = className;
this.containerClassName.set(className);
} else {
this.containerClassName.set('mb-2');
}
const [keyControl, valueControl] = this.getTypeaheadControls();
if (keyControl && valueControl)
this.typeaheadModel = { key: keyControl.value, value: valueControl.value };
this.cdRef.markForCheck();
if (keyControl && valueControl) {
this.typeaheadModel.set({ key: keyControl.value, value: valueControl.value });
}
});
}
setTypeaheadValue(selectedOption: ABP.Option<string>) {
this.typeaheadModel = selectedOption || { key: null, value: null };
const { key, value } = this.typeaheadModel;
const model = selectedOption || { key: null, value: null };
this.typeaheadModel.set(model);
const { key, value } = model;
const [keyControl, valueControl] = this.getTypeaheadControls();
if (valueControl?.value && !value) valueControl.markAsDirty();
keyControl?.setValue(key);
@ -200,10 +211,6 @@ export class ExtensibleFormPropComponent implements AfterViewInit {
return [keyControl, valueControl];
}
private setAsterisk() {
this.asterisk = this.service.calcAsterisks(this.validators);
}
ngAfterViewInit() {
if (this.isFirstGroup() && this.first() && this.fieldRef()) {
requestAnimationFrame(() => {

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

@ -1,7 +1,6 @@
import { TrackByService } from '@abp/ng.core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
inject,
Optional,
@ -36,7 +35,6 @@ import { PropDataDirective } from '../../directives/prop-data.directive';
],
})
export class ExtensibleFormComponent<R = any> {
public readonly cdRef = inject(ChangeDetectorRef);
public readonly track = inject(TrackByService);
private readonly container = inject(ControlContainer);
private readonly extensions = inject(ExtensionsService);

3
npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table-row-detail/extensible-table-row-detail.component.ts

@ -1,6 +1,7 @@
import { Component, contentChild, input, TemplateRef } from '@angular/core';
import {Component, contentChild, input, TemplateRef, ChangeDetectionStrategy,} from '@angular/core';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'abp-extensible-table-row-detail',
template: '',
})

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

@ -114,8 +114,8 @@
@if (actionsTemplate() || (actionList.length && hasAtLeastOnePermittedAction)) {
<ngx-datatable-column
[name]="actionsText() | abpLocalization"
[maxWidth]="_actionsColumnWidth() ?? undefined"
[width]="_actionsColumnWidth() ?? 200"
[maxWidth]="_actionsColumnWidth()"
[width]="_actionsColumnWidth() || 200"
[canAutoResize]="!_actionsColumnWidth()"
[sortable]="false"
>
@ -134,7 +134,7 @@
@for (prop of propList; track prop.name; let i = $index) {
<ngx-datatable-column
*abpVisible="prop.columnVisible(getInjected)"
[width]="columnWidths()[i] ?? 200"
[width]="columnWidths()[i] || 200"
[canAutoResize]="!columnWidths()[i]"
[name]="(prop.isExtra ? '::' + prop.displayName : prop.displayName) | abpLocalization"
[prop]="prop.name"

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

@ -1,7 +1,6 @@
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
computed,
inject,
@ -94,7 +93,6 @@ const DEFAULT_ACTIONS_COLUMN_WIDTH = 150;
export class ExtensibleTableComponent<R = any> implements AfterViewInit, OnDestroy {
readonly #injector = inject(Injector);
readonly getInjected = this.#injector.get.bind(this.#injector);
protected readonly cdr = inject(ChangeDetectorRef);
protected readonly locale = inject(LOCALE_ID);
protected readonly config = inject(ConfigStateService);
protected readonly timeZoneService = inject(TimezoneService);
@ -368,7 +366,6 @@ export class ExtensibleTableComponent<R = any> implements AfterViewInit, OnDestr
?.requestStatus$?.pipe(filter(status => status === 'loading'))
.subscribe(() => {
this._data.set([]);
this.cdr.markForCheck();
});
}
}

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

@ -1,4 +1,4 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { Component, ViewEncapsulation, ChangeDetectionStrategy } from '@angular/core';
export enum PageParts {
title = 'PageTitleContainerComponent',
@ -7,6 +7,7 @@ export enum PageParts {
}
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'abp-page-title-container',
template: ` <ng-content></ng-content> `,
encapsulation: ViewEncapsulation.None,

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

@ -1,4 +1,12 @@
import { Component, ViewEncapsulation, input, effect, signal, contentChild } from '@angular/core';
import {
Component,
ViewEncapsulation,
input,
effect,
signal,
contentChild,
ChangeDetectionStrategy,
} from '@angular/core';
import {
PageTitleContainerComponent,
PageBreadcrumbContainerComponent,
@ -10,6 +18,7 @@ import { PageToolbarComponent } from '@abp/ng.components/extensible';
import { PagePartDirective } from './page-part.directive';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'abp-page',
templateUrl: './page.component.html',
encapsulation: ViewEncapsulation.None,

41
npm/ng-packs/packages/components/tree/src/lib/components/tree.component.ts

@ -1,6 +1,5 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
contentChild,
inject,
@ -10,7 +9,7 @@ import {
input,
output,
signal,
effect
effect,
} from '@angular/core';
import { NgbDropdown, NgbDropdownMenu, NgbDropdownToggle } from '@ng-bootstrap/ng-bootstrap';
import {
@ -54,7 +53,6 @@ export type DropEvent = NzFormatEmitEvent & { pos: number };
export class TreeComponent implements OnInit {
private lazyLoadService = inject(LazyLoadService);
private subscriptionService = inject(SubscriptionService);
private cdr = inject(ChangeDetectorRef);
private disableTreeStyleLoading = inject(DISABLE_TREE_STYLE_LOADING_TOKEN, { optional: true });
dropPosition!: number;
@ -69,7 +67,7 @@ export class TreeComponent implements OnInit {
readonly selectedNodeChange = output<any>();
readonly dropOver = output<DropEvent>();
readonly nzExpandChange = output<NzFormatEmitEvent>();
// Input signals
readonly noAnimation = input(true);
readonly draggable = input<boolean | undefined>(undefined);
@ -81,15 +79,15 @@ export class TreeComponent implements OnInit {
readonly selectedNodeInput = input<any>(undefined, { alias: 'selectedNode' });
readonly changeCheckboxWithNode = input<boolean | undefined>(undefined);
readonly isNodeSelectedFn = input<(node: any) => boolean>(
(node) => this._selectedNode()?.id === node.key,
{ alias: 'isNodeSelected' }
node => this._selectedNode()?.id === node.key,
{ alias: 'isNodeSelected' },
);
readonly beforeDropFn = input<(event: NzFormatBeforeDropEvent) => any>(
(event: NzFormatBeforeDropEvent) => {
this.dropPosition = event.pos;
return of(false);
},
{ alias: 'beforeDrop' }
{ alias: 'beforeDrop' },
);
// Internal signals for two-way binding
@ -99,12 +97,24 @@ export class TreeComponent implements OnInit {
protected readonly _nodes = signal<any[]>([]);
// Getters for template access
get checkedKeys() { return this._checkedKeys(); }
get expandedKeys() { return this._expandedKeys(); }
get selectedNode() { return this._selectedNode(); }
get nodes() { return this._nodes(); }
get isNodeSelected() { return this.isNodeSelectedFn(); }
get beforeDrop() { return this.beforeDropFn(); }
get checkedKeys() {
return this._checkedKeys();
}
get expandedKeys() {
return this._expandedKeys();
}
get selectedNode() {
return this._selectedNode();
}
get nodes() {
return this._nodes();
}
get isNodeSelected() {
return this.isNodeSelectedFn();
}
get beforeDrop() {
return this.beforeDropFn();
}
constructor() {
// Sync input signals to internal signals
@ -164,12 +174,12 @@ export class TreeComponent implements OnInit {
}
onCheckboxChange(event: NzFormatEmitEvent) {
this._checkedKeys.set([...event.keys as any[]]);
this._checkedKeys.set([...(event.keys as any[])]);
this.checkedKeysChange.emit(event.keys as any[]);
}
onExpandedKeysChange(event: NzFormatEmitEvent) {
this._expandedKeys.set([...event.keys as string[]]);
this._expandedKeys.set([...(event.keys as string[])]);
this.expandedKeysChange.emit(event.keys as string[]);
this.nzExpandChange.emit(event);
}
@ -204,6 +214,5 @@ export class TreeComponent implements OnInit {
setSelectedNode(node: any) {
const newSelectedNode = this.findNode(node, this._nodes());
this._selectedNode.set({ ...newSelectedNode });
this.cdr.markForCheck();
}
}

Loading…
Cancel
Save