Ruan Mendes
Ruan Mendes

Reputation: 92304

How to get reference to unknown children elements using @ViewChildren

I'm using a third party library that lets me create tabsets. This is the code I'm using to create a simple tabset

<clr-tabs           
   (clrTabsCurrentTabContentChanged)="onTabContentActivated($event)" >

  <clr-tab-link>Firewall</clr-tab-link>
  <clr-tab-link>DHCP</clr-tab-link>

  <clr-tab-content>
      <vcd-firewall-tab></vcd-firewall-tab>
  </clr-tab-content>

  <clr-tab-content>
     <vcd-dhcp-tab></vcd-dhcp-tab>
  </clr-tab-content>
</clr-tabs>

I have hooked up an event that will tell me when a tab was selected, and I would like to call the loadData()method on <vcd-firewall-tab> and <vcd-dhcp-tab>.

The clrTabsCurrentTabContentChanged will give me a reference to the clr-tab-content that was selected but I would like to access its first child and call loadData() to implement lazy loading.

I think I can use @QueryChildren annotation, except that I have to specify the type of element to query for. The problem is that in this case, I don't know the type, it could be <vcd-firewall-tab>, <vcd-dhcp-tab> or the many other tabs that we have and I don't want to add custom code every time I add a new tab.

I was hoping to be able to do something like this from my event handler (but that doesn't exist

onTabContentActivated(tabContent: TabContent){
    (tabContent.query(':first-child') as CanLoadData).loadData();
}

I'm open to any suggestions, I thought maybe I could match the index of the tab to something like @QueryChildren('clr-tab-content > *'), assuming there's only one child under each tab.

Upvotes: 1

Views: 1725

Answers (2)

Ruan Mendes
Ruan Mendes

Reputation: 92304

The real answer to the question, as mentioned by Günter, is that it can't be done. You cannot query unless you know the type ahead of time.

I hacked a solution to this particular problem minimizing the amount of code to add for each tab by doing two things

  • Creating a directive that can be added to the tabset, which will fire a DOM event on the child element of <clr-tab-contents>.
  • Creating a function that does the hookup of the DOM event and calls the loadData() automatically when the tab is activated

Here's a minimal example that implements that. It's very crude, my real code takes care of other corner cases, but I didn't want to add noise to the solution.

<!-- One piece of glue per tabset -->
<clr-tabs vcd-lazy-tab-loader>
  <clr-tab-link>Firewall</clr-tab-link>
  <clr-tab-link>DHCP</clr-tab-link>

  <clr-tab-content>
      <vcd-firewall-tab></vcd-firewall-tab>
  </clr-tab-content>

  <clr-tab-content>
     <vcd-dhcp-tab></vcd-dhcp-tab>
  </clr-tab-content>
</clr-tabs>

// lazy-tab-loader.directive.ts
@Directive({
  selector: '[vcd-lazy-tab-loader]'
})

export class VcdLazyTabLoader {  
  constructor(@Inject(forwardRef(() => Tabs)) private tabSet: Tabs,
              private el: ElementRef) {
    tabSet.currentTabIndexChanged.subscribe((tabIndex) => {
      // Not very pretty, we'll find a nicer way later
      // It relies on the internal HTML structure of clr-tab-content
      const element = this.el.nativeElement.querySelectorAll(`clr-tab-content`)[tabIndex]                      
            .firstElementChild.firstElementChild;
      element.dispatchEvent(new CustomEvent("vcd-activated", {}));
    });
  }
}

export interface CanLoadData {
  loadData(): void;
}

export function setupLazyLoader(el: HTMLElement, dataLoader: CanLoadData) {
  el.addEventListener('vcd-activated', () => {
    dataLoader.loadData();
  });
}

// firewall-tab.component.ts (AND dhcp-tab.components.ts)
@Component(...)
// First piece of glue (implement CanLoadData) for a tab
class FirewallTab implements CanLoadData {

  constructor(private firewallService: FirewallService,
              private el: ElementRef) {
      // Second piece of glue, per tab
      // Constructor is not the best place, it's here just to avoid extra code
      setupLazyLoader(el.nativeElement, this);
  }
  loadData () {
    this.service.getRules().subscribe((data)=> this.rules = rules);
  }
}

Upvotes: 1

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 657731

There are only two ways supported

  • passing a component or directive type
  • passing the name of a template variable

For other requirements you can inject ElementRef and access the DOM directly using ElementRef.nativeElement.... but this way you only get the element not components or directives.

Upvotes: 2

Related Questions