Description
Display an indicator for form fields explaining the status of the inherited-value config of that field
and allowing users to re-sync the inherited value manually.
Example
Methods
|
Async
updateLinkedRecords
|
updateLinkedRecords()
|
|
|
|
|
|
canUpdateLinkedRecords
|
Type : unknown
|
Default value : computed(() => {
this.formValueChange();
const form = this.form();
const field = this.field();
return (
!!form?.entity &&
!!field &&
this.automatedFieldUpdateConfigService.hasLinkedRecordsToUpdate(
form.entity,
field.id,
)
);
})
|
|
|
Whether this field's value is passed on to linked records, which can be updated through this button
|
|
defaultValueHint
|
Type : unknown
|
Default value : computed<DefaultValueHint | undefined>(() => {
this.formValueChange();
const form = this.form();
const field = this.field();
if (!form || !field) {
return undefined;
}
return this.defaultValueService.getDefaultValueUiHint(form, field.id);
})
|
|
|
also updates on its own when asynchronously loaded details used by the hint are ready
|
|
isUpdatingLinkedRecords
|
Type : unknown
|
Default value : signal(false)
|
|
|
Whether the linked records are being loaded or are being reviewed in the dialog
|
import { EntityForm } from "../../../core/common-components/entity-form/entity-form";
import {
Component,
inject,
ChangeDetectionStrategy,
computed,
effect,
input,
signal,
} from "@angular/core";
import { MatIconButton } from "@angular/material/button";
import { FaIconComponent } from "@fortawesome/angular-fontawesome";
import { FormFieldConfig } from "../../../core/common-components/entity-form/FormConfig";
import { TemplateTooltipDirective } from "../../../core/common-components/template-tooltip/template-tooltip.directive";
import {
DefaultValueHint,
DefaultValueService,
} from "../../../core/default-values/default-value-service/default-value.service";
import { EntityFieldLabelComponent } from "../../../core/entity/entity-field-label/entity-field-label.component";
import { Entity } from "../../../core/entity/model/entity";
import { debounceTime } from "rxjs/operators";
import { AlertService } from "../../../core/alerts/alert.service";
import { Logging } from "../../../core/logging/logging.service";
import { AutomatedFieldUpdateConfigService } from "../automated-field-update/automated-field-update-config.service";
import { NoCommonValueHintComponent } from "../no-common-value-hint/no-common-value-hint.component";
/**
* Display an indicator for form fields explaining the status of the inherited-value config of that field
* and allowing users to re-sync the inherited value manually.
*/
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: "app-inherited-value-button",
imports: [
EntityFieldLabelComponent,
FaIconComponent,
NoCommonValueHintComponent,
TemplateTooltipDirective,
MatIconButton,
],
templateUrl: "./inherited-value-button.component.html",
styleUrl: "./inherited-value-button.component.scss",
})
export class InheritedValueButtonComponent {
private readonly defaultValueService = inject(DefaultValueService);
private readonly automatedFieldUpdateConfigService = inject(
AutomatedFieldUpdateConfigService,
);
private readonly alertService = inject(AlertService);
form = input<EntityForm<any>>();
field = input<FormFieldConfig>();
entity = input<Entity>();
/** changes whenever a value in the form changes, because form values are not signals */
private readonly formValueChange = signal(0);
/** also updates on its own when asynchronously loaded details used by the hint are ready */
defaultValueHint = computed<DefaultValueHint | undefined>(() => {
this.formValueChange();
const form = this.form();
const field = this.field();
if (!form || !field) {
return undefined;
}
return this.defaultValueService.getDefaultValueUiHint(form, field.id);
});
/** Whether this field's value is passed on to linked records, which can be updated through this button */
canUpdateLinkedRecords = computed(() => {
this.formValueChange();
const form = this.form();
const field = this.field();
return (
!!form?.entity &&
!!field &&
this.automatedFieldUpdateConfigService.hasLinkedRecordsToUpdate(
form.entity,
field.id,
)
);
});
/** Whether the linked records are being loaded or are being reviewed in the dialog */
isUpdatingLinkedRecords = signal(false);
constructor() {
effect((onCleanup) => {
const form = this.form();
if (!form) {
return;
}
const sub = form.formGroup.valueChanges
.pipe(debounceTime(50))
.subscribe(() => this.formValueChange.update((count) => count + 1));
onCleanup(() => sub.unsubscribe());
});
}
async updateLinkedRecords() {
this.isUpdatingLinkedRecords.set(true);
try {
const wereUpdated =
await this.automatedFieldUpdateConfigService.updateLinkedRecords(
this.form().entity,
this.field().id,
);
if (!wereUpdated) {
this.alertService.addInfo(
$localize`The linked records are up to date.`,
);
}
} catch (error) {
Logging.error(
"InheritedValueButton could not update the linked records",
error,
);
this.alertService.addDanger(
$localize`The linked records could not be updated.`,
);
} finally {
this.isUpdatingLinkedRecords.set(false);
}
}
}
@if (canUpdateLinkedRecords()) {
<div [appTemplateTooltip]="updateLinkedRecordsTooltip" class="inline-box">
<button
mat-icon-button
type="button"
[disabled]="form()?.formGroup?.enabled || isUpdatingLinkedRecords()"
(click)="updateLinkedRecords()"
>
<fa-icon icon="circle-arrow-up"></fa-icon>
</button>
</div>
}
@if (defaultValueHint()) {
<div [appTemplateTooltip]="tooltip" class="inline-box">
<button
mat-icon-button
type="button"
[disabled]="
form()?.formGroup?.disabled ||
defaultValueHint()?.isInSync ||
defaultValueHint()?.isEmpty
"
(click)="defaultValueHint()?.syncFromParentField()"
>
<fa-icon
[icon]="defaultValueHint()?.isInSync ? 'link' : 'link-slash'"
></fa-icon>
</button>
</div>
}
<ng-template #tooltip>
<div class="field-hint">
@if (defaultValueHint()?.isCollectedFromLinkedRecords) {
<div i18n>
Values collected from all records linking to this record in their field
"<app-entity-field-label
[field]="defaultValueHint()?.inheritedFromField"
[entityType]="defaultValueHint()?.inheritedFromType"
></app-entity-field-label
>"
</div>
} @else {
<div i18n>
Inherited value from parent record selected in field
"<app-entity-field-label
[field]="defaultValueHint()?.inheritedFromField"
[entityType]="entity()?.getConstructor()"
></app-entity-field-label
>"
</div>
}
<br />
@if (defaultValueHint()?.isCollectedFromLinkedRecords) {
@if (defaultValueHint()?.isInSync) {
<div i18n>(up-to-date, contains the values of all linked records)</div>
} @else {
<div i18n>
(manually changed): click to replace with the values of all linked
records
@if (!form()?.formGroup?.enabled) {
(requires the form to be in "Edit" mode)
}
</div>
}
} @else if (defaultValueHint()?.isLoading) {
<div i18n>Loading the linked records ...</div>
} @else if (defaultValueHint()?.noCommonValue; as reason) {
<div><app-no-common-value-hint [reason]="reason" /></div>
} @else if (defaultValueHint()?.isEmpty) {
<div i18n>
Select one or more records in the parent field to automatically inherit
their shared value here.
</div>
} @else if (defaultValueHint()?.isInSync) {
<div i18n>(up-to-date, inherited from parent record)</div>
} @else {
<div i18n>
(manually overwritten): click to reset value to inherited from parent
record
@if (!form()?.formGroup?.enabled) {
(requires the form to be in "Edit" mode)
}
</div>
<div i18n></div>
}
</div>
</ng-template>
<ng-template #updateLinkedRecordsTooltip>
<div class="field-hint">
<div i18n>
The value of this field is automatically passed on to linked records.
</div>
<br />
<div i18n>
Click to review and update them now
@if (form()?.formGroup?.enabled) {
(not available while editing)
}
</div>
</div>
</ng-template>
.inline-box {
display: inline-block;
}
.field-hint {
color: white;
font-size: 12px;
border-radius: 4px;
background-color: #3e3e3e;
padding: 1em;
}
Legend
Html element with directive