Search code examples
angulartypescriptviewchild

Angular tooling to map a parameter to a ViewChild element


For a video meeting application I have constructed, I have one method (below) that needs refactoring. The application works well, and there are actually 18 tiles, but for purposes of discussion i have abbreviated it. Is there a way in Angular (in this case Angular 10.0.6 w/ TypeScript 3.9.5) to access a @ViewChild/ElementRef with a dynamic parameter?

Here is the method I use:

  getTilesFromIndex = (index: number) => {
    const tileObject = {
      tile: null,
      video: null,
      nameplate: null
    };
    switch (index) {
      case 0:
        tileObject.tile = this.tile0.nativeElement;
        tileObject.video = this.video0.nativeElement;
        tileObject.nameplate = this.nameplate0.nativeElement;
        break;
      case 1:
        tileObject.tile = this.tile1.nativeElement;
        tileObject.video = this.video1.nativeElement;
        tileObject.nameplate = this.nameplate1.nativeElement;
        break;
      case 2:
        tileObject.tile = this.tile2.nativeElement;
        tileObject.video = this.video2.nativeElement;
        tileObject.nameplate = this.nameplate2.nativeElement;
        break;
      case 3:
        tileObject.tile = this.tile3.nativeElement;
        tileObject.video = this.video3.nativeElement;
        tileObject.nameplate = this.nameplate3.nativeElement;
        break;
      case 4:
        tileObject.tile = this.tile4.nativeElement;
        tileObject.video = this.video4.nativeElement;
        tileObject.nameplate = this.nameplate4.nativeElement;
        break;
    }
    return tileObject;
  }

it references the corresponding markup:

  <div id="tile-0" style="display:none" #tile0>
    <video id="video-0" class="w-100 h-100" #video0></video>
    <div id="nameplate-0" #nameplate0></div>
  </div>
  <div id="tile-1" style="display:none" #tile1>
    <video id="video-1" class="w-100 h-100" #video1></video>
    <div id="nameplate-1" #nameplate1></div>
  </div>
  <div id="tile-2" style="display:none" #tile2>
    <video id="video-2" class="w-100 h-100" #video2></video>
    <div id="nameplate-2" #nameplate2></div>
  </div>
  <div id="tile-3" style="display:none" #tile3>
    <video id="video-3" class="w-100 h-100" #video3></video>
    <div id="nameplate-3" #nameplate3></div>
  </div>
  <div id="tile-4" style="display:none" #tile4>
    <video id="video-4" class="w-100 h-100" #video4></video>
    <div id="nameplate-4" #nameplate4></div>
  </div>

the @ViewChild elements are defined as such:

  @ViewChild('tile0') tile0: ElementRef;
  @ViewChild('tile1') tile1: ElementRef;
  @ViewChild('tile2') tile2: ElementRef;
  @ViewChild('tile3') tile3: ElementRef;
  @ViewChild('tile4') tile4: ElementRef;

  @ViewChild('video0') video0: ElementRef;
  @ViewChild('video1') video1: ElementRef;
  @ViewChild('video2') video2: ElementRef;
  @ViewChild('video3') video3: ElementRef;
  @ViewChild('video4') video4: ElementRef;

  @ViewChild('nameplate0') nameplate0: ElementRef;
  @ViewChild('nameplate1') nameplate1: ElementRef;
  @ViewChild('nameplate2') nameplate2: ElementRef;
  @ViewChild('nameplate3') nameplate3: ElementRef;
  @ViewChild('nameplate4') nameplate4: ElementRef;

The application dynamically assigns tiles and the method is used throughout to manipulate the DOM, stop and start video, toggle visibility, and change layout.

I want to get away from such a literal reference to the ViewChild elements. I have tried string literals, but the just operate on strings. How would i refactor this method so I can take the index parameter and return a reference to the specific @ViewChild. To give an example in pseudo code:

getTilesFromIndex = (index: number) => {
    const tileObject = {
      tile: null,
      video: null,
      nameplate: null
    };

   tileObject.tile = this.tile${index}.nativeElement;
   tileObject.video = this.video${index}.nativeElement;
   tileObject.nameplate = this.nameplate${index}.nativeElement;
    
   return tileObject;
  }

I know a string literal does not work, this is really done to just convey the idea of what I want the refactor to do. How could I make this method return the viewChild without so explicitly referencing the individual @viewChild's.


Solution

  • Thats why there is ViewChildrenin in Angular. It returns a QueryList though, so you will need to subscribe to it. Also will need to group HTML elements somehow (like adding CSS class, or by reading by their type).

    @ViewChildren('.video') videos!: QueryList<ElementRef>;
    
    ngAfgterViewInit() {
      this.videos.changes.subscribe(...
    }