import { Directive, ComponentFactoryResolver, ComponentRef, ViewContainerRef, Input, Output, EventEmitter, OnInit, OnDestroy, Optional, Self, SkipSelf, Inject } from '@angular/core'; import { Form, FormControl, AbstractControl, NgControl, ControlContainer, ControlValueAccessor, NG_VALIDATORS, Validator, Validators, ValidatorFn, AsyncValidatorFn } from '@angular/forms'; import * as formFieldComponents from './../components'; interface FFC { control: FormControl; // Remember, this can be an individual FormControl or a FormGroup meta: StringMap; propagateChange?: Function; call?: EventEmitter } type FFCCustom = FFC & ControlValueAccessor; // Generate component name given type const componentType = (type: string): string => type[0].toUpperCase() + type.slice(1) + 'Component'; @Directive({ // tslint:disable-next-line:directive-selector selector: '[dynafield]' }) export class DynafieldDirective extends NgControl implements OnInit, OnDestroy { @Input() meta: StringMap; @Input() set control(fc: FormControl) { this._control = this._control || fc; } get control(): FormControl | null { return this._control; } // tslint:disable-next-line:no-output-rename @Output('ngModelChange') update = new EventEmitter(); @Output() call: EventEmitter = new EventEmitter(); component: ComponentRef; _control; constructor( private resolver: ComponentFactoryResolver, private container: ViewContainerRef, @SkipSelf() private cc: ControlContainer, @Optional() @Self() @Inject(NG_VALIDATORS) private validators: Array ) { super(); } ngOnInit() { const type = componentType(this.meta.type); if (!formFieldComponents[type]) { const validComponentTypes = Object.keys(formFieldComponents).join(', '); throw new Error( `Dynaform Dynafield Error: Invalid field type: ${type}. Supported types: ${validComponentTypes}` ); } try { let { control, meta } = this; const { name, class: cssClass, id: cssId, isDisabled } = meta; // Create the component const component = this.resolver.resolveComponentFactory(formFieldComponents[type]); this.component = this.container.createComponent(component); const instance = this.component.instance; const el = this.component.location.nativeElement; el.classList.add(type.toLowerCase().replace('component', '')); // Support the recursive insertion of Dynaform components if (type === 'DynaformComponent') { if (meta.template) { (instance).template = meta.template; } meta = meta.meta; } // Check whether it's disabled, then set its FormControl and metadata if (isDisabled) { this.control.reset({ value: this.control.value, disabled: true }); } instance.control = control; instance.meta = meta; // Listen for 'call' Output events and send them onwards if (instance.call) { instance.call.subscribe((fnId: string) => this.call.emit(fnId)); } // Add id and classes (as specified) if (cssId) { el.id = cssId; } if (cssClass) { const classesToAdd = Array.isArray(cssClass) ? cssClass : [cssClass]; el.classList.add(...classesToAdd); } // Connect custom components if (instance.propagateChange) { // We're dealing with a custom form control which implements the ControlValueAccessor interface, // so we need to wire it up! this.name = name; this.valueAccessor = this.component.instance; // Attach sync validators /* const ngValidators = this.component.injector.get(NG_VALIDATORS, null); if (ngValidators && ngValidators.some(x => x as any === this.component.instance)) { console.log('HERE'); this.validators = [...(this.validators || []), ...(ngValidators as Array)]; } */ this._control = this.formGroupDirective.addControl(this); } } catch (e) { console.error('ERROR INSTANTIATING DYNAFORM CHILD COMPONENT', type); console.log(e); } } ngOnDestroy(): void { if (this.formGroupDirective) { this.formGroupDirective.removeControl(this); } if (this.component) { this.component.destroy(); } } get path(): string[] { return [...this.cc.path, this.name]; } get formGroupDirective(): Form | null { return this.cc ? this.cc.formDirective : null; } get validator(): ValidatorFn | null { return this.validators !== null ? Validators.compose(this.validators.map(this.normalizeValidator)) : null; } get asyncValidator(): AsyncValidatorFn { return null; } // Override the method in NgControl viewToModelUpdate(newValue: any): void { this.update.emit(newValue); } normalizeValidator(validator: ValidatorFn | Validator): ValidatorFn { if ((validator).validate) { return (c: AbstractControl) => (validator).validate(c); } else { return validator; } } }