Description
Component responsible for displaying the Note creation/view window
Extends
Example
|
bottomForm
|
Default value : this.defaultFormConfig.bottomForm
|
|
|
|
middleForm
|
Default value : this.defaultFormConfig.middleForm
|
|
|
|
topForm
|
Default value : this.defaultFormConfig.topForm
|
|
|
Methods
|
Protected
Async
loadEntity
|
loadEntity(id: string, isCancelled: () => void)
|
|
|
|
|
Parameters :
| Name |
Type |
Optional |
Default value |
| id |
string
|
No
|
|
| isCancelled |
function
|
No
|
() => false
|
|
|
Protected
onEntityUpdated
|
onEntityUpdated()
|
|
|
|
|
Hook called whenever the entity is updated via the live subscription (e.g. after save or anonymize).
Subclasses can override this to react to entity changes beyond what markForCheck() provides.
|
|
Protected
subscribeToEntityChanges
|
subscribeToEntityChanges()
|
|
|
|
|
|
|
|
Readonly
bottomFieldGroups
|
Type : unknown
|
Default value : computed(() => [{ fields: this.bottomForm() }])
|
|
|
|
Readonly
entityConstructor
|
Type : unknown
|
Default value : computed<EntityConstructor>(() => Note)
|
|
|
|
|
|
Readonly
form
|
Type : unknown
|
Default value : signal<EntityForm<Note> | undefined>(undefined)
|
|
|
|
Readonly
middleFieldGroups
|
Type : unknown
|
Default value : computed(() => [{ fields: this.middleForm() }])
|
|
|
|
Readonly
tmpEntity
|
Type : unknown
|
Default value : signal<Note | undefined>(undefined)
|
|
|
|
Readonly
topFieldGroups
|
Type : unknown
|
Default value : computed(() =>
this.topForm().map((f) => ({ fields: [f] })),
)
|
|
|
|
Protected
Readonly
ability
|
Type : unknown
|
Default value : inject(EntityAbility)
|
|
|
|
|
|
Protected
Readonly
entities
|
Type : unknown
|
Default value : inject(EntityRegistry)
|
|
|
|
|
|
Protected
Readonly
entityMapperService
|
Type : unknown
|
Default value : inject(EntityMapperService)
|
|
|
|
|
|
Readonly
isLoading
|
Type : unknown
|
Default value : signal(false)
|
|
|
|
|
|
Protected
Readonly
router
|
Type : unknown
|
Default value : inject(Router)
|
|
|
|
|
|
Protected
Readonly
unsavedChanges
|
Type : unknown
|
Default value : inject(UnsavedChangesService)
|
|
|
|
|
import { EntityForm } from "#src/app/core/common-components/entity-form/entity-form";
import {
ChangeDetectionStrategy,
Component,
computed,
DestroyRef,
effect,
inject,
input,
signal,
ViewEncapsulation,
} from "@angular/core";
import { MatProgressBar } from "@angular/material/progress-bar";
import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy";
import { CustomDatePipe } from "../../../core/basic-datatypes/date/custom-date.pipe";
import { EntityFormService } from "../../../core/common-components/entity-form/entity-form.service";
import { EntityFormComponent } from "../../../core/common-components/entity-form/entity-form/entity-form.component";
import { ViewActionsComponent } from "../../../core/common-components/view-actions/view-actions.component";
import { ViewTitleComponent } from "../../../core/common-components/view-title/view-title.component";
import { DynamicComponent } from "../../../core/config/dynamic-components/dynamic-component.decorator";
import { AbstractEntityDetailsComponent } from "../../../core/entity-details/abstract-entity-details/abstract-entity-details.component";
import { EntityArchivedInfoComponent } from "../../../core/entity-details/entity-archived-info/entity-archived-info.component";
import { UnsavedChangesService } from "../../../core/entity-details/form/unsaved-changes.service";
import { EntityConstructor } from "../../../core/entity/model/entity";
import { DialogButtonsComponent } from "../../../core/form-dialog/dialog-buttons/dialog-buttons.component";
import { getDefaultNoteDetailsConfig } from "../add-default-note-views";
import { Note } from "../model/note";
/**
* Component responsible for displaying the Note creation/view window
*/
@UntilDestroy()
@DynamicComponent("NoteDetails")
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: "app-note-details",
templateUrl: "./note-details.component.html",
styleUrls: ["./note-details.component.scss"],
imports: [
CustomDatePipe,
EntityFormComponent,
DialogButtonsComponent,
EntityArchivedInfoComponent,
ViewTitleComponent,
MatProgressBar,
ViewActionsComponent,
],
encapsulation: ViewEncapsulation.None,
})
export class NoteDetailsComponent extends AbstractEntityDetailsComponent {
private entityFormService = inject(EntityFormService);
private unsavedChangesService = inject(UnsavedChangesService);
private readonly destroyRef = inject(DestroyRef);
override readonly entityConstructor = computed<EntityConstructor>(() => Note);
private readonly defaultFormConfig = getDefaultNoteDetailsConfig();
topForm = input(this.defaultFormConfig.topForm);
middleForm = input(this.defaultFormConfig.middleForm);
bottomForm = input(this.defaultFormConfig.bottomForm);
readonly topFieldGroups = computed(() =>
this.topForm().map((f) => ({ fields: [f] })),
);
readonly middleFieldGroups = computed(() => [{ fields: this.middleForm() }]);
readonly bottomFieldGroups = computed(() => [{ fields: this.bottomForm() }]);
readonly form = signal<EntityForm<Note> | undefined>(undefined);
readonly tmpEntity = signal<Note | undefined>(undefined);
constructor() {
super();
effect((onCleanup) => {
const entity = this.entity();
this.topForm();
this.middleForm();
this.bottomForm();
let cancelled = false;
onCleanup(() => {
cancelled = true;
});
void this.initForm(entity as Note, () => cancelled);
});
// Subscribe to form value changes; onCleanup tears down stale subscription on re-init.
effect((onCleanup) => {
const currentForm = this.form();
const entity = this.entity() as Note;
if (!currentForm || !entity) return;
const sub = currentForm.formGroup.valueChanges
.pipe(untilDestroyed(this))
.subscribe((value) => {
this.tmpEntity.set(Object.assign(entity.copy(), value));
});
onCleanup(() => sub.unsubscribe());
});
}
private async initForm(
entity: Note,
isCancelled: () => boolean = () => false,
) {
if (!entity) return;
const newForm = await this.entityFormService.createEntityForm(
this.middleForm().concat(this.topForm(), this.bottomForm()),
entity,
this.destroyRef,
);
if (isCancelled()) return;
// Clear the previous form's unsaved-changes registration before replacing it,
// to prevent stale dirty state from accumulating when the form is recreated.
const previousForm = this.form();
if (previousForm) {
this.unsavedChangesService.setUnsavedChanges(previousForm, false);
}
this.tmpEntity.set(entity.copy());
this.form.set(newForm); // triggers the valueChanges effect
}
}
@if (isLoading() || !tmpEntity()) {
<div>
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
} @else {
<app-view-title>
{{ tmpEntity()!.date | customDate: "shortDate" }}:
{{ tmpEntity()!.subject }}
</app-view-title>
<div class="flex-column gap-regular">
<app-entity-archived-info [entity]="entity()"></app-entity-archived-info>
<app-entity-form
[fieldGroups]="topFieldGroups()"
[entity]="entity()"
[form]="$any(form())"
>
</app-entity-form>
<!-- Primary information of Note -->
<app-entity-form
[fieldGroups]="middleFieldGroups()"
[entity]="entity()"
[form]="$any(form())"
[fullWidth]="true"
></app-entity-form>
<app-entity-form
[fieldGroups]="bottomFieldGroups()"
[entity]="entity()"
[form]="$any(form())"
style="margin-top: 10px"
></app-entity-form>
</div>
<app-view-actions>
<app-dialog-buttons
[form]="$any(form())"
[entity]="entity()"
></app-dialog-buttons>
</app-view-actions>
}
@use "variables/sizes";
@use "mixins/grid-layout";
.input-medium {
width: 100%;
max-width: 500px;
}
.content-header {
@include grid-layout.adaptive(450px);
}
.additional-actions-menu {
position: absolute;
right: sizes.$small;
top: sizes.$regular;
}
.form-section {
margin-top: 24px;
}
Legend
Html element with directive