UNPKG

di-controls

Version:
945 lines (937 loc) 33.5 kB
import * as i0 from '@angular/core'; import { signal, inject, model, ElementRef, Renderer2, ChangeDetectorRef, Directive, DestroyRef, computed } from '@angular/core'; import { NgControl } from '@angular/forms'; import { EMPTY_FUNCTION, DI_DEFAULT_COMPARE } from 'di-controls/constants'; import { hasValue, resolveValue } from 'di-controls/helpers'; import { toObservable } from '@angular/core/rxjs-interop'; import { SetCompare } from 'di-controls/classes'; /** * Base implementation of ControlValueAccessor */ class DIControlValueAccessor { config; model = signal(null); ngControl = inject(NgControl, { optional: true, self: true }); changeDetectorRef; disabled = model(false); touch = EMPTY_FUNCTION; change = EMPTY_FUNCTION; #element = inject(ElementRef).nativeElement; #renderer = inject(Renderer2); constructor(config) { this.config = config; this.changeDetectorRef = inject(ChangeDetectorRef); if (this.ngControl) { this.ngControl.valueAccessor = this; } if (this.config?.withNativeElementSupport) { toObservable(this.disabled) .subscribe((isDisabled) => this.addDisabledAttribute(isDisabled)); } } /** * Returns true if the control is not empty. */ get hasValue() { return hasValue(this.model()); } /** * Method is called by the forms API. * * @param fn - callback function to register on value change * @internal */ registerOnChange(fn) { this.change = fn; } /** * Method is called by the forms API. * * @param fn - callback function to register on touch * @internal */ registerOnTouched(fn) { this.touch = fn; } /** * Method is called by the forms API to write to the view when programmatic changes from model to view are requested. * * @param obj - new value * @internal */ writeValue(obj) { if (this.model() !== obj) { this.update(obj); } } /** * Updates the model. * * @param value - new value */ internalUpdateModel(value) { this.model.set(value); this.change(this.model()); this.changeDetectorRef.markForCheck(); } /** * Method is called by the forms API to write to the view when programmatic changes from model to view are requested. * * @param isDisabled - new value * @internal */ setDisabledState(isDisabled) { this.disabled.set(isDisabled); this.addDisabledAttribute(isDisabled); } update(value) { this.model.set(value); this.config?.onIncomingUpdate && this.config.onIncomingUpdate(value); this.changeDetectorRef.markForCheck(); } addDisabledAttribute(isDisabled) { if (this.config?.withNativeElementSupport) { isDisabled ? this.#renderer.setAttribute(this.#element, 'disabled', 'true') : this.#renderer.removeAttribute(this.#element, 'disabled'); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DIControlValueAccessor, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.0.5", type: DIControlValueAccessor, isStandalone: true, inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange" }, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DIControlValueAccessor, decorators: [{ type: Directive }], ctorParameters: () => [{ type: undefined }] }); /** * `DIControl` can be used to implement any control that you want. It can work with any model type. * All updates from children will be accepted as is. And updates from outside (`FormControl`, `NgModel`, another Control) * will be accepted as is too. * * ## Creating a control * To create a control you need to extend your `@Component` or `@Directive` from `DIControl` class. * After that your control will be able to work with `NgModel`, `FormControl`. * * ```ts fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent extends DIControl<string> { * constructor() { * super(); * } * } * ``` * * ## Registering as a host * By default your control can work only with `NgModel` and `FormControl`. But you can register your control as a host * for another controls, then your control will be able to update them and accept updates from them. To do that you need to * use `provideHostControl` function. * * ```ts {2} fileName="custom-control.component.ts" * @Component({ * providers: [provideHostControl(CustomControlComponent)], * }) * export class CustomControlComponent extends DIControl<string> { * constructor() { * super(); * } * } * ``` * * ## Injecting host control * By default your control doesn't communicate with host controls. But you can inject host control and put it * into `super` call. This will register your control in the host control and start communication between them. * * > **Note** * > If you register your control as a host for another controls, then you can inject it * > only with `skipSelf` option. * * ```ts {5} fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent extends DIControl<string> { * constructor() { * // we add `optional` option to make it possible to use this control without host * super({host: injectHostControl({optional: true})}); * } * } * ``` * * ## Getting model * To get model you need to use `model` property. It will return model for the current control. * * ```ts {9} fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent extends DIControl<string> { * constructor() { * super(); * } * * @HostListener('click') * onClick() { * console.log(this.model()); * } * } * ``` * * ## Updating model * To update model you need to call `updateModel` method. It will update model for the current control and all * children controls, as well as for the `NgModel` or `FormControl`. * * ```ts {9} fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent extends DIControl<string> { * constructor() { * super(); * } * * @HostListener('click') * onClick() { * this.updateModel('new value'); * } * } * ``` * ## Catching updates * Sometimes you may need to catch updates from different sources. For example, to update the value of the native * input element. To do this, you can provide the `onIncomingUpdate` hook. * * ```ts {6} fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent extends DIControl<string> { * constructor() { * super({ * onIncomingUpdate: (value: string | null) => { * this.elementRef.nativeElement.value = value; * }, * }); * } * } * ``` */ class DIControl extends DIControlValueAccessor { config; /** * List of children controls. * * @protected * @internal */ children = new Set(); /** * Control from which we have to update our model. * * @protected * @internal */ updateFrom = null; /** * Request host for update the current control. * Host will update the current control based on its current state and host control logic. * * @protected * @internal */ requestForUpdate = EMPTY_FUNCTION; /** * Function that should be used to make control touched. */ touch = () => this.config?.host?.touch(); onControlChangeFn = EMPTY_FUNCTION; destroyRef = inject(DestroyRef); constructor(config) { super(config); this.config = config; this.destroyRef.onDestroy(() => this.config?.host?.unregisterControl(this)); } ngOnInit() { /* * We have to register control with Promise.resolve because NgModel uses it too to set first * value (https://github.com/angular/angular/blob/7df9127088bda3c9d29937a04287b87dc2045ea7/packages/forms/src/directives/ng_model.ts#L314) */ Promise.resolve().then(() => this.config?.host?.registerControl(this)); } /** * Registers provided control as a child of the current control. * * @param control - control that will be registered. * @internal */ registerControl(control) { this.children.add(control); /* * We have to update control because its can be created dynamically. * We use Promise.resolve because NgModel uses it too to set first value (https://github.com/angular/angular/blob/7df9127088bda3c9d29937a04287b87dc2045ea7/packages/forms/src/directives/ng_model.ts#L314) * so there's no need to use angular life cycle hooks */ Promise.resolve().then(() => { this.updateControl(control, this.model()); }); control.registerOnControlChange((value) => { this.childControlChange(control, value); this.config?.onChildControlChange?.(control, value); }); control.registerRequestForUpdate(() => { this.updateControl(control, this.model()); }); } /** * Unregisters provided control from the current control. * * @param control - control that will be unregistered. * @internal */ unregisterControl(control) { this.children.delete(control); } registerOnTouched(fn) { this.touch = () => { fn(); // Touch host control to update its state this.config?.host?.touch(); }; } /** * Registers provided function as a callback that will be called when the current control changes. * This function will be provided by the host control to update its model. * * @param fn - callback function. * @internal */ registerOnControlChange(fn) { this.onControlChangeFn = fn; } /** * Registers provided function as a callback that can be called to request an update from the host control. * After calling this function the host control will update the model of the current control based on the current * state of the control and host control logic. * * @param fn - callback function. * @internal */ registerRequestForUpdate(fn) { this.requestForUpdate = fn; } /** * Updates the model of the current control. * This is the main method that should be used to update the model. * * @param value - new value. */ updateModel(value) { this.updateFrom = null; this.internalUpdateModel(value); } /** * Updates the model of the current control. * Don't use this method directly, use `updateModel` instead. * * @param value - new value. * @internal */ internalUpdateModel(value) { super.internalUpdateModel(value); this.onControlChangeFn(value); this.updateControls(this.model()); } writeValue(value) { if (this.model() !== value) { super.writeValue(value); this.updateControls(value); this.onControlChangeFn(value); } } /** * Method is called by the host to update the value of the control. * * @param value - new value * @internal */ writeValueFromHost(value) { if (this.model() !== value) { super.writeValue(value); this.change(value); this.updateControls(value); } } /** * Updates all child controls with the provided value. * * @param value - new value. * @protected * @internal */ updateControls(value) { this.children.forEach((control) => { if (control !== this.updateFrom) { this.updateControl(control, value); } }); this.updateFrom = null; } /** * Updates provided control with the provided value. * * @param control - control that will be updated. * @param value - new value. * @protected * @internal */ updateControl(control, value) { control.writeValueFromHost(value); } /** * Function catches updates from child controls and updates the current control model. * * @param control - control that was updated. * @param value - new value. * @protected * @internal */ childControlChange(control, value) { if (this.model() !== value) { this.updateFrom = control; this.internalUpdateModel(value); this.config?.onIncomingUpdate && this.config.onIncomingUpdate(value); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DIControl, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.5", type: DIControl, isStandalone: true, usesInheritance: true, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DIControl, decorators: [{ type: Directive }], ctorParameters: () => [{ type: undefined }] }); /** * `DIStateControl` can be used to implement state controls (checkbox, radio, chip, switch, etc.). * It extends `DIControl` and adds `checked` signal that can be used to get checked state. * By default it works with `boolean` type, it adds `value` input that can be used to set custom * "true" value. * * ## Creating a control * To create a control you need to extend your `@Component` or `@Directive` from `DIStateControl` class. * After that your control will be able to work with `NgModel`, `FormControl`. * * ```ts fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent<T = boolean> extends DIStateControl<T> { * @Input({required: true}) * value!: T; * * constructor() { * super(); * } * } * ``` * * ## Injecting host control * By default your control doesn't communicate with host controls. But you can inject host control and put it * into `super` call. This will register your control in the host control and start communication between them. * * > **Note** * > If you register your control as a host for another controls, then you can inject it * > only with `skipSelf` option. * * ```ts {5} fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent<T = boolean> extends DIStateControl<T> { * constructor() { * // we add `optional` option to make it possible to use this control without host * super({host: injectHostControl({optional: true})}); * } * } * ``` * * ## Getting checked state * To get checked state you need to use `checked` signal. It will return `true` if the current control is checked, * `false` if it is unchecked and `null` if it is in intermediate state. * * ```ts {9} fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent<T> extends DIControl<T> { * @Input({required: true}) * value!: T; * * constructor() { * super(); * * console.log(this.checked()); * } * } * ``` * * ## Getting model * To get model you need to use `model` property. It will return model for the current control. * * ```ts {9} fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent<T = boolean> extends DIStateControl<T> { * constructor() { * super(); * } * * @HostListener('click') * onClick() { * console.log(this.checked()); * } * } * ``` * * ## Updating model * To update model you should use `check`, `uncheck`, `intermediate` or `toggle` methods. * They will update model based on the current state and configuration. * * ```ts {9} fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent<T = boolean> extends DIStateControl<T> { * constructor() { * super(); * } * * @HostListener('click') * onClick() { * this.toggle(); * } * } * ``` * * ## Catching updates * Sometimes you may need to catch updates from different sources. For example, to update the value of the native * input element. To do this, you can provide the `onIncomingUpdate` hook. * * ```ts {6} fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent<T = boolean> extends DIStateControl<T> { * constructor() { * super({ * onIncomingUpdate: (value: string | null) => { * this.elementRef.nativeElement.value = value; * }, * }); * } * } * ``` * * * ## Using with DICollectionControl * Using `DIStateControl` together with `DICollectionControl` will result in * `DICollectionControl` containing a list of values from `DIStateControl` that have * a checked state. If your `DIStateControl` has objects as values, you may * likely need a comparison function because they can sometimes be immutable. * * To achieve this, provide your `DICollectionControl` as a `DICompareHost` and * inject it into your `DIStateControl` to give `DIStateControl` access to * the `compareFn` function. * * > **Warning** * > `DICollectionControl` requires an explicit specification of the `uncheckedValue` in the `DIStateControl` * * ```ts {3-4} fileName="checkbox-group.component.ts" * @Component({ * providers: [ * provideHostControl(CheckboxGroupComponent), * provideCompareHost(CheckboxGroupComponent), * ], * }) * export class CheckboxGroupComponent<T> extends DICollectionControl<T> { * constructor() { * super(); * } * } * ``` * * ```ts {5-7} fileName="checkbox.component.ts" * @Component() * export class CheckboxComponent<T> extends DIStateControl<T> { * constructor() { * super({ * host: injectHostControl({ optional: true }), * compare: inject(DICompareHost, { optional: true }), * hasIntermediate: true, * }); * * @HostListener('click') * onClick() { * this.toggle(); * } * } * ``` */ class DIStateControl extends DIControl { config; checked = computed(() => { const compareFn = typeof this.config?.compare === 'function' ? this.config.compare : resolveValue(this.config?.compare?.compareFn ?? DI_DEFAULT_COMPARE); return compareFn(this.model(), resolveValue(this.value)) ? true : this.isIntermediate ? null : false; }); constructor(config) { super(config); this.config = config; } ngOnChanges({ value }) { /* * We have to request host for updates, because when we use ngFor directive * with trackBy function, Angular doesn't re-create components, it just changes their inputs, * so we have to request for updates our host, to determine right checked state */ if (value) { this.requestForUpdate(); } } /** Sets checked state */ check() { this.updateModel(resolveValue(this.value)); } /** Sets unchecked state */ uncheck() { if (!('uncheckValue' in (this.config ?? {}))) { throw new Error('To use uncheck method you need to provide uncheckValue in DIStateControl config'); } this.updateModel(this.config.uncheckValue); } /** Sets intermediate state */ intermediate() { this.updateModel(null); } /** Toggles checked state */ toggle() { this.checked() ? this.uncheck() : this.check(); } get isIntermediate() { return this.model() === null && !!this.config?.hasIntermediate; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DIStateControl, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.5", type: DIStateControl, isStandalone: false, usesInheritance: true, usesOnChanges: true, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DIStateControl, decorators: [{ type: Directive, args: [{ standalone: false }] }], ctorParameters: () => [{ type: undefined }] }); /** * `DICollectionControl` can be used to implement array controls (checkbox group, radio group, chips, etc.). * It has an additional integration with `DIStateControl` that allows you to use it as a host for * `DIStateControl` controls. If you use `DIStateControl` as a child control, then `DICollectionControl` * will update its model when the child control is checked or unchecked, so `DICollectionControl` will * contain only checked values. * * It also works with other controls, but their model should be an array. * * > **Warning** * > If child control model is updated with non-array value, then `DICollectionControl` will be updated with `null`. * * ## Creating a control * To create a control you need to extend your `@Component` or `@Directive` from `DICollectionControl` class. * After that your control will be able to work with `NgModel`, `FormControl`. * * ```ts fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent extends DICollectionControl<string> { * constructor() { * super(); * } * } * ``` * * ## Registering as a host * By default your control can work only with `NgModel` and `FormControl`. But you can register your control as a host * for another controls, then your control will be able to update them and accept updates from them. To do that you need to * use `provideHostControl` function. * * ```ts {2} fileName="custom-control.component.ts" * @Component({ * providers: [provideHostControl(CustomControlComponent)], * }) * export class CustomControlComponent extends DICollectionControl<string> { * constructor() { * super(); * } * } * ``` * * ## Injecting host control * By default your control doesn't communicate with host controls. But you can inject host control and put it * into `super` call. This will register your control in the host control and start communication between them. * * > **Note** * > If you register your control as a host for another controls, then you can inject it * > only with `skipSelf` option. * * ```ts {5} fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent extends DICollectionControl<string> { * constructor() { * // we add `optional` option to make it possible to use this control without host * super({host: injectHostControl({optional: true})}); * } * } * ``` * * ## Getting model * To get model you need to use `model` property. It will return model for the current control. * * ```ts {9} fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent extends DICollectionControl<string> { * constructor() { * super(); * } * * @HostListener('click') * onClick() { * console.log(this.model()); * } * } * ``` * * ## Updating model * To update model you need to call `updateModel` method. It will update model for the current control and all * children controls, as well as for the `NgModel` or `FormControl`. * * ```ts {9} fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent extends DICollectionControl<string> { * constructor() { * super(); * } * * @HostListener('click') * onClick() { * this.updateModel(['new value']); * } * } * ``` * ## Catching updates * Sometimes you may need to catch updates from different sources. For example, to update the value of the native * input element. To do this, you can provide the `onIncomingUpdate` hook. * * ```ts {6} fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent extends DICollectionControl<string> { * constructor() { * super({ * onIncomingUpdate: (value: string[] | null) => { * this.elementRef.nativeElement.value = value; * }, * }); * } * } * ``` * ``` */ class DICollectionControl extends DIControl { config; proxyModel = new SetCompare(); constructor(config) { super(config); this.config = config; } getCompareFn() { return typeof this.config?.compare === 'function' ? this.config.compare : resolveValue(this.config?.compare?.compareFn ?? DI_DEFAULT_COMPARE); } internalUpdateModel(obj) { this.proxyModel = new SetCompare(this.getCompareFn(), obj); super.internalUpdateModel(obj); } writeValue(value) { this.proxyModel = new SetCompare(this.getCompareFn(), value); super.writeValue(value); } writeValueFromHost(obj) { this.proxyModel = new SetCompare(this.getCompareFn(), obj); super.writeValueFromHost(obj); } childControlChange(control, value) { this.updateFrom = control; this.updateModel(this.getNewModel(control, value)); this.config?.onIncomingUpdate && this.config.onIncomingUpdate(this.model()); } getNewModel(control, updates) { if (control instanceof DIStateControl) { control.checked() ? this.proxyModel.add(resolveValue(control.value)) : this.proxyModel.delete(resolveValue(control.value)); } else if (Array.isArray(updates)) { this.proxyModel = new SetCompare(this.getCompareFn(), updates); } else { this.proxyModel = new SetCompare(this.getCompareFn()); return null; } return this.proxyModel.toArray(); } updateControl(control) { if (control instanceof DIStateControl) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error control.writeValueFromHost(this.proxyModel.has(resolveValue(control.value)) ? resolveValue(control.value) : control.config.uncheckValue); } else { control.writeValueFromHost(this.model()); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DICollectionControl, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.5", type: DICollectionControl, isStandalone: true, usesInheritance: true, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DICollectionControl, decorators: [{ type: Directive }], ctorParameters: () => [{ type: undefined }] }); /** * `DIProxyControl` is very suitable. * It is typically used as a host and works exclusively with objects. It is * necessary to bind child controls to a specific property of the object, * thereby ensuring that they update only that specific property and not the * entire object as a whole. Please see `*DateRangePage`. * * ## Creating a control * To create a control you need to extend your `@Component` or `@Directive` from `DIProxyControl` class * and provide `getValue` and `setValue` functions that will be used to get and set value from the object * to the child control. * * ```ts fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent extends DIProxyControl<MyObject> { * constructor() { * super({ * getValue: (model) => model.objectProperty, * setValue: (model, value) => ({...model, objectProperty: value}), * }); * } * } * ``` * * ## Registering as a host * By default your control can work only with `NgModel` and `FormControl`. But you can register your control as a host * for another controls, then your control will be able to update them and accept updates from them. To do that you need to * use `provideHostControl` function. * * ```ts {2} fileName="custom-control.component.ts" * @Component({ * providers: [provideHostControl(CustomControlComponent)], * }) * export class CustomControlComponent extends DIProxyControl<MyObject> { * constructor() { * super({ * getValue: (model) => model.objectProperty, * setValue: (model, value) => ({...model, objectProperty: value}), * }); * } * } * ``` * * ## Injecting host control * By default your control doesn't communicate with host controls. But you can inject host control and put it * into `super` call. This will register your control in the host control and start communication between them. * * > **Note** * > If you register your control as a host for another controls, then you can inject it * > only with `skipSelf` option. * * ```ts {5} fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent extends DIProxyControl<MyObject> { * constructor() { * // we add `optional` option to make it possible to use this control without host * super({ * host: injectHostControl({optional: true}), * getValue: (model) => model.objectProperty, * setValue: (model, value) => ({...model, objectProperty: value}), * }); * } * } * ``` * * ## Getting model * To get model you need to use `model` property. It will return model for the current control. * * ```ts {9} fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent extends DIProxyControl<MyObject> { * constructor() { * super( * getValue: (model) => model.objectProperty, * setValue: (model, value) => ({...model, objectProperty: value}), * ); * } * * @HostListener('click') * onClick() { * console.log(this.model()); * } * } * ``` * * ## Updating model * To update model you need to call `updateModel` method. It will update model for the current control and all * children controls, as well as for the `NgModel` or `FormControl`. * * ```ts {9} fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent extends DIProxyControl<MyObject> { * constructor() { * super( * getValue: (model) => model.objectProperty, * setValue: (model, value) => ({...model, objectProperty: value}), * ); * } * * @HostListener('click') * onClick() { * this.updateModel({objectProperty: 'new value'}); * } * } * ``` * ## Catching updates * Sometimes you may need to catch updates from different sources. For example, to update the value of the native * input element. To do this, you can provide the `onIncomingUpdate` hook. * * ```ts {6} fileName="custom-control.component.ts" * @Component({}) * export class CustomControlComponent extends DIProxyControl<MyObject> { * constructor() { * super({ * onIncomingUpdate: (value: MyObject | null) => { * this.elementRef.nativeElement.value = value; * }, * }); * } * } * ``` */ class DIProxyControl extends DIControl { config; constructor(config) { super(config); this.config = config; } registerControl(control) { this.children.add(control); /* * We have to update control because its can be created dynamically. * We use Promise.resolve because NgModel uses it too to set first value (https://github.com/angular/angular/blob/7df9127088bda3c9d29937a04287b87dc2045ea7/packages/forms/src/directives/ng_model.ts#L314) * so there's no need to use angular life cycle hooks */ Promise.resolve().then(() => control.writeValueFromHost(this.config.getValue(this.model()))); control.registerOnControlChange((value) => { value = this.config.setValue(this.model(), value); this.childControlChange(control, value); this.config?.onChildControlChange?.(control, value); }); control.registerRequestForUpdate(() => { this.updateControl(control, this.config.getValue(this.model())); }); } updateControl(control, value) { control.writeValueFromHost(this.config.getValue(value)); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DIProxyControl, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.5", type: DIProxyControl, isStandalone: true, usesInheritance: true, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DIProxyControl, decorators: [{ type: Directive }], ctorParameters: () => [{ type: undefined }] }); /** * Generated bundle index. Do not edit. */ export { DICollectionControl, DIControl, DIControlValueAccessor, DIProxyControl, DIStateControl }; //# sourceMappingURL=di-controls-controls.mjs.map