dynafield.directive.ts 5.1 KB

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