Angular 4 - rxjs BehaviorSubject usage in Service

coder87 picture coder87 · Jun 15, 2017 · Viewed 8.5k times · Source

My Service code looks like below -

DataService

@Injectable()
export class DataService {
...
private serviceRequestDtoSource = new BehaviorSubject<ServiceRequestDto>(null);
serviceRequestDto$ = this.serviceRequestDtoSource.asObservable();
...
getAccountInfo(serviceRequestDto : ServiceRequestDto){
    let body = JSON.stringify(serviceRequestDto);
    let headers = new Headers({
        'Content-Type': 'application/json'
    });
    let options = new RequestOptions({ headers: headers });
    console.log("In data service.getAccountInfo");
    this.http
        .post(this.clientAccountInfoURL, body, options)
        .map((response : Response) => { return <AccountDto[]> response.json().accountDtoList})
        .do(data=> console.log('All :'+ JSON.stringify(data)))
        .subscribe( response => { 
                             this.accountList = response; 
                             this.serviceRequestDto.accountDtoList = this.accountList;
                             this.serviceRequestDtoSource.next(serviceRequestDto);
                         },
                         error => this.errorMessage = <any>error);
}

Search Component (which hits above service and also subscribes) looks like below -

onSearch(value) {  
...
this.dataService.getAccountInfo(this.serviceRequestDto); //service call
this.subscription = this.dataService.serviceRequestDto$
        .subscribe( (serviceRequestDtoValue : ServiceRequestDto) => {
        // Control doesn't come here..
         this.serviceRequestDto = serviceRequestDtoValue;
         console.log('search.serviceRequestDto.length:'+this.serviceRequestDto.accountDtoList.length);
         console.log('search.serviceRequestDto.accountDtoList.name:'+this.serviceRequestDto.accountDtoList[0].name);
    });

As mentioned above, control doesn't come here inside observing component's subscribe method as none of the logs are printed and also I don't get any error in console.Not sure if I'm using BehaviorSubject in proper way.

I tried to follow following links -

Angular 2 - Behavior Subject vs Observable? -> Not entering subscribe as mentioned here.
http-observables -> When I tried this way , it does enter subscribe but then throws error for logs -

cannot read property xyz of null as component logs gets printed even before i get data from service.Service logs got printed later after i got above mentioned error.

My intention is not to subscribe to the BehaviorSubject from SearchComponent itself which hits the service. I'm just trying to test if i get the values from service in any component.I believe it's as good as subscribing from any other component which needs serviceRequestDto values as syntax for subscribing remains same from all components.

Update 1:

I tried to use ReplaySubject(1) as suggested by Brandon, but still i was getting below error while subscribing to subject in component.

TypeError: Cannot read property 'name' of undefined(…)

My updated service code looks like this now -

serviceRequestDtoSource = new ReplaySubject<ServiceRequestDto>(1);
getAccountInfo(serviceRequestDto : ServiceRequestDto){
 ...
 //Logic same as earlier
 //I'm getting all values from service here in the logs I print

 }

Please note earlier I was using following in service code -

private serviceRequestDtoSource = new BehaviorSubject<ServiceRequestDto>(null);
serviceRequestDto$ = this.serviceRequestDtoSource.asObservable();

My updated Search Component code looks like this -

this.dataService.getAccountInfo(this.serviceRequestDto);
this.dataService.serviceRequestDtoSource.subscribe((serviceRequestDtoValue : ServiceRequestDto) => {
this.serviceRequestDto = serviceRequestDtoValue;
console.log('search.serviceRequestDto.length:'+this.serviceRequestDto.accountDtoList.length);           
// prints 'search.serviceRequestDto.length:0'
console.log('search.serviceRequestDto.accountDtoList   name:'+this.serviceRequestDto.accountDtoList[0].name); // throws 'vendor.bundle.js:5764 ERROR TypeError: Cannot read property 'name' of undefined(…)' error
});

The way earlier component code was subscribing to subject was different and control never went inside component's subscribe though there was no error.

My initial code is different from what I've posted now and it was based on following link - delegation-eventemitter-or-observable-in-angular2

Answer

Picci picture Picci · Jun 15, 2017

I think this depends on the fact that BehaviorSubject emits always its first value as soon as it is subscribed (see marble diagram here http://reactivex.io/RxJava/javadoc/rx/subjects/BehaviorSubject.html).

Now the first value emitted in your case is null, i.e. The value you passed in the constructor of BehaviorSubject.

One way to overcome the problem is to use check for not null values (now that you know that there may be a legitimate one) or use the operator skip() to skip always the first emitted value.