Crowdpleasr
Crowdpleasr

Reputation: 4044

How to get specific item in ViewChildren QueryList as ElementRef while filtering on property(ies)?

I'm trying to set focus to any given input after that input changes (or potentially manipulate that same DOM element in other ways). I'm using Reactive Forms and the Form elements are rendered dynamically.

The problem I have is that I can access the element that has been changed using either @ViewChildren(AppTag) ipt!: QueryList<AppTag> which lets me filter on the element I want, but then I don't know how to grab that element using nativeElement, or I can use @ViewChildren(AppTag) ipt!: QueryList<ElementRef>, which (surprisingly) seems to be letting me filter the element in question, but (also surprisingly) I'm still unable to manipulate the DOM element. Either way, I get error: "itm.nativeElement is undefined".

To reproduce the error, please enter any value into one of the Inputs in the following StackBlitz, and then TAB out of the entry. You'll see the error in the console.

Below are what I think are the relevant pieces of code, but please play around with the full StackBlitz example to see the template and data structure more clearly:

app.component.ts

export class AppComponent implements OnInit {
  @ViewChildren(AppTag) ipt !: QueryList<ElementRef>
  name = 'Angular';
  formCtls: any = {};
  form: FormGroup = new FormGroup({});
  data: {} = { // raw data
    name: 
    {
      first: {values: [""], label: "first name"},
      middle: {values: [""], label: "middle name"},
      last: {values: [""], label: "last name"}
    } 
  };
  dispData: any[] = []; // Data formatted for display/iteration by template
  itmNo: number = 0; // Unique ID for each Input
  focusItm = 0; // The bridge by which "FormControl.valueChanges" communicates with "QueryList.changes"

. . . 
. . .

  ngAfterViewInit() {
    this.ipt.changes.subscribe(list=>{
      setTimeout(()=>
        list.filter(itm=>+itm.id===this.focusItm).forEach(itm=>{
          console.log(`Item: ${itm.id} Focus: ${this.focusItm} ${+itm.id===this.focusItm}`);
          itm.nativeElement.focus();  // <-- HERE'S WHERE I'M HAVING THE TROUBLE
        }  
    ),0)}
    )
  }

. . . 
. . .


  renderDisplayArray(){
    this.dispData = [];
    this.itmNo = 0;

. . . 
. . .

          const i=r;
          this.formCtls[ctlName] = new FormControl(itm["values"][r], {updateOn: 'blur'});
          this.form.addControl(ctlName,this.formCtls[ctlName]);
          const curItm=this.itmNo;

          this.formCtls[ctlName].valueChanges.subscribe(val=>{
            console.log(`VALUE: ${val}`);
            itm["values"][i]=val || '';
            this.renderDisplayArray();
            this.focusItm = curItm;

          })
. . . 
. . .

Here's the Directive AppTag which I apply to the Inputs to be able to filter and grab the DOM elements I want. appTag.ts

import { Component, Directive, ElementRef, OnInit, Input } from "@angular/core";

@Directive({
  selector: '[appTag]'
})

export class AppTag implements OnInit{

constructor(private el: ElementRef) {}
@Input('appTag') id: number;

}

Upvotes: 1

Views: 4734

Answers (1)

Benjamin O. P.
Benjamin O. P.

Reputation: 106

Just change the visibility from el: ElementRef in AppTag to public

@Directive({
  selector: '[appTag]'
})
export class AppTag implements OnInit{
   constructor(public el: ElementRef) {}
   @Input('appTag') id: number;
}

and use in your filter function the el property of the AppTag.

Change your problem line to this:

itm.el.nativeElement.focus();  // <-- HERE'S WHERE I'M HAVING THE TROUBLE

Upvotes: 1

Related Questions