0

I'm trying to simulate tab key press when enter is pressed on input control.For that I use a directive:

 private el: ElementRef;
    @Input() onEnter: string;
    constructor(private _el: ElementRef, public renderer: Renderer) {
        this.el = this._el;
    }
    @HostListener('keydown', ['$event']) onKeyDown(e: any) {
        if ((e.which === 13 || e.keyCode === 13)) {
            e.preventDefault();


    const event = new KeyboardEvent("keypress", {
            "key": "Tab"
          });
          this.el.nativeElement.dispatchEvent(event);
............

code for enter key fires but no tab is sent

mrapi
  • 5,831
  • 8
  • 37
  • 57

3 Answers3

3

Updated see better aproach in this stackblitz(sorry,I don't remember the post where I talk about it)

If you want to use ENTER to focus an element you can use a directive

@Directive({
  selector: '[next-tab]',
})
export class NextTabDirective {


  @Input('next-tab') nextControl: any;

  @HostListener("keydown.enter", ["$event"])
  onEnter(event: KeyboardEvent) {
    if (this.nextControl) {
      if (this.nextControl.focus) {
        this.nextControl.focus();
        this.nextControl.select();
        event.preventDefault();
        return false;
      }
    }
  }

  constructor(private control: NgControl) {
  }
}

You can use in a form like

  <form (submit)="Submit()">
      <input #input0 [next-tab]="input1"  />
      <input #input1 [next-tab]="input2"  />
      <!--the last not have [next-tab]-->
      <!-an ENTER make a submit -->
      <input #input2   />
       <button type="button" (click)="cancel()">Cancel</button>
       <button type="submit">OK</button>
   </form>

I would like not use this ugly work-around, but we can improve the directive sending as next-tab an array of controls

@Directive({
  selector: '[next-tab]',
})
export class NextTabDirective {

  @Input('next-tab') nextControl: any[]; //<--an array of controls

  @HostListener("keydown.enter", ["$event"])
  onEnter(event: KeyboardEvent) {

    //find the nextControl not disabled. We check if c is defined
    //This allow us to use *ngIf and not put the control
    let nextControl=this.nextControl.find(c=>c && !c.disabled);
    if (nextControl) {
      if (nextControl.focus) {
        nextControl.focus();
        nextControl.select();
        event.preventDefault();
        return false;
      }
    }
  }

  constructor(private control: NgControl) {
  }
}

The form is look like

   <form (submit)="Submit()">
      <!--see that we create an array-->
      <input #input0 [next-tab]="[input1,input2,input3]"  />
      <input #input1 [next-tab]="[input2,input3]"  />
      <!--if only one element, we make an array of one element-->
      <input #input2 [style.display]="existInput2?'inherit':'none'" [next-tab]="[input3]"   />
      <!--if we want make invisible control NOT use *nfIf, therefore, we must hidden and disabled too -->

      <input #input3 />
       <button type="button" (click)="cancel()">Cancel</button>
       <button type="submit">OK</button>
   </form>

Finally I put a stackblitz in https://stackblitz.com/edit/angular-v8fkkf

Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • Hi.it is good solution but not fully working for me: sometimes I need conditionally to disable some inputs,in this case it doesn't skip to next enabled input as pressing tab key does.thanks – mrapi Jun 20 '18 at 05:27
  • 1
    Puff, I improve the directive using an array of controls. I would like get other solution, but I can't get it :( – Eliseo Jun 20 '18 at 22:43
  • It is a much better solution than before,I've tried to manage the array inside component code,conditionally adding or remove items,but not wrking [nextTab]="arrTabs1" .. @ViewChildren('inp1') inp1; @ViewChildren('inp2') inp2; ...this.arrTabs1 = [this.inp1, this.inp2]; – mrapi Jun 21 '18 at 05:47
  • 1
    I try the code with a ReactiveForm. Don't remove items or use ViewChildren. just write betwenn "[" "]" the "reference name" of the inputs (the reference name is the "#variable"). We pass to the directive all the "controls" captable to be focused (they're in the this.nextControl array). nextControl=this.nextControl.find(c=>c && !c.disabled); find the first control not disabled and "visible" – Eliseo Jun 21 '18 at 06:37
  • Hi.I miss something there,for me it is not working: this.arrTabs1 = ['inp1', 'inp2']; – mrapi Jun 22 '18 at 08:12
  • @mrapi, You don't understand me. I use "reference variables". So you don't give value in the .ts, just in the .html (and not change it anyway). Therefore, the array is an array of "controls", not an array of strings – Eliseo Jun 22 '18 at 08:29
  • 1
    @mrapi I put a stackblitz in https://stackblitz.com/edit/angular-v8fkkf There're a problem if we use ngIf instead of [style.display]="none". I hope this help you – Eliseo Jun 23 '18 at 11:05
  • Hi.your above code worked for me.the only thing I miss is the possibility to add/remove items from array inside component code,manging it easier.thanks for your time – mrapi Jun 23 '18 at 12:19
  • Hi.I have one issue,I want to use the directive with ngx-boostrap datepicker (https://valor-software.com/ngx-bootstrap/) but got this error: CfComponent.html:157 ERROR Error: StaticInjectorError(AppModule)[NextTabDirective -> NgControl]: StaticInjectorError(Platform: core)[NextTabDirective -> NgControl]: NullInjectorError: No provider for NgControl! – mrapi Jun 23 '18 at 15:33
  • 1
    See the directive. In constructor we have control:NgControl. As the directive not use "control", I hope you can simply remove the constructor function to work – Eliseo Jun 24 '18 at 10:18
1

There are a more "confortable way to do a "next-tab" th idea is that the next-tab directive has two variables "self" and "next-control". We not use @Input, the directive is like

@Directive({
  selector: '[next-tab]',
})
export class NextTabDirective {

  self:any;     
  nextControl:any;  //See that is not a @Input

  @HostListener("keydown.enter", ["$event"])
  onEnter(event: KeyboardEvent) {
    if (this.nextControl) {
      if (this.nextControl.focus) {
        this.nextControl.focus();
        this.nextControl.select();
        event.preventDefault();
        return false;
      }
    }
  }

  constructor(private control: ElementRef) {
    //we store in "self" the native element. This make us easy refered to 
    //html properties of control
    this.self=control.nativeElement;
  }
}

In the .html see that the directive is simply next-tab, not [next-tab] and not [next-tab]="". Take account that input4 is too "decorated" with the directive, and that all the input have a "name" html property

<form [formGroup]="myForm" (submit)="submit(myForm)">
  <p>
    <input type="checkbox" formControlName="ckDisabled"/>Disabled input 2
  </p>
  <p>
    <input type="checkbox" formControlName="ckInvisible"/>Invisible input 3
  </p>
  <p>
    Input 1: <input name="input1" formControlName="input1"
              next-tab/>
  </p>
  <p>
    Input 2: <input name="input2" formControlName="input2" tabindex=""
              [enableControl]="!myForm.controls['ckDisabled'].value" 
              next-tab/>
  </p>
  <p [style.display]="myForm.controls['ckInvisible'].value?'none':'inherit'">
    Input 3: <input name="input3" formControlName="input3"
              [enableControl]="!myForm.controls['ckInvisible'].value"
              next-tab
/>
  </p>
  <p>
    Input 4: <input name="input4"  next-tab formControlName="input4"/>
  </p>
  <p>
  <button>OK</button>
</p>
  </form>

And now the .ts. We use ViewChildren to get all the element that have a next-tab directive.

export class AppComponent implements OnInit,AfterViewInit  {
  //We use a ViewChildren to get all the controls width NextTabDirective
  //We use QueryList<NextTabDirective>. So the elements of "inputs"
  //will have the properties: self and nextControl

  @ViewChildren(NextTabDirective) inputs: QueryList<NextTabDirective>;

  myForm:FormGroup;

  constructor(private fb:FormBuilder){}
  //in ngAfterViewInit we asing to nextControl, the self of the next control
  ngAfterViewInit() {
      const controls=this.inputs.toArray();
      //controls will be [input1,input2,input3,input4]
      for (let i=0;i<controls.length-1;i++)
          controls[i].nextControl=controls[i+1].self;        }
}


  ngOnInit()
  {
   this.myForm=this.fb.group({
    ckDisabled:false,
    ckInvisible:false,
    input1:'',
    input2:'',
    input3:'',
    input4:''

  });
  this.onChanges();
  }
  onChanges()
  {
    this.myForm.valueChanges.subscribe((value)=>{
         if (this.inputs)
         {
           //see how we get the control using self.name
           const input1=this.inputs.find(c=>c.self.name=="input1");
           const input2=this.inputs.find(c=>c.self.name=="input2");
           const input3=this.inputs.find(c=>c.self.name=="input3");
           const input4=this.inputs.find(c=>c.self.name=="input4");

           input1.nextControl=(value.ckDisabled && value.ckInvisible)?input4.self:
                            (value.ckDisabled)?input3.self:input2.self;
           input2.nextControl=(value.ckInvisible)?input4.self:input3.self;
         }

    })

  }
  submit(form:any)
  {
    if (form.valid)
      alert("Fom submitted!!")
  }
}

there are a stackBlitz in https://stackblitz.com/edit/angular-dzdxmh

Eliseo
  • 50,109
  • 4
  • 29
  • 67
1

Very simple, No need custom directive or any dependences:

Give an "#Name" for your inputs and use (keyup.enter) native directive

  <mat-form-field>
    <mat-label>xxxx</mat-label>
    <input matInput [(ngModel)]="xxxx" name="xxxx" #xxxxInput (keyup.enter)="yyyyInput.focus()">
  </mat-form-field>

  <mat-form-field>
    <mat-label>yyyy</mat-label>
    <input matInput [(ngModel)]="yyyy" name="yyyy" #yyyyInput (keyup.enter)="zzzzInput.focus()">
  </mat-form-field>

  <mat-form-field>
    <mat-label>zzzz</mat-label>
    <input matInput [(ngModel)]="zzzz" name="zzzz" #zzzzInput>
  </mat-form-field>