src/app/child-dev-project/notes/note-details/note-details.component.ts

Description

Component responsible for displaying the Note creation/view window

Extends

AbstractEntityDetailsComponent

Example

Metadata

Relationships

Index

Properties
Methods
Inputs
Outputs

Constructor

constructor()

Inputs

bottomForm
Default value : this.defaultFormConfig.bottomForm
middleForm
Default value : this.defaultFormConfig.middleForm
topForm
Default value : this.defaultFormConfig.topForm
entity
Type : Entity | null
Default value : null
entityType
Type : string
id
Type : string

Outputs

entity
Type : Entity | null

Methods

Protected Async loadEntity
loadEntity(id: string, isCancelled: () => void)
Parameters :
Name Type Optional Default value
id string No
isCancelled function No () => false
Returns : any
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.

Returns : void
Protected subscribeToEntityChanges
subscribeToEntityChanges()
Returns : void

Properties

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>
}

./note-details.component.scss

@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
Component
Html element with directive

results matching ""

    No results matching ""