Search code examples
angularviewchild

Template parse errors: Reference "#XXX" is defined several times in angular


I want to use the same name of template reference variables for querying at @ViewChildren.

Metadata Properties:

selector - the directive type or the name used for querying.
read - read a different token from the queried elements.

But, I got a template parse error:

Reference "#abc" is defined several times

Sample:

import {AfterViewInit, Component, Directive, Input, QueryList, ViewChildren, ElementRef} from '@angular/core';

@Directive({selector: 'pane'})
export class Pane {
  @Input() id: string;
}

@Directive({selector: 'pane1'})
export class Pane1 {
  @Input() id: string;
}

@Component({
  selector: 'app-root',
  template: `
    <span #abc id="1"></span>
    <pane1 #abc id="2"></pane1>
    <pane #abc id="3" *ngIf="shouldShow"></pane>
    <button (click)="show()">Show 3</button>
    <button (click)="hide()">Hide 3</button>

    <div>panes: {{serializedPanes}}</div> 
  `,
})
export class ViewChildrenComp implements AfterViewInit {
  @ViewChildren('abc') panes: QueryList<ElementRef>;
  serializedPanes: string = '';

  shouldShow = false;

  show() { this.shouldShow = true; }
  hide() { this.shouldShow = false; }

  ngAfterViewInit() {
    this.calculateSerializedPanes();
    this.panes.changes.subscribe(
      (r) => {
      this.calculateSerializedPanes(); 
    });
  }

  calculateSerializedPanes() {
    setTimeout(
      () => {
        this.serializedPanes = this.panes.map(p => p.nativeElement.id).join(', '); 
      }, 0);
  }
}

Question:
1. Whether I can define template reference variables with same name in template?
2. How to query multiple elements using the same selector, not defining names individually?


Solution

  • You can't define template reference variables with the same name in one template.

    You can only define it in different templates including EmbeddedViewTemplate, i.e.

    <div #abc>
      <ng-template #abc>
        <ng-template>
          <div #abc></div>
        </ng-template>
      </ng-template>
    </div>
    

    should work

    or

    <div #abc>
      <ng-template #abc>
        <div #abc></div>
      </ng-template>
      <ng-template #abc>
        <div #abc></div>
      </ng-template>
    </div>
    

    should also work

    How to query multiple elements using the same selector, not defining names individually?

    You can define directive like

    @Directive({selector: '[id]'}) 
    export class Abc {
      constructor(public elRef: ElementRef) {}
    }
    

    Selector can be [abc] then you need to add abc attribute to all elements

    <span abc id="1"></span>
    <pane1 abc id="2"></pane1>
    <pane abc id="3" *ngIf="shouldShow"></pane>
    

    But since you have already defined id i use it as selector

    After that you can use Abc directive mentioned above as selector for @ViewChildren

    @ViewChildren(Abc) panes: QueryList<ElementRef>;
    
    this.serializedPanes = this.panes.map(p => p.elRef.nativeElement.id).join(', '); 
    

    Plunker Example

    But there is some trick that can help us to have one variable several times. Just wrap your elements into another element i.e.

     <div>
       <span #abc id="1"></span>
       <pane1 #abc id="2"></pane1>
       <pane #abc id="3" *ngIf="shouldShow"></pane>
     </div>
    

    or

    <ng-container>
      <span #abc id="1"></span>
      <pane1 #abc id="2"></pane1>
      <pane #abc id="3" *ngIf="shouldShow"></pane>
    </ng-container>
    

    Plunker Example