dynafield.directive.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import {
  2. Directive, ComponentFactoryResolver, ComponentRef, ViewContainerRef,
  3. Input, Output, EventEmitter, OnInit, OnDestroy,
  4. Optional, Self, SkipSelf, Inject
  5. } from '@angular/core';
  6. import {
  7. Form, FormControl, AbstractControl,
  8. NgControl, ControlContainer, ControlValueAccessor,
  9. NG_VALIDATORS, Validator, Validators, ValidatorFn, AsyncValidatorFn
  10. } from '@angular/forms';
  11. import * as formFieldComponents from './../components';
  12. interface FFC {
  13. control: FormControl; // Remember, this can be an individual FormControl or a FormGroup
  14. meta: StringMap;
  15. propagateChange?: Function;
  16. call?: EventEmitter<string>
  17. }
  18. type FFCCustom = FFC & ControlValueAccessor;
  19. // Generate component name given type
  20. const componentType = (type: string): string => type[0].toUpperCase() + type.slice(1) + 'Component';
  21. @Directive({
  22. // tslint:disable-next-line:directive-selector
  23. selector: '[dynafield]'
  24. })
  25. export class DynafieldDirective extends NgControl implements OnInit, OnDestroy {
  26. @Input()
  27. meta: StringMap;
  28. @Input()
  29. set control(fc: FormControl) {
  30. this._control = this._control || fc;
  31. }
  32. get control(): FormControl | null {
  33. return this._control;
  34. }
  35. // tslint:disable-next-line:no-output-rename
  36. @Output('ngModelChange')
  37. update = new EventEmitter();
  38. @Output()
  39. call: EventEmitter<string> = new EventEmitter<string>();
  40. component: ComponentRef<FFC|FFCCustom>;
  41. _control;
  42. constructor(
  43. private resolver: ComponentFactoryResolver,
  44. private container: ViewContainerRef,
  45. @SkipSelf() private cc: ControlContainer,
  46. @Optional() @Self() @Inject(NG_VALIDATORS) private validators: Array<Validator | ValidatorFn>
  47. ) {
  48. super();
  49. }
  50. ngOnInit() {
  51. const type = componentType(this.meta.type);
  52. if (!formFieldComponents[type]) {
  53. const validComponentTypes = Object.keys(formFieldComponents).join(', ');
  54. throw new Error(
  55. `Dynaform Dynafield Error: Invalid field type: ${type}.
  56. Supported types: ${validComponentTypes}`
  57. );
  58. }
  59. try {
  60. let { control, meta } = this;
  61. const { name, class: cssClass, id: cssId, isDisabled } = meta;
  62. // Create the component
  63. const component = this.resolver.resolveComponentFactory<FFC>(formFieldComponents[type]);
  64. this.component = this.container.createComponent(component);
  65. const instance = this.component.instance;
  66. const el = this.component.location.nativeElement;
  67. el.classList.add(type.toLowerCase().replace('component', ''));
  68. // Support the recursive insertion of Dynaform components
  69. if (type === 'DynaformComponent') {
  70. if (meta.template) {
  71. (<any>instance).template = meta.template;
  72. }
  73. meta = meta.meta;
  74. }
  75. // Check whether it's disabled, then set its FormControl and metadata
  76. if (isDisabled) {
  77. this.control.reset({ value: this.control.value, disabled: true });
  78. }
  79. instance.control = control;
  80. instance.meta = meta;
  81. // Listen for 'call' Output events and send them onwards
  82. if (instance.call) {
  83. instance.call.subscribe((fnId: string) => this.call.emit(fnId));
  84. }
  85. // Add id and classes (as specified)
  86. if (cssId) {
  87. el.id = cssId;
  88. }
  89. if (cssClass) {
  90. const classesToAdd = Array.isArray(cssClass) ? cssClass : [cssClass];
  91. el.classList.add(...classesToAdd);
  92. }
  93. // Connect custom components
  94. if (instance.propagateChange) {
  95. // We're dealing with a custom form control which implements the ControlValueAccessor interface,
  96. // so we need to wire it up!
  97. this.name = name;
  98. this.valueAccessor = <FFCCustom>this.component.instance;
  99. // Attach sync validators
  100. /*
  101. const ngValidators = this.component.injector.get(NG_VALIDATORS, null);
  102. if (ngValidators && ngValidators.some(x => x as any === this.component.instance)) {
  103. console.log('HERE');
  104. this.validators = [...(this.validators || []), ...(ngValidators as Array<Validator|ValidatorFn>)];
  105. }
  106. */
  107. this._control = this.formGroupDirective.addControl(this);
  108. }
  109. } catch (e) {
  110. console.error('ERROR INSTANTIATING DYNAFORM CHILD COMPONENT', type);
  111. console.log(e);
  112. }
  113. }
  114. ngOnDestroy(): void {
  115. if (this.formGroupDirective) {
  116. this.formGroupDirective.removeControl(this);
  117. }
  118. if (this.component) {
  119. this.component.destroy();
  120. }
  121. }
  122. get path(): string[] {
  123. return [...this.cc.path, this.name];
  124. }
  125. get formGroupDirective(): Form | null {
  126. return this.cc ? this.cc.formDirective : null;
  127. }
  128. get validator(): ValidatorFn | null {
  129. return this.validators !== null ? Validators.compose(this.validators.map(this.normalizeValidator)) : null;
  130. }
  131. get asyncValidator(): AsyncValidatorFn { return null; }
  132. // Override the method in NgControl
  133. viewToModelUpdate(newValue: any): void {
  134. this.update.emit(newValue);
  135. }
  136. normalizeValidator(validator: ValidatorFn | Validator): ValidatorFn {
  137. if ((<Validator>validator).validate) {
  138. return (c: AbstractControl) => (<Validator>validator).validate(c);
  139. } else {
  140. return <ValidatorFn>validator;
  141. }
  142. }
  143. }