import { Component, OnInit, EventEmitter, ElementRef, Input, Output, DoCheck, ViewContainerRef, ViewChild, ComponentRef, Renderer, ComponentFactoryResolver, } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { FormlyPubSub, FormlyEventEmitter, FormlyValueChangeEvent } from '../services/formly.event.emitter'; import { FormlyConfig } from '../services/formly.config'; import { Field } from '../templates/field'; import { evalExpression } from '../utils'; import { FormlyFieldConfig } from './formly.field.config'; import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/map'; @Component({ selector: 'formly-field', template: `
`, }) export class FormlyField implements DoCheck, OnInit { @Input() model: any; @Input() form: FormGroup; @Input() field: FormlyFieldConfig; @Input() options: any = {}; @Input() get hide() { return this._hide; } set hide(value: boolean) { this._hide = value; this.renderer.setElementStyle(this.elementRef.nativeElement, 'display', value ? 'none' : ''); if (this.field.fieldGroup) { for (let i = 0; i < this.field.fieldGroup.length; i++) { this.psEmit(this.field.fieldGroup[i].key, 'hidden', this._hide); } } else { this.psEmit(this.field.key, 'hidden', this._hide); } } @Output() modelChange: EventEmitter = new EventEmitter(); @ViewChild('fieldComponent', {read: ViewContainerRef}) fieldComponent: ViewContainerRef; private _hide; constructor( private elementRef: ElementRef, private formlyPubSub: FormlyPubSub, private renderer: Renderer, private formlyConfig: FormlyConfig, private componentFactoryResolver: ComponentFactoryResolver, ) {} ngDoCheck() { this.checkExpressionChange(); this.checkVisibilityChange(); } ngOnInit() { this.createFieldComponents(); } changeModel(event: FormlyValueChangeEvent) { this.modelChange.emit(event); } private createFieldComponents() { if (this.field && !this.field.template && !this.field.fieldGroup && !this.field.fieldArray) { let debounce = 0; if (this.field.modelOptions && this.field.modelOptions.debounce && this.field.modelOptions.debounce.default) { debounce = this.field.modelOptions.debounce.default; } let fieldComponentRef = this.createFieldComponent(); if (this.field.key) { let valueChanges = fieldComponentRef.instance.formControl.valueChanges; if (debounce > 0) { valueChanges = valueChanges.debounceTime(debounce); } if (this.field.parsers && this.field.parsers.length > 0) { this.field.parsers.map(parserFn => { valueChanges = valueChanges.map(parserFn); }); } valueChanges.subscribe((event) => this .changeModel(new FormlyValueChangeEvent(this.field.key, event)), ); } let update = new FormlyEventEmitter(); update.subscribe((option: any) => { this.field.templateOptions[option.key] = option.value; }); this.formlyPubSub.setEmitter(this.field.key, update); } else if (this.field.fieldGroup || this.field.fieldArray) { this.createFieldComponent(); } // TODO support this.field.hideExpression as a callback/observable this.hide = this.field.hideExpression ? true : false; } private createFieldComponent(): ComponentRef { if (this.field.fieldGroup) { this.field.type = this.field.type || 'formly-group'; } let type = this.formlyConfig.getType(this.field.type); let fieldComponent = this.fieldComponent; const fieldManipulators = this.getManipulators(this.field.templateOptions); let preWrappers = this.runManipulators(fieldManipulators.preWrapper, this.field); let postWrappers = this.runManipulators(fieldManipulators.postWrapper, this.field); if (!type.wrappers) type.wrappers = []; if (!this.field.wrappers) this.field.wrappers = []; let wrappers = [...preWrappers, ...this.field.wrappers, ...postWrappers]; wrappers.map(wrapperName => { let wrapperRef = this.createComponent(fieldComponent, this.formlyConfig.getWrapper(wrapperName).component); fieldComponent = wrapperRef.instance.fieldComponent; }); return this.createComponent(fieldComponent, type.component); } private createComponent(fieldComponent, component): ComponentRef { let componentFactory = this.componentFactoryResolver.resolveComponentFactory(component); let ref = >fieldComponent.createComponent(componentFactory); Object.assign(ref.instance, { model: this.model, form: this.form, field: this.field, options: this.options, }); return ref; } private psEmit(fieldKey: string, eventKey: string, value: any) { if (this.formlyPubSub && this.formlyPubSub.getEmitter(fieldKey) && this.formlyPubSub.getEmitter(fieldKey).emit) { this.formlyPubSub.getEmitter(fieldKey).emit(new FormlyValueChangeEvent(eventKey, value)); } } private getManipulators(options) { let preWrapper = []; let postWrapper = []; if (options && options.templateManipulators) { addManipulators(options.templateManipulators); } addManipulators(this.formlyConfig.templateManipulators); return {preWrapper, postWrapper}; function addManipulators(manipulators) { const {preWrapper: pre = [], postWrapper: post = []} = (manipulators || {}); preWrapper = preWrapper.concat(pre); postWrapper = postWrapper.concat(post); } } private runManipulators(manipulators: Function[], field: FormlyFieldConfig) { let wrappers = []; if (manipulators) { manipulators.map(manipulator => { if (manipulator(field)) { wrappers.push(manipulator(field)); } }); return wrappers; } } private checkVisibilityChange() { if (this.field && this.field.hideExpression !== undefined && this.field.hideExpression) { const hideExpressionResult: boolean = evalExpression( this.field.hideExpression, this, [this.model, this.options.formState], ); if (hideExpressionResult !== this.hide) { this.hide = hideExpressionResult; } } } private checkExpressionChange() { if (this.field && this.field.expressionProperties !== undefined) { const expressionProperties = this.field.expressionProperties; if (expressionProperties) { for (let key in expressionProperties) { const expressionValue = evalExpression( expressionProperties[key].expression, this, [this.model, this.options.formState], ); evalExpression( expressionProperties[key].expressionValueSetter, this, [expressionValue, this.model, this.field.templateOptions, this.field.validation], ); } const formControl = this.form.get(this.field.key), field = this.field; if (formControl) { if (formControl.status === 'DISABLED' && !field.templateOptions.disabled) { formControl.enable(); } if (formControl.status !== 'DISABLED' && field.templateOptions.disabled) { formControl.disable(); } if (!formControl.dirty && formControl.invalid && field.validation && !field.validation.show) { formControl.markAsUntouched(); } if (!formControl.dirty && formControl.invalid && field.validation && field.validation.show) { formControl.markAsTouched(); } } } } } }