How to properly multicast result of HttpClient request to several components in Angular?

angular httpclient post example
angular 2 http get example
angular/common/http
httpclientmodule
behaviorsubject angular 8 example
behaviorsubject in angular
observable in angular 8
subject in angular 8

I'm trying to send the result of HttpClient post requests multiple components in my Angular app. I'm using a Subject and calling its next() method whenever a new post request is successfully executed. Each component subscribes to the service's Subject.

The faulty services is defined as

@Injectable()
export class BuildingDataService {

  public response: Subject<object> = new Subject<object>();

  constructor (private http: HttpClient) { }

  fetchBuildingData(location) {
    ...

    this.http.post(url, location, httpOptions).subscribe(resp => {
      this.response.next(resp);
    });
}

The components subscribe to BuildingService.response as follows

@Component({
  template: "<h1>{{buildingName}}</h1>"
  ...
})

export class SidepanelComponent implements OnInit {
  buildingName: string;

  constructor(private buildingDataService: BuildingDataService) { }

  ngOnInit() {
    this.buildingDataService.response.subscribe(resp => {
        this.buildingName = resp['buildingName'];
      });
  }

  updateBuildingInfo(location) {
    this.buildingDataService.fetchBuildingData(location);
  }
}

updateBuildingInfo is triggered by users clicking on a map.

Retrieving the data from the server and passing it to the components works: I can output the payloads to the console in each component. However, the components' templates fail to update.

After Googling and fiddling for most of today I found that this implementation does not trigger Angular's change detection. The fix is to either

  • wrap my call to next() in the service in NgZone.run(() => { this.response.next(resp); }
  • call ApplicationRef.tick() after this.title = resp['title'] in the component.

Both solutions feel like dirty hacks for such a trivial use case. There must be a better way to achieve this.

My question therefore is: what is the proper way to fetch data once and send it off to several components?

I'd furthermore like to understand why my implementation escapes Angular's change detection system.

EDIT it turns out I was initiating my call to HttpClient outside of Angular's zone hence it could not detect my changes, see my answer for more details.

One way is to get an Observable of the Subject and use it in your template using async pipe:

(building | async)?.buildingName

Also, if different components are subscribing to the service at different times, you may have to use BehaviorSubject instead of a Subject.


@Injectable()
export class BuildingDataService {
  private responseSource = new Subject<object>();
  public response = this.responseSource.asObservable()

  constructor (private http: HttpClient) { }

  fetchBuildingData(location) {
    this.http.post(url, location, httpOptions).subscribe(resp => {
      this.responseSource.next(resp);
    });
  }
}

@Component({
  template: "<h1>{{(building | async)?.buildingName}}</h1>"
  ...
})

export class SidepanelComponent implements OnInit {
  building: Observable<any>;

  constructor(private buildingDataService: DataService) {
      this.building = this.buildingDataService.response;
  }

  ngOnInit() {
  }

  updateBuildingInfo(location) {
    this.buildingDataService.fetchBuildingData(location);
  }
}

Communicating with backend services using HTTP, Modern browsers support two different APIs for making HTTP requests: the Re-​subscribing to the result of an HttpClient method call has the effect of When the server responds successfully with the newly added hero, the component adds It's defined as an RxJS Subject , which means it is a multicasting Observable  How to send an example DELETE request with Angular 9 and HttpClient.delete(). Note: We'll be using the new http client in Angular which is available from the @angular/common/http module starting with Angular 4.3+ and which replaces the old HTTP client that was available from the @angular/http package. This upgrade is not just a change in the

I think I found the issue. I briefly mention that fetchBuildingData is triggered by users clicking on a map; that map is Leaflet as provided by the ngx-leaflet module. I bind to its click event as follows

map.on('click', (event: LeafletMouseEvent) => {
  this.buildingDataService.fetchBuildingData(event.latlng);
});

The callback is, I now realise, fired outside Angular's zone. Angular therefore fails to detect the change to this.building. The solution is to bring the callback in Angular's zone through NgZone as

map.on('click', (event: LeafletMouseEvent) => {
  ngZone.run(() => {
    this.buildingDataService.fetchBuildingData(event.latlng);
  });
});

This solves the problem and every proposed solution works like a charm.

A big thank you for the quick and useful responses. They were instrumental in helping me narrow down the problem!

To be fair: this issue is mentioned in ngx-leaflet's documentation [1]. I failed to understand the implications right away as I'm still learning Angular and there is a lot to take in.

[1] https://github.com/Asymmetrik/ngx-leaflet#a-note-about-change-detection

Multicasting Observables Using RxJS Subjects in Angular, Let's say that we have two independent components that want to talk to each other. Here, let's say rxjs with angular 5 ,web dev ,angular tutorial ,web application data. Like (3) Our data model is successfully created now. Angular is a platform for building mobile and desktop web applications. Join the community of millions of developers who build compelling user interfaces with Angular.

EDIT:

As your change detection seems to struggle with whatsoever detail in your object, I have another suggestion. And as I also prefer the BehaviorSubject over a sipmple Subject I adjusted even this part of code.

1st define a wrapper object. The interesting part is, that we add a second value which definitely has to be unique compared to any other wrapper of the same kind you produce.

I usually put those classes in a models.ts which I then import wherever I use this model.

export class Wrapper {
   constructor(
       public object: Object,
       public uniqueToken: number
   ){}
}

2nd in your service use this wrapper as follows.

import { Observable } from 'rxjs/Observable'; 
import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 
import { Wrapper } from './models.ts';

@Injectable()
export class BuildingDataService {
   private response: BehaviorSubject<Wrapper> = new BehaviorSubject<Wrapper>(new Wrapper(null, 0));

   constructor (private http: HttpClient) { }

   public getResponse(): Observable<Wrapper> {
        return this.response.asObservable();
   }

   fetchBuildingData(location) {
      ...
      this.http.post(url, location, httpOptions).subscribe(resp => {

         // generate and fill the wrapper
         token: number = (new Date()).getTime();
         wrapper: Wrapper = new Wrapper(resp, token);

         // provide the wrapper
         this.response.next(wrapper);
      });
   }

Import the model.ts in all subscribing classes in order to be able to handle it properly and easily. Let your Subscribers subscribe to the getResponse()-method.

This must work. Using this wrapper + unique token technique I always solved such change detection problems eventually.

Rxjs Multiple Http Requests, You may also like: Angular 8 RxJS Multiple HTTP Request using the forkJoin Using the HttpClient to Make HTTP Requests. x is in long term support. js file maps to that Here we'll go over the differences, why it matters, and how to properly RxJS multicast operators, better known as sharing operators, are probably the  With a multicasting observable, you don't register multiple listeners on the document, but instead re-use the first listener and send values out to each subscriber. When creating an observable you should determine how you want that observable to be used and whether or not you want to multicast its values.

The code of sabith is wrong (but the idea is the correct)

  //declare a source as Subject
  private responseSource = new Subject<any>(); //<--change object by any

  public response = this.responseSource.asObservable() //<--use"this"

  constructor (private http: HttpClient) { }

  fetchBuildingData(location) {
    this.http.post(url, location, httpOptions).subscribe(resp => {
      this.responseSource.next(resp);
    });
  }

Must be work

Another idea is simply using a getter in your component

//In your service you make a variable "data"
data:any
fetchBuildingData(location) {
    this.http.post(url, location, httpOptions).subscribe(resp => {
      this.data=resp
    });
  }

//in yours components

get data()
{
     return BuildingDataService.data
}

Angular pitfall: Multiple HTTP requests with RxJS and observable , The app was using the HttpClient introduced in Angular 4.3, which enables direct view raw blog-pitfall-component.html hosted with ❤ by GitHub Each async pipe triggers a new HTTP request, because each result$ | async  Sometimes you need to clear the request body rather than replace it. If you set the cloned request body to undefined, Angular assumes you intend to leave the body as is. That is not what you want. If you set the cloned request body to null, Angular knows you intend to clear the request body.

The standard way to do this in an angular app is a getter in the service class.

get data()
{
     return data;
}

Why are you trying to complicate matters? What are the benefits, and if there are benefits they will overcome the drawbacks?

48 answers on StackOverflow to the most popular Angular questions, @Directive v/s @Component in Angular; Angular HTTP GET with TypeScript error An Observable is like a Stream (in many languages) and allows to pass zero or If the result of an HTTP request to a server or some other expensive async class is instantiated and ensures proper initialization of fields in the class and its  Angular 4.3 was released with the addition of a new service to make HTTP requests: HttpClient. The old Http service is still available and the main goal of HttpClient is to provide a simpler API out of the box.

Advanced caching with RxJS, In an Angular application, we typically perform HTTP requests through the HttpClient that comes with the HttpClientModule . Our app has two components where navigating from component A to B should prefer requesting B's This is called multicasting and defines the foundation for our simple cache. Angular 4.3 brings us a new easier way to handle http requests with the HttpClient library. It’s available under a new name to avoid causing breaking changes with the current Http library. HttpClient also gives us advanced functionality like the ability to listen for progress events and interceptors to monitor or modify requests or responses.

Reducing HTTP requests within an angular 2 app, If like me you're taking your first steps with angular 2 and the daunting new world and to properly sum up where I am on that journey, here's a baby giraffe. Our service returns an observable to our component which we then consume HTTP service is cold and this is the reason for our multiple requests. Explaining the proper way to cache HTTP calls using Angular and RxJS in a for loop or in *ngFor or just have several components using it within the same template? It turns out that our cache

Angular/RxJS 6: How to prevent duplicate HTTP requests?, Publish allows us to multicast the result, and refCount allows us to close the subscription have the expect result: One HTTP call even if multiple calls are made to the service. When I subscribe (without the AsyncPipe) to a HTTP request in a component I could How to correctly use Observable and Angular async pipe? In Angular 4.3 the new HttpClientModule has been introduced. This new module is available in package @angular/common/http and a complete re-implementation of the former HttpModule. The new HttpClient service is included in HttpClientModule and can be used to initiate HTTP request and process responses within your application.

Comments
  • Thanks for the quick reply! Unfortunately, the async pipe produces the same effect: it only works when I trigger a manual change detection preferably through NgZone or else it lags one tick i.e. it displays the previous value instead of the current. I'll research BehaviourSubject, thanks for pointing it out.
  • Also make sure you are unsubscribing the subscriptions made manually. I don't really see any need for manual change detection in this scenario.
  • Also, you are injecting DataService instead of BuildingDataService is that a typo?
  • That's a typo; fixed now. I'm reaching for manual change detection since the template does not update upon arrival of a new value through the Subject (or BehaviourSubject for that matter, see my comments in DiabolicWord's answer.
  • I found the issue: I'm triggering fetchBuildingData from a callback outside of Angular's zone. Thanks a lot for the different approaches; they helped me zone in on the problem. Your remark that you "don't really see any need for manual change detection in this scenario" somehow reminded me to look at how I call fetchBuildingData.
  • I was unsure what to do as the other answers directly address the question about multicasting whereas my solution fixes something completely unrelated. I'd hate someone to Google for multicasting and end up on a page describing how to fix something with Leaflet. Still unsure what to, so please advise.
  • Well, I think that makes sense too. I guess you can leave this as is.
  • Thanks for the quick reply! The problem, unfortunately, persists.
  • Hi, I understand. I updated my post. I hope this eventually works for you.
  • Thanks for this creative solution! Unfortunately it does not work either. I get the BehaviourSubject's initial value but nothing afterwards unless I wrap it in NgZone.run(). Seeing that your solution is very similar to @sabithpocker's I'm starting to think there is something wrong with my Angular installation (5.2.10). Alternatively, Angular does not do change detection in callbacks: I tried calling next() in the service every second through setInterval but it also only works by manually triggering a change detection.
  • I found the issue: I'm triggering fetchBuildingData from a callback outside of Angular's zone. See my solution. Thanks a million for the help! By providing a number of different solutions you helped me verify that the Subject/Observable part is correct.
  • Yes, the getter works beautifully. I wasn't aware tha they are bound to variables and automatically receive new values.
  • No it isn't exactly this way. Angular is not magic, it's must take acount the changes in the application (a click, a change in one input...). When a change happens, it look for all the "things" that can change and show the new values. Using a getter "force" to Angular to look for the new value.
  • Thanks for the clarification. How does the "forcing" work? Is it something that's done in Angular or is it a property of JavaScript itself?