How can I get ViewChildren of a Directive when it is inside an ngIf with async pipe

Viewed 160

I have a component that renders its content based on an observable with async pipe.

<div *ngIf="equipment$ | async as equipment">
  <button myDirective></button>
</div>

In the .ts file, I'm getting all of the MyDirective children. There can be any number.

@ViewChildren(MyDirective) actions!: QueryList<MyDirective>;

And then I want to access it in the ngAfterViewInit.

ngAfterViewInit(): void {
  console.log(this.actions)
}

this logs a QueryList with 0 items. If I put the directive outside of my async pipe it all works perfectly:

<div *ngIf="equipment$ | async as equipment">
  // Some HTML here
</div>
<button myDirective></button>

Is there any way I can retrieve the directives nested under the async pipe? I need to keep the ngIf because I have inputs bound to the equipment variable (which contains properties).

EDIT:

So, after thinking and thinking, I realized that what I'm trying to do here is to re-create NgRx. After all this time thinking it was too complicated, I now understand all the benefits it brings and the problems it solves. I'm gonna go ahead and try implementing it!

3 Answers

you need to add static: false since it's a dynamically rendered component:

@ViewChildren(MyDirective, { static: false }) actions!: QueryList<MyDirective>;

but you won't be sure that the directive will be available in ngAfterView init. because equipment$can take some time...

maybe you can add a tap() in the observable and do your console.log() there istead

You can create a hide directive, let me expain it:

    @Input() hide: boolean;

constructor(private renderer: Renderer2, private elRef: ElementRef) {
}

ngOnChanges() {
    if (this.hide) {
        this.renderer.setStyle(this.elRef.nativeElement, 'visibility', 'hidden');
    } else {
        this.renderer.setStyle(this.elRef.nativeElement, 'visibility', 'visible');
    }
}

since the hide directive will not display the element on the dom but it can still be accesible

Simon, You can not access to an element that it's not in the screen when you execute an instruction.

Of course you can not access in afterViewInit because the condition is yet false. So, you has two aproach

  1. Create an observable based in yours and use rxjs operator tap

    equipmentDone$=this.equipment$.pipe(
       tap((res:any)=>{
          ..here you has yet your this.actions...
          //it's possible, I'm not pretty sure you need enclosed in a setTimeout
          setTimeout(()=>{
              ..here sure, sure you has this.actions..
          })
       })
    )
    

    And use

    <div *ngIf="equipmentDone$ | async as equipment">
      <button myDirective></button>
    </div>
    
  2. Subscribe to the observable changes of the QueryList

    ngAfterViewInit(){
      this.actions.changes.subscribe(res=>{
         ..here you has your actions..
      })
    }
    
Related