Conditional duplicate templateref in ng-content with selector
Asked Answered
A

1

1

I have a component which toggles the component's template based on client device size. Component code is:

import {Component} from '@angular/core';
import {BreakpointObserver, Breakpoints} from '@angular/cdk/layout';

@Component({
    selector: 'ui-switcher',
    template: `
        <ng-content *ngIf="isSmall" select="mobile"></ng-content>
        <ng-content *ngIf="!isSmall" select="web"></ng-content>
`
})
export class UiSwitcherComponent {
    public isSmall: boolean;

    constructor(breakpointObserver: BreakpointObserver) {
        breakpointObserver.observe([Breakpoints.Small, Breakpoints.XSmall]).subscribe(result => {
            this.isSmall = result.matches;
        });
    }    
}

I use it like this:

<ui-switcher>
    <web>
        <!-- some commented details -->
        <input class="form-control mr-2" #searchInput 
        type="text" (keyup)="this.search(searchInput.value)">
    </web>

    <mobile>
        <!-- some commented details -->
        <input class="form-control" #searchInput 
        type="text" (keyup)="this.search(searchInput.value)">
    </mobile>
</ui-switcher>

In the mobile size, everything works correctly but in desktop size the value passed to search(value) function is always an empty string.

When I debug the app, it seems that #searchInput templateref is not working correctly (value of the element it refers to is always empty).

Why templateref doesn't work correctly?

Anni answered 28/4, 2018 at 7:12 Comment(3)
I don't believe you can have two elements with the same template ref name, the angular template compiler will always take the first element and ignore the 2ndExult
But now it seems that the first is skipped, because in mobile everything works correctlyAnni
I don't know how it should behave, this is undocumented behavior at best...Exult
P
7

In angular template reference variables should be unique per view.

Views can be two types View and EmbeddedView. Templates, that we write within structural directives(inside ng-template tag or *ngFor), represent embedded views. This way we can have the same name of template reference variable in different ng-templates.

For an example see

Let's imagine we have AppComponent and wrote in template:

<ui-switcher>
    <web>
        <!-- some commented details -->
        <input class="form-control mr-2" #searchInput 
        type="text" (keyup)="this.search(searchInput.value)">
    </web>

    <mobile>
        <!-- some commented details -->
        <input class="form-control" #searchInput 
        type="text" (keyup)="this.search(searchInput.value)">
    </mobile>
</ui-switcher>

Angular treats it as one AppComponentView because there is no any structural directives in this template. Both inputs belong to the same view.

Now when Angular compiler parses this template it creates one ViewBuilder per view with refNodeIndices property:

private refNodeIndices: {[refName: string]: number} = Object.create(null);

that holds all references in current template.

Let's reproduce your case: enter image description here

We can see that second template reference variable overrides previous.

And as result Angular handles click event on the same element:

enter image description here

Pettit answered 28/4, 2018 at 20:37 Comment(2)
Thanks for your detailed explanation. There is a structural component in ui-switcher component. Is there any approach to use same templateref in <web> and <mobile> sections?Anni
But <web> and <mobile> are not components, they are just selectors of ng-content inside ui-switcher componentAnni

© 2022 - 2024 — McMap. All rights reserved.