src/app/core/default-values/admin-default-value/admin-default-value.component.ts
Admin UI component used in AdminEntityFieldComponent dialog to let users configure different defaultValue modes for an Entity field.
CustomFormControlDirective<DefaultValueConfig>
| changeDetection | ChangeDetectionStrategy.OnPush |
| providers |
AdminDefaultValueComponent
|
| selector | app-admin-default-value |
| standalone | true |
| imports |
MatFormField
MatLabel
MatError
MatButtonToggleGroup
MatButtonToggle
MatTooltip
MatSuffix
MatIconButton
|
| styleUrls | ./admin-default-value.component.scss |
| templateUrl | ./admin-default-value.component.html |
| styleUrl | ./admin-default-value.component.scss |
MatFormField
MatLabel
MatError
ReactiveFormsModule
FormsModule
MatButtonToggleGroup
MatButtonToggle
MatTooltip
FaDynamicIconComponent
FaIconComponent
MatSuffix
MatIconButton
AdminDefaultValueDynamicComponent
AdminDefaultValueStaticComponent
AdminInheritedFieldComponent
CustomFormControlDirective<DefaultValueConfig>
Properties |
|
Methods |
Inputs |
Outputs |
constructor()
|
| entitySchemaField | |
Type : EntitySchemaField
|
|
| Required : true | |
| entityType | |
Type : EntityConstructor
|
|
| Required : true | |
| aria-describedby | |
Type : string
|
|
|
Inherited from
CustomFormControlDirective
|
|
| disabled | |
Type : boolean
|
|
|
Inherited from
CustomFormControlDirective
|
|
| ngControl | |
Type : any
|
|
Default value : inject(NgControl, { optional: true, self: true })
|
|
|
Inherited from
CustomFormControlDirective
|
|
| placeholder | |
Type : string
|
|
|
Inherited from
CustomFormControlDirective
|
|
| required | |
Type : boolean
|
|
|
Inherited from
CustomFormControlDirective
|
|
| value | |
Type : T
|
|
|
Inherited from
CustomFormControlDirective
|
|
| valueChange | |
Type : EventEmitter
|
|
|
Inherited from
CustomFormControlDirective
|
|
| clearDefaultValue |
clearDefaultValue()
|
|
Returns :
void
|
| writeValue | ||||||||||||
writeValue(value: DefaultValueConfig, notifyFormControl: unknown)
|
||||||||||||
|
Inherited from
CustomFormControlDirective
|
||||||||||||
|
Parameters :
Returns :
void
|
| blur |
blur()
|
|
Inherited from
CustomFormControlDirective
|
|
Returns :
void
|
| focus |
focus()
|
|
Inherited from
CustomFormControlDirective
|
|
Returns :
void
|
| onContainerClick | ||||||
onContainerClick(event: MouseEvent)
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| registerOnChange | ||||||
registerOnChange(fn: any)
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| registerOnTouched | ||||||
registerOnTouched(fn: any)
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| setDescribedByIds | ||||||
setDescribedByIds(ids: string[])
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| setDisabledState | ||||||
setDisabledState(isDisabled: boolean)
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| form |
Type : unknown
|
Default value : new FormGroup({
mode: this.modeControl,
config: this.configControl,
})
|
| modes |
Type : unknown
|
Default value : computed(() => this.modesResource.value() ?? [])
|
| selectedMode |
Type : unknown
|
Default value : signal<DefaultValueMode | null>(null)
|
| controlType |
Type : string
|
Default value : "custom-control"
|
|
Inherited from
CustomFormControlDirective
|
| elementRef |
Type : unknown
|
Default value : inject<ElementRef<HTMLElement>>(ElementRef)
|
|
Inherited from
CustomFormControlDirective
|
| Readonly enabled |
Type : Signal<boolean>
|
Default value : computed(() => !this._disabled())
|
|
Inherited from
CustomFormControlDirective
|
|
Whether the control is currently enabled, as a signal (tracks disabled). |
| errorStateMatcher |
Type : unknown
|
Default value : inject(ErrorStateMatcher)
|
|
Inherited from
CustomFormControlDirective
|
| id |
Type : unknown
|
Default value : `custom-form-control-${CustomFormControlDirective.nextId++}`
|
|
Inherited from
CustomFormControlDirective
|
| Static nextId |
Type : number
|
Default value : 0
|
|
Inherited from
CustomFormControlDirective
|
| onChange |
Type : unknown
|
Default value : () => {...}
|
|
Inherited from
CustomFormControlDirective
|
| onTouched |
Type : unknown
|
Default value : () => {...}
|
|
Inherited from
CustomFormControlDirective
|
| parentForm |
Type : unknown
|
Default value : inject(NgForm, { optional: true })
|
|
Inherited from
CustomFormControlDirective
|
| parentFormGroup |
Type : unknown
|
Default value : inject(FormGroupDirective, { optional: true })
|
|
Inherited from
CustomFormControlDirective
|
| stateChanges |
Type : unknown
|
Default value : new Subject<void>()
|
|
Inherited from
CustomFormControlDirective
|
| Readonly valueSignal |
Type : Signal<T>
|
Default value : computed(() => this._value())
|
|
Inherited from
CustomFormControlDirective
|
|
The current value of the control as a signal.
Authoritative in both modes: it reflects the bound |
import {
computed,
Component,
inject,
ChangeDetectionStrategy,
input,
resource,
signal,
} from "@angular/core";
import { DefaultValueConfig, DefaultValueMode } from "../default-value-config";
import {
MatError,
MatFormField,
MatFormFieldControl,
MatLabel,
MatSuffix,
} from "@angular/material/form-field";
import {
FormControl,
FormGroup,
FormsModule,
ReactiveFormsModule,
ValidatorFn,
} from "@angular/forms";
import {
MatButtonToggle,
MatButtonToggleGroup,
} from "@angular/material/button-toggle";
import {
AdminDefaultValueContext,
DefaultValueStrategy,
} from "../default-value-strategy.interface";
import { MatTooltip } from "@angular/material/tooltip";
import { FaIconComponent } from "@fortawesome/angular-fontawesome";
import { FaDynamicIconComponent } from "../../common-components/fa-dynamic-icon/fa-dynamic-icon.component";
import { MatIconButton } from "@angular/material/button";
import { EntityConstructor } from "../../entity/model/entity";
import { CustomFormControlDirective } from "../../common-components/basic-autocomplete/custom-form-control.directive";
import { AdminDefaultValueDynamicComponent } from "../x-dynamic-placeholder/admin-default-value-dynamic/admin-default-value-dynamic.component";
import { AdminDefaultValueStaticComponent } from "../x-static/admin-default-value-static/admin-default-value-static.component";
import { EntitySchemaField } from "../../entity/schema/entity-schema-field";
import { AdminInheritedFieldComponent } from "../../../features/inherited-field/admin-inherited-field/admin-inherited-field.component";
/**
* Admin UI component used in AdminEntityFieldComponent dialog
* to let users configure different defaultValue modes for an Entity field.
*/
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: "app-admin-default-value",
imports: [
MatFormField,
MatLabel,
MatError,
ReactiveFormsModule,
FormsModule,
MatButtonToggleGroup,
MatButtonToggle,
MatTooltip,
FaDynamicIconComponent,
FaIconComponent,
MatSuffix,
MatIconButton,
AdminDefaultValueDynamicComponent,
AdminDefaultValueStaticComponent,
AdminInheritedFieldComponent,
],
templateUrl: "./admin-default-value.component.html",
styleUrl: "./admin-default-value.component.scss",
providers: [
{ provide: MatFormFieldControl, useExisting: AdminDefaultValueComponent },
],
})
export class AdminDefaultValueComponent extends CustomFormControlDirective<DefaultValueConfig> {
entityType = input.required<EntityConstructor>();
entitySchemaField = input.required<EntitySchemaField>();
private readonly modeControl = new FormControl<DefaultValueMode | null>(null);
private readonly configControl = new FormControl<
DefaultValueConfig["config"] | null
>(null, {
validators: [this.requiredIfModeSelected()],
});
form = new FormGroup({
mode: this.modeControl,
config: this.configControl,
});
private defaultValueStrategies = inject(
DefaultValueStrategy,
) as unknown as DefaultValueStrategy[];
private readonly modesResource = resource<
AdminDefaultValueContext[],
unknown
>({
loader: async () =>
Promise.all(
this.defaultValueStrategies.map((strategy) => strategy.getAdminUI()),
),
});
modes = computed(() => this.modesResource.value() ?? []);
selectedMode = signal<DefaultValueMode | null>(null);
constructor() {
super();
this.syncFormWithValue(this.value);
this.form.get("mode")?.valueChanges.subscribe((mode) => {
this.selectedMode.set(mode);
this.form.get("config")?.setValue(null);
});
this.form.get("config")?.valueChanges.subscribe((value) => {
if (!this.form.get("mode")?.getRawValue() && !!value) {
// set default mode as "static" after user started typing a value
this.form.get("mode")?.setValue("static", { emitEvent: false });
}
});
this.form.valueChanges.subscribe(() => this.updateValue());
}
override writeValue(value: DefaultValueConfig, notifyFormControl = false) {
super.writeValue(value, notifyFormControl);
this.syncFormWithValue(value);
}
private syncFormWithValue(value: DefaultValueConfig | null | undefined) {
const newFormValue = {
mode: value?.mode ?? null,
config: value?.config ?? null,
};
if (
JSON.stringify(this.form.getRawValue()) !== JSON.stringify(newFormValue)
) {
this.form.setValue(newFormValue, { emitEvent: false });
this.form.updateValueAndValidity({ emitEvent: false });
this.selectedMode.set(newFormValue.mode);
}
}
private updateValue() {
this.form.markAllAsTouched();
if (this.form.invalid) {
// TODO: make sure the admin components of each mode are correctly setting themselves as invalid
return;
}
let newConfigValue: DefaultValueConfig = this.form.getRawValue();
// output as `null` if no value is set to conform with standard form controls
if (!newConfigValue || (!newConfigValue.mode && !newConfigValue.config)) {
newConfigValue = null;
}
if (JSON.stringify(newConfigValue) !== JSON.stringify(this.value)) {
this.value = newConfigValue;
}
}
private requiredIfModeSelected(): ValidatorFn {
return (control) => {
if (this.modeControl.value && !control.value) {
return { requiredForMode: true };
}
return null;
};
}
clearDefaultValue() {
// TODO: this causes the config to keep a defaultValue with `config: { value: null }` instead of returning just `null` and deleting the property
// Need to fix so that the parent form control gets reset to null (without causing errors in the component here internally)
this.form.setValue({ mode: null, config: null });
}
}
<mat-form-field [formGroup]="form" floatLabel="always">
<mat-label i18n>Default Value</mat-label>
<div class="flex-row gap-regular align-center">
<div class="flex-grow">
@switch (selectedMode()) {
@default {
<app-admin-default-value-static
formControlName="config"
[entitySchemaField]="entitySchemaField()"
></app-admin-default-value-static>
}
@case ("dynamic") {
<app-admin-default-value-dynamic
formControlName="config"
></app-admin-default-value-dynamic>
}
@case ("inherited-field") {
<app-admin-inherited-field
formControlName="config"
[entityType]="entityType()"
[entitySchemaField]="entitySchemaField()"
></app-admin-inherited-field>
}
}
</div>
<mat-button-toggle-group
formControlName="mode"
i18n-aria-label
aria-label="default value mode"
hideSingleSelectionIndicator
style="flex: none"
>
@for (strategy of modes(); track strategy.mode) {
<mat-button-toggle
[value]="strategy.mode"
[matTooltip]="strategy.description"
>
<app-fa-dynamic-icon [icon]="strategy.icon"></app-fa-dynamic-icon>
</mat-button-toggle>
}
</mat-button-toggle-group>
<button
mat-icon-button
matIconSuffix
matTooltip="Remove default value"
i18n-matTooltip
(click)="clearDefaultValue()"
color="{{ form.invalid ? 'accent' : '' }}"
>
<fa-icon icon="times"></fa-icon>
</button>
</div>
@if (form.invalid) {
<mat-error i18n="error message"
>Select and fully configure default value or clear the mode
</mat-error>
}
</mat-form-field>
./admin-default-value.component.scss
:host {
min-width: 450px;
}
mat-form-field {
width: 100%;
}
mat-option {
font-style: italic;
}