How to wait completing of event subscriber with Observable call

Viewed 16179

I have two components. Child component has an event (EventEmitter). Parent component has handler with long time method.

I need to wait for finishing of executing of my method (longTimeMethod) before further actions. Method continues near 5 seconds.

I want to reach execution of all steps consistently (1 -> 2 -> 3 -> 4 -> 5).

But now sequence is 1 -> 2 -> 4 -> 5 -> 3

@Component({
    selector: 'child-selector',
    template: '<>********</>'
})
export class ChildComponent{
    @Output() childEvent = new EventEmitter();

    someFunction() {
       // Step 1: event emit

        this.childEvent.emit();
        // Step 5: Subscribers execution finished
        // Some code which should to reached after all event subscribers
    }
}

@Component({
    selector: 'parent-selector',
    template: '<child-selector (childEvent)="handleChildEvent()"></child-selector>'
})

export class ParentComponent{
       // Step 2: handler start 
    handleChildEvent() {
        // this.longTimeMethod return Observable<void>
        this.myService.longTimeMethod()
            .subscribe(() => {
                // Step 3: Method executing finished
            });
    // Step 4: Another code
    }
}

I tried to use async-await approach:

async handleChildEvent() {
    // this.longTimeMethod return Observable<void>
    await this.myService.longTimeMethod().toPromise()
        .then(() => {
            // Step 3: Method executing finished
        });
       // Step 4: Another code
}

Sequence changed but it's still not correct: 1 -> 2 -> 5 -> 3 -> 4. How to accomplish correct behavior?

3 Answers

To delay step5 emit a callback from child.

export class ChildComponent{
  @Output() childEvent = new EventEmitter();

  someFunction() {
    // Step 1: event emit
    this.childEvent.emit(execStep5);
  }

  execStep5() {
    // Step 5: Subscribers execution finished
    // Some code which should to reached after all event subscribers
  }
}

Execute execStep5 in the parent when ready

handleChildEvent(execStep5) {
  ...
// Step 4: Another code
execStep5();

For step3 & step4, change subscribe to map, then subscribe and execute step4 and the callback.
Don't use await/async, since rxjs already has these tools - don't mix two methods.

export class ParentComponent{
  // Step 2: handler start 
  handleChildEvent(execStep5) {
    // this.longTimeMethod return Observable<void>
    this.myService.longTimeMethod()
      .map(() => {
        // Step 3: Method executing finished
        return Observable.of('done');
      })
      .subscribe(() => {
        // Step 4: Another code
        execStep5();
      });
  }
}

There is probably even shorter code to do this, but this should work.

I have tried this solution in similar situations but not exactly the same. Since your problem now only lies in step 5 not being in order, you can wrap step 5 in a setTimeout function with 0 wait. This will cause that step 5 be a scheduled task.

    setTimeout(_ => {/*step 5 code*/});

I face it when trying in a web component standalone app to call to some function in a hosted app (angular, in my case).

So using the EventEmitter class from Angular or from the Stencil (in the web component app) wasn't an option, as it's 2 different framework-based apps.

and basically, Event Emitting is an observable design pattern, not intended for synchronous promise-based actions.

So here is my solution:

Child component / app:

@Component({...})
export class ChildComponent {   
  @Prop() parentFunction: () => Promise<void>; 
  ....
  await this.parentFunction();
}

Parent component / app:

in HTML:

<child [parentFunction]='this.onParentFunction.bind(this)'></child>

in Ts file:

 async onParentFunction() {
   await this.sessionService.refreshSession();    
 }
Related