import { AfterViewInit, Component, ContentChild, Directive, forwardRef, HostListener, Input, OnInit, TemplateRef } from '@angular/core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; import cloneDeep from 'lodash/cloneDeep'; import {ArrayInputHelperService} from './array-input-helper.service'; @Directive({ // Directive for template and input values // Tslint:disable-next-line:directive-selector selector: '[rbArrayInputItem]' }) export class RbArrayInputItemDirective { constructor(public templateRef: TemplateRef) { } } @Directive({ // Directive for change detection // Tslint:disable-next-line:directive-selector selector: '[rbArrayInputListener]' }) export class RbArrayInputListenerDirective { @Input() rbArrayInputListener: string; @Input() index; constructor( private helperService: ArrayInputHelperService ) { } @HostListener('ngModelChange', ['$event']) onChange(event) { // Emit new value this.helperService.newValue(this.rbArrayInputListener, this.index, event); } } @Component({ // Tslint:disable-next-line:component-selector selector: 'rb-array-input', templateUrl: './rb-array-input.component.html', styleUrls: ['./rb-array-input.component.scss'], providers: [{provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RbArrayInputComponent), multi: true}] }) export class RbArrayInputComponent implements ControlValueAccessor, OnInit, AfterViewInit { pushTemplate: any = ''; // Array element template @Input('pushTemplate') set _pushTemplate(value) { this.pushTemplate = value; if (this.values.length) { this.updateArray(); } } @Input() pushPath: string = null; @ContentChild(RbArrayInputItemDirective) item: RbArrayInputItemDirective; @ContentChild(RbArrayInputListenerDirective) item2: RbArrayInputListenerDirective; values = []; // Main array to display onChange = (ignore?: any): void => {}; onTouched = (ignore?: any): void => {}; constructor( private helperService: ArrayInputHelperService ) { } ngOnInit(): void { } ngAfterViewInit() { setTimeout(() => { // Needed to find reference this.helperService.values(this.item2.rbArrayInputListener).subscribe(data => { // Action on value change // Assign value if (this.pushPath) { this.values[data.index][this.pushPath] = data.value; } else { this.values[data.index] = data.value; } this.updateArray(); }); }, 0); } updateArray() { let res; // Adjust fields if pushTemplate is specified if (this.pushTemplate !== null) { if (this.pushPath) { // Remove last element if last two are empty if (this.values[this.values.length - 1][this.pushPath] === '' && this.values[this.values.length - 2][this.pushPath] === '') { this.values.pop(); } // Add element if last all are filled else if (this.values.filter(e => e[this.pushPath] !== '').length === this.values.length) { this.values.push(cloneDeep(this.pushTemplate)); } res = this.values.filter(e => e[this.pushPath] !== ''); } else { // Remove last element if last two are empty if (this.values[this.values.length - 1] === '' && this.values[this.values.length - 2] === '') { this.values.pop(); } else if (this.values.filter(e => e !== '').length === this.values.length) { // Add element if all are is filled this.values.push(cloneDeep(this.pushTemplate)); } res = this.values.filter(e => e !== ''); } } else { this.values = [this.values[0]]; res = this.values; } if (!res.length) { res = ['']; } this.onChange(res); // Trigger ngModel with filled elements } writeValue(obj: any) { // Add empty value on init if (obj) { if (this.pushTemplate !== null) { // Filter out empty values if (this.pushPath) { this.values = [...obj.filter(e => e[this.pushPath] !== ''), cloneDeep(this.pushTemplate)]; } else { this.values = [...obj.filter(e => e !== ''), cloneDeep(this.pushTemplate)]; } } else { this.values = obj; } } else { if (this.pushTemplate !== null) { this.values = [cloneDeep(this.pushTemplate)]; } else { this.values = ['']; } } } registerOnChange(fn: any) { this.onChange = fn; } registerOnTouched(fn: any) { this.onTouched = fn; } }