Subscribe to one observable twice from a component doesn't work

Issue

I got several components subscribing to my data service and they are all working fine. But in one of my components, I try to subscribe twice (inside ngOnInit and ngAfterViewInit) but this doesn’t work. Here is the component:

ngOnInit() {
    this.dataService.data$.pipe(first()).subscribe(subscribeToData => {
        this.title = this.dataService.getData("...");
            this.anotherService.getData
                .subscribe(another => {
                    this.data = data;
                },
                ...
        });
    }

ngAfterViewInit() {
    this.dataService.data$.pipe(first()).subscribe(subscribeToData => {
        let options = {
            data: {
            }
            ...
            {
            title: this.dataService.getData("...");
            },
            ...

        };
        ...

    });
}

If I remove subscribe from ngOnInit then ngAfterViewInit works fine, else it fails. So is there a way to subscribe two or more times from within the same component at the same time?

Here is the data service:

private dataSource = new ReplaySubject(1);
data$ = this.dataSource.asObservable();

loadData(... : void) {
    if (sessionStorage["data"] == null {
        this.http.request(...)
        .map((response: Response) => response.json()).subscribe(data => {
            ...
            sessionStorage.setItem("data", JSON.stringify(this.data));
            this.dataSource.next(this.data);
            ...
        });
    } else {
        this.dataSource.next(this.data);
    }
}

getData(... : string){
    ...
}

Solution

There are no problems with double subscribe in your code – probably you are facing some asynchronous code issues. The browser is fast enough to quickly go through component’s lifecycle and to quickly call ngOnInit and ngAfterViewInit. Both of them will be executed almost simultaneously and lightning fast – definitely faster than a http call. In this case, in your ngOnInit‘s subscribe you have another call that might be executed after the ngAfterViewInit (although I’m not sure).

Here is an example that shows that double subscribe in a single component works:
https://stackblitz.com/edit/double-subscribe?file=src/app/app.component.ts

Try to refactor your logic to be more consecutive: if your ngAfterViewInit must be executed after all the asynchronous code in ngOnInit is done – store the result of the ngOnInit‘s chain somewhere in a variable; if your ngAfterViewInit does not care about the ngOnInit try to avoid accessing the same variables, especially this.data.

Also try to avoid nested subscribes – they can be replaced with switchMap/flatMap:

ngOnInit() {
    this.dataService.data$.pipe(
      first(),
      tap(data => this.title = this.dataService.getData(data)), // note that this should be synchronous
      switchMap(data => {
        // another asynchronous call here
        return this.anotherService.getData(data)
      })
    ).subscribe(finalData => {
        this.data = finalData
    }

To refactor your ngAfterViewInit to be executed after ngOnInit do the following:

onInitData$: Observable<any>;

ngOnInit() {
  this.onInitData$ = this.dataService.data$.pipe(
      first(),
      tap(data => this.title = this.dataService.getData(data)), // note that this should be synchronous
      switchMap(data => {
        // another asynchronous call here
        return this.anotherService.getData(data)
      }),
      shareReplay(1) // shareReplay(1) is important to avoid doing double http requests per subscribe
    );
  this.onInitData$.subscribe(data => console.log('data from ngOnInit', data));
}

ngAfterViewInit() {
  this.onInitData$.pipe(switchMap(thatData) => {
     // will be executed only AFTER the ngOnInit is done
     return this.dataService.data$.pipe(first()).subscribe(subscribeToData => {
        let options = {
            data: {
            }
            ...
            {
            title: this.dataService.getData("...");
            },
            ...

        };
        ...

    });
  }).subscribe(dataFromAfterViewInit => {})
}

In general, you’d want to think why do you even need ngAfterViewInit? What do you want to achieve by splitting those calls between onInit/afterViewInit? Why do they access the same data in a component?

Answered By – Yevhenii Dovhaniuk

Answer Checked By – Timothy Miller (AngularFixing Admin)

Leave a Reply

Your email address will not be published.