-2

How do you bind to an EventEmiter or Subject in a child component when that component is contained within an *ngIf ?

I've got a component as a @ViewChild() inside an *ngIf

@ViewChild('foo')
foo: FooComponent;
<div *ngIf="someToggle">
   <app-foo #foo/>
</div>

My FooComponent has a Subject to notify the parent component when something changes:

export class FooComponent
{
    @Output()
    somethingChanged = new Subject();
}

Now, in my parent component, when I try to subscribe to the somethingChanged subject I get an error because this.foo is undefined. In the example below there are two places I could subscribe, in the ngAfterViewInit which would be the place to do it if the child component wasn't in an *ngIf, but it is so it's undefined at this point.

The second place to subscribe is in my showTheFoo() method which flips the switch to show the component, but the viewChild is still undefined immediately after that because angular hasn't processed the change yet, so how do I subscribe after the change has been processed and the component is added to the DOM?

export class ParentComponent implements AfterViewInit
{
    @ViewChild('foo')
    foo: FooComponent;

    someToggle = false;


    ngAfterViewInit(): void {
        // this.foo === undefined.
        // this makes sense because it's hidden
    }

    showTheFoo()
    {
        this.someToggle = true;
        
        // this.foo is still undefined.
        // this also makes sense, because although we've flipped the switch
        // angular hasn't processed the change, 
        // but how do I subscribe after it's been shown?
        this.foo.somethingChanged.subscribe(()=>{});

    }
}

Greg B
  • 14,597
  • 18
  • 87
  • 141
  • Does this help? https://stackoverflow.com/questions/39366981/viewchild-in-ngif – MikeOne Jun 29 '21 at 13:43
  • @MikeOne I've tried that and the outcome is the same. I did think about putting the subscription in the setter but that seemed hacky and un-angular. I think Michael D's answer below is the way to go but I'll confirm that shortly. – Greg B Jun 30 '21 at 08:12

1 Answers1

1

I'd say it's better to use EventEmitter instead of a Subject here. It's an Angular specific extension of the RxJS Subject, so you aren't very far from what you're attempting now.

With EventEmitter you could emit instead of using next and capture the events similar to JS event binding. If you don't need the reference to the child element from the DOM for anything else, then the @ViewChild could be ignored.

Try the following

Child controller (*.ts)

export class FooComponent {
    @Output()
    somethingChanged = new EventEmitter<any>();

    someFunction() {
        this.somethingChanged.emit('some value');
    }
}

Parent template (*.html)

<div *ngIf="someToggle">
   <app-foo (somethingChanged)="onSomethingChanged($event)"><app-foo/>
</div>

Parent controller (*.ts)

showTheFoo() {    
    this.someToggle = true;
}

onSomethingChanged(data: any) {
    console.log(data);    // <-- data from the child component
}
ruth
  • 29,535
  • 4
  • 30
  • 57