File

libs/acl/form-support/src/lib/binding/auto-form-binding.ts

Description

merge control finding with a given map of controls

Index

Properties

Properties

excludes (Optional)
Type string[]
overrides (Optional)
Type AclMapItem[]
import { AclEvaluationInterface } from '@allianz/taly-acl';
import { AbstractControl, FormGroup } from '@angular/forms';
import { BehaviorSubject, mergeAll, Observable, Subject } from 'rxjs';
import { finalize, takeUntil } from 'rxjs/operators';
import { AclMapItem } from '../../types';
import { clearAclReadOnlyValues } from '../data/clear-acl-read-only';
import { getAclPatchObject } from '../data/get-acl-path-object';
import { AclControlItem } from '../support/acl-control-item';
import { updateAclOnStructureChange } from '../support/update-acl-on-structure-change';

/**
 * merge control finding with a given map of controls
 */
export interface AutoFormBindingFactoryParams {
  overrides?: AclMapItem[];
  excludes?: string[];
}

export interface FormBindingReturnValue {
  stream$: Observable<boolean>;
  clearAclReadOnlyValues: <T>(state: T) => T;
  getPatchObject: <T>(state: T) => unknown;
}

export function autoFormBindingFactory(
  autoFormBindingFactoryParams: AutoFormBindingFactoryParams = {}
) {
  return (acl: AclEvaluationInterface, form: FormGroup): FormBindingReturnValue => {
    if (!form) {
      throw new Error(
        'Trying to ACL-bind a malformed form. The given form is probably null or undefined.'
      );
    }
    // This is triggered via the outside BB/component, when the ACL handling should disconnect.
    // Usually that happens on destroy or when a form structure is changed.
    const tearDown$ = new Subject<void>();

    // This stream is used to collect and subscribe to the ACL observables.
    const newStream$ = new Subject<Observable<boolean>>();
    newStream$
      .pipe(
        // mergeAll() automatically subscribes to all the ACL observables for the formControls.
        // When a formControl is removed from the formGroup, the ACL handling is automatically
        // disconnected/cleaned up within updateAclOnStructureChange()
        // and mergeAll() is also not subscribed anymore.
        mergeAll(),
        takeUntil(tearDown$)
      )
      .subscribe();

    const latestControlList$ = new BehaviorSubject<AclControlItem[]>([]);
    const aclHandledControls = new Map<AbstractControl, Subject<void>>();

    updateAclOnStructureChange(
      acl,
      newStream$,
      form,
      latestControlList$,
      autoFormBindingFactoryParams,
      aclHandledControls
    );

    form.valueChanges.pipe(takeUntil(tearDown$)).subscribe(() => {
      updateAclOnStructureChange(
        acl,
        newStream$,
        form,
        latestControlList$,
        autoFormBindingFactoryParams,
        aclHandledControls
      );
    });

    const clearFn = <T extends Record<string, unknown>>(state: T) => {
      const currentControlList = latestControlList$.value;
      return clearAclReadOnlyValues(currentControlList, acl)(state);
    };

    const patchFn = <T extends Record<string, unknown>>(state: T) => {
      const currentControlList = latestControlList$.value;
      return getAclPatchObject(currentControlList, form, acl)(state);
    };

    return {
      stream$: new Observable<boolean>().pipe(
        finalize(() => {
          tearDown$.next();
        })
      ),
      clearAclReadOnlyValues: clearFn,
      getPatchObject: patchFn
    } as FormBindingReturnValue;
  };
}

results matching ""

    No results matching ""