Home > Software design >  How to bind to angular template reference variable?
How to bind to angular template reference variable?

Time:10-26

I have old code that looks something like this:

<as-split unit="pixel" #mainViewSplit  direction="horizontal" 
   gutterSize="2">
  <as-split-area #propertiesContentPanel [size]="(propertiesWidth | async)" 
    [minSize]="350" maxSize="{{propertiesMinimized? minPanelWidth : '*'}}" 
    *ngIf="(propertiesSplitVisible | async)" [order]="splitOrder.properties">
    <app-properties-content-panel></app-properties-content-panel>
  </as-split-area>
  <as-split-area #someContentPanel [size]="(someWidth | async)" [minSize]="350"
    maxSize="{{someMinimized? minPanelWidth : '*'}}" *ngIf="(someSplitVisible | async)"
    [order]="splitOrder.some">
    <app-some-content-panel></app-some-content-some>
  </as-split-area>
</as-split>

In the above html there's as-split-component that contains 2 as-split-area directives (https://angular-split.github.io/documentation). Also in the html above there's 2 template reference variables used to reference the 2 as-split-area directives. They are

#propertiesContentPanel

and

#someContentPanel

In the class (.ts) files they are referenced like this:

@ViewChild('propertiesContentPanel') propertiesContentPanelSplit: SplitAreaDirective;
@ViewChild('someContentPanel') someContentPanel: SplitAreaDirective;

In reality there's a lot more of these as-split-areas but for simplicity only 2 are shown. I want to add all the as-split-area directives to the as-split component with ngFor. So the code would look like this:

  <as-split unit="pixel" #mainViewSplit  direction="horizontal" gutterSize="2">
    <ng-container *ngFor="let splitItem of splitData">
      <as-split-area id="splitItem.id" *ngIf="(splitItem.isVisible | async) 
        [size]="(splitItem.width | async)" [minSize]="splitItem.minSize" 
        maxSize="{{splitItem.isMinimized? minPanelWidth : '*'}}" [order]="splitItem.order">
         <ng-container *ngTemplateOutlet="splitItem.template"></ng-container>
      </as-split-area>
    </ng-container>
  </as-split>

Above all the templates and related .ts code used in

<ng-container *ngTemplateOutlet="splitItem.template"></ng-container>

are omitted for simplicity.

Everything else works but I cannot now reference the as-split-area directives in the code anymore because template reference variables are missing. How can I bind to as-split-area directives in the refactored ngFor-version so that I can still reference them in the class file with the 2 ViewChild I have (propertiesContentPanel and someContentPanel)?

I would like to do something like this:

<as-split-area #="splitItem.referenceVariable"

But this doesn't work. I have also omitted type of splitItem also from the question to avoid confusion.

CodePudding user response:

You can't dynamically define template reference variable. The feature was requested, but it was closed:

https://github.com/angular/angular/issues/33233


But you can work around this by querying all of the SplitArea directives, access its order property and add your own logic. Please note that you need to map each order to each panel. The idea is to use order as the id since we can't make any change to SplitArea directive. This way, you can give each SplitArea a separate treatment according to the order.

If you, instead, want to apply the treatment for each type (SomeContent vs. PropertiesContent vs. etcContent), please check out the Bonus section below.

example.component.ts

@Component({
 ...
})
export class ExampleComponent implements AfterViewInit {
  
  @ViewChildren(SplitAreaDirective)
  splitAreas!: QueryList<SplitAreaDirective>;

  public ngAfterViewInit() {
    
    const propertiesContentPanel = this.splitAreas.find(s => s.order === 0);

    if (propertiesContentPanel) {
       // do your logic here.
    }
  }
}

example.component.html

<as-split>
  <ng-container *ngFor="let splitItem of splitData">
    <as-split-area id="splitItem.id" 
         *ngIf="(splitItem.isVisible | async)"
         [order]="splitItem.order">
      <ng-container *ngTemplateOutlet="splitItem.template"></ng-container>
    </as-split-area>
  </ng-container>
</as-split>

Bonus:

You can also create a custom directive that accepts a string literal type, append it to the SplitArea directive, query it with @ViewChildren, then do your logic according to the string literal type.

SplitData type also need to include a new property called templateType and is of SplitAreaType type.

myTemplateRef.directive.ts

export type SplitAreaType = 'SomeContent' | 'PropertiesContent'; 

@Directive({
  selector: '[appMyTemplateRef]'
})
export class MyTemplateRefDirective {

  public get SplitArea() {
    return this._splitAreaRef;
  }

  constructor(private _splitAreaRef:SplitAreaDirective) {}
  
  @Input()
  public templateType: SplitAreaType = 'SomeContent';
}

example1.component.html

<as-split>
  <ng-container *ngFor="let splitItem of splitData">
    <as-split-area appMyTemplateRef [templateType]="splitItem.templateType">
      <ng-container *ngTemplateOutlet="splitItem.template"></ng-container>
    </as-split-area>
  </ng-container>
</as-split>

example1.component.ts

@Component({
 ...
})
export class Example1Component implements AfterViewInit {
  
  @ViewChildren(MyTemplateRefDirective)
  myTemplateRefs!: QueryList<MyTemplateRefDirective>;

  public ngAfterViewInit() {
    
    this.myTemplateRefs.forEach(t => {

      if(t.templateType === 'SomeContent') {
         // do something with the SplitArea here.
         t.splitArea.gutterSize = 200;
      } else {
     
      }

    });
  }
}
  • Related