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) { 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 = ''; @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 this.values = obj ? obj : []; if (this.values.length === 0 || this.values[0] !== '') { // add empty last field if pushTemplate is specified if (this.pushTemplate !== null) { this.values.push(cloneDeep(this.pushTemplate)); } } } registerOnChange(fn: any) { this.onChange = fn; } registerOnTouched(fn: any) { this.onTouched = fn; } }