htmlangularngforangular-inputangular-output

Angular @input and @Output conflict


I made a directive in angular which is act as a time input and in template of component I repeated it based on an array of string. The directive has an @Input that is the initial value which is set to directive elementRef value. In addition the directive has an @Output that emit the value of elementRef. All things goes well unless when I want to get the emitted value from directive and set it in the array which is the source of repetition for directive.

The Component ts file

import {Component} from '@angular/core';
@Component({
    templateUrl:'./home.component.html',
    styleUrls:['./home.component.css']
})
export class HomeComponent{
    timeArray:string[] = ['01:25','00:00','10:10','00:00','00:00'];

    onTimeChanging(arrayIndex : number,time:string){
        //problem occure in the following line
        this.timeArray[arrayIndex] = time;
    }
}

The Component html file

<div>
     <input [timeInput]="time" (changeTime)="onTimeChanging(arrayIndex,$event)" *ngFor="let time of timeArray; index as arrayIndex">
 </div>
 <hr>
 <ol>
     <li *ngFor="let time of timeArray">{{time}}</li>
 </ol>

The directive ts file

    import { Directive,ElementRef,EventEmitter,HostListener,Input, Output } from "@angular/core";

@Directive({
    selector:'[timeInput]'
})
export class TimeInputDirective{
    @Input('timeInput') initialValue: string = '00:00';
    @Output() changeTime = new EventEmitter<string>();

    constructor(private elementRef:ElementRef<HTMLInputElement>){
        this.elementRef.nativeElement.className = 'timeInput';
    }
    
    ngAfterViewInit(){
        this.elementRef.nativeElement.value = this.initialValue
    }

    @HostListener('keydown', ['$event']) onKeydownHandler(event: KeyboardEvent) {
        if (event.key != 'Tab') {
          event.preventDefault();
          let value = this.elementRef.nativeElement.value;
          let valuePart;
      
          switch (event.key) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
              if ((this.elementRef.nativeElement.selectionStart == null ? 0 : this.elementRef.nativeElement.selectionStart) < value.indexOf(':')) {
                valuePart = (value.substring(0, value.indexOf(':')) + event.key).substring(3, 1);
                valuePart = valuePart > '24' ? '0' + valuePart.substring(2, 1) : valuePart;
      
                this.elementRef.nativeElement.value = `${valuePart}:${value.substring(value.indexOf(':') + 1)}`;
      
                this.elementRef.nativeElement.selectionStart = 0;
                this.elementRef.nativeElement.selectionEnd = 2;
              } else {
                valuePart = (value.substring(value.indexOf(':') + 1) + event.key).substring(3, 1);
                valuePart = valuePart > '59' ? '00' : valuePart;
      
                this.elementRef.nativeElement.value = `${value.substring(0, value.indexOf(':'))}:${valuePart}`;
      
                this.elementRef.nativeElement.selectionStart = 3;
                this.elementRef.nativeElement.selectionEnd = 5;
              }
              break;
            case 'ArrowUp':
              if (this.elementRef.nativeElement.selectionStart == null ? 0 : this.elementRef.nativeElement.selectionStart < value.indexOf(':')) {
                valuePart = (+value.substring(0, value.indexOf(':')) + 1).toString();
                valuePart = ('0' + valuePart).substring(valuePart.length + 1, valuePart.length - 1) > '24' ? '00' : ('0' + valuePart).substring(valuePart.length + 1, valuePart.length - 1);
      
                this.elementRef.nativeElement.value = `${valuePart}:${value.substring(value.indexOf(':') + 1)}`;
      
                this.elementRef.nativeElement.selectionStart = 0;
                this.elementRef.nativeElement.selectionEnd = 2;
              } else {
                valuePart = (+value.substring(value.indexOf(':') + 1) + 1).toString();
      
                if (valuePart <= '59') {
                  this.elementRef.nativeElement.value = `${value.substring(0, value.indexOf(':'))}:${('0' + valuePart).substring(valuePart.length + 1, valuePart.length - 1)}`;
                } else {
                  var hourPart = Math.floor(+valuePart / 60) + (+value.substring(0, value.indexOf(':')));
      
                  if (hourPart > 24) {
                    this.elementRef.nativeElement.value = `${value.substring(0, value.indexOf(':'))}:00`;
                  } else {
                    this.elementRef.nativeElement.value = `${('0' + hourPart).substring(hourPart.toString().length + 1, hourPart.toString().length - 1)}:${('0' + (+valuePart % 60)).substring((+valuePart % 60).toString().length + 1, (+valuePart % 60).toString().length - 1)}`
                  }
                }
      
                this.elementRef.nativeElement.selectionStart = 3;
                this.elementRef.nativeElement.selectionEnd = 5;
              }
              break;
            case 'ArrowDown':
              if (this.elementRef.nativeElement.selectionStart == null ? 0 : this.elementRef.nativeElement.selectionStart < value.indexOf(':')) {
                valuePart = (+value.substring(0, value.indexOf(':')) - 1).toString();
                valuePart = ('0' + valuePart).substring(valuePart.length + 1, valuePart.length - 1) < '00' ? '24' : ('0' + valuePart).substring(valuePart.length + 1, valuePart.length - 1);
      
                this.elementRef.nativeElement.value = `${valuePart}:${value.substring(value.indexOf(':') + 1)}`;
      
                this.elementRef.nativeElement.selectionStart = 0;
                this.elementRef.nativeElement.selectionEnd = 2;
              } else {
                valuePart = (+value.substring(value.indexOf(':') + 1) - 1).toString();
                valuePart = ('0' + valuePart).substring(valuePart.length + 1, valuePart.length - 1) < '00' ? '59' : ('0' + valuePart).substring(valuePart.length + 1, valuePart.length - 1);
      
                this.elementRef.nativeElement.value = `${value.substring(0, value.indexOf(':'))}:${valuePart}`;
      
                this.elementRef.nativeElement.selectionStart = 3;
                this.elementRef.nativeElement.selectionEnd = 5;
              }
              break;
            case 'ArrowLeft':
              if (this.elementRef.nativeElement.selectionStart == null ? 0 : this.elementRef.nativeElement.selectionStart > this.elementRef.nativeElement.value.indexOf(':')) {
                this.elementRef.nativeElement.selectionStart = 0;
                this.elementRef.nativeElement.selectionEnd = 2;
              }
              break;
            case 'ArrowRight':
              if (this.elementRef.nativeElement.selectionStart == null ? 0 : this.elementRef.nativeElement.selectionStart < this.elementRef.nativeElement.value.indexOf(':')) {
                this.elementRef.nativeElement.selectionStart = 3;
                this.elementRef.nativeElement.selectionEnd = 5;
              }
              break;
            case 'Delete':
              if (this.elementRef.nativeElement.selectionStart == null ? 0 : this.elementRef.nativeElement.selectionStart < value.indexOf(':')) {
                this.elementRef.nativeElement.value = `00:${value.substring(value.indexOf(':') + 1)}`;
                this.elementRef.nativeElement.selectionStart = 0;
                this.elementRef.nativeElement.selectionEnd = 2;
              } else {
                this.elementRef.nativeElement.value = `${value.substring(0, value.indexOf(':'))}:00`;
                this.elementRef.nativeElement.selectionStart = 3;
                this.elementRef.nativeElement.selectionEnd = 5;
              }
              break;
          }      
        }

        if (['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'ArrowUp', 'ArrowDown', 'Delete'].includes(event.key)) {
            this.changeTime.emit(this.elementRef.nativeElement.value);
        }
      }
}

I would be really appreciated if someone can help me.


Solution

  • When relying on indices for event binding, You must use trackBy with *ngFor

    in component ts file add:

    trackByIndex(index: number, item: any): number {
      return index;
    }
    

    change this in html:

       <input [timeInput]="time" (changeTime)="onTimeChanging(arrayIndex, $event)" *ngFor="let time of timeArray; index as arrayIndex; trackBy: trackByIndex">