libs/acl/form-support/src/lib/binding/auto-form-binding.ts
merge control finding with a given map of controls
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;
};
}