Component BehaviorSubject subscriber does not rece

2019-09-02 20:44发布

问题:

I'm trying to understand why this BehaviorSubject subscriber does not receive emitted values.

In my component ngOnInit I setup a subscriber to a service method that returns a reference to a BehaviorSubject:

 // project-detail.component.ts
 ngOnInit() {
  this.activatedRoute.params.subscribe((data: Params)  => {
    if (data && data.id) {
      this.projectService.getProject(data.id).subscribe(project => {
        console.log(project);
      });
    }
  });
 }

In the service, I make an http request and then push the response data into the BehaviorSubject's next method:

 // project.service.ts
 import { HttpClient } from '@angular/common/http';  
 import { BehaviorSubject, Observable } from 'rxjs';

 @Injectable()
 export class ProjectService {
 private apiUrl = envLocal.apiJsonServerUrl;
 private currentProjectObs$ = new BehaviorSubject<IProject>(null);

 constructor(private http: HttpClient) {
 }

 getProject(id: number = 0) {
  if (id) {
    this.http.get(`${this.apiUrl}/projects/${id}`)
      .subscribe(project => {
        this.currentProjectObs$.next(project);
      });
  }
  return this.currentProjectObs$;
 }

My understanding is that the subscriber in my component's ngOnInit should listen for changes from the BehaviorSubject reference returned from getProject (i.e. currentProjectObs$).

When ngOnInit first runs it calls getProject and since the http call inside getProject is asynchronous currentProjectObs$ is returned with the initial value of null. But once the http get request completes, currentProjectObs$.next(project) gets invoked and, I would expect, the subscriber in ngOnInit to receive the newly emitted value - but this doesn't happen.

I'd like to understand what's going on here and why the subscriber doesn't receive the async value from the http request, and how to fix it so that it would.

回答1:

When you use below function it will again return null value because it will not wait for api call. and if you are using Behavior subject than you can directly take value from behavior subject, you not need to return it.

getProject(id: number = 0) {
  if (id) {
    this.http.get(`${this.apiUrl}/projects/${id}`)
      .subscribe(project => {
        this.currentProjectObs$.next(project);
      });
  }
  return this.currentProjectObs$;
}

You can do it with these two types. Do Change it to Something like this :

1. Without use of behavior subject.

// Your Service 
getProject(id: number = 0): Observable<any> {
    if (id) {
       return this.http.get(`${this.apiUrl}/projects/${id}`)
          .pipe(map(project => {
               return project;
               // this.currentProjectObs$.next(project);
          });
    }
    return Observable.of(null);
};

// Your Component
ngOnInit() {
  this.activatedRoute.params.subscribe((data: Params)  => {
    if (data && data.id) {
      this.projectService.getProject(data.id)
        .subscribe(project => {
            console.log(project);
      });
    }
  });
}

2. Use With Behavior subject

// Your service

public currentProjectObs$ = new BehaviorSubject<IProject>(null);

getProject(id: number = 0) {
  if (id) {
    this.http.get(`${this.apiUrl}/projects/${id}`)
      .pipe(map(project => {
        this.currentProjectObs$.next(project);
      });
  }
  // return this.currentProjectObs$;
}

// Your component
this.activatedRoute.params.subscribe((data: Params)  => {
    if (data && data.id) {
      this.projectService.getProject(data.id);
    }
});

this.projectService.currentProjectObs$
   .subscribe(project => {
       if(project){
          console.log(project);
       }
})


回答2:

I think your code can be simplified a bit:

// project-detail.component.ts
 ngOnInit() {
  this.activatedRoute.params.pipe(switchMap(params => {
       if (data && data.id) {
          return this.projectService.getProject(data.id)    
       }
     // return observable of undefined in case id was not found
     return of(undefined);

  })).subscribe(actualData  => {
     console.log("actual data returned by api: ", actualData)
  });
 }

In your service:

// project.service.ts
 import { HttpClient } from '@angular/common/http';  
 import { BehaviorSubject, Observable } from 'rxjs';

 @Injectable()
 export class ProjectService {
 private apiUrl = envLocal.apiJsonServerUrl;

 constructor(private http: HttpClient) {
 }

 getProject(id: number = 0) {
    return this.http.get(`${this.apiUrl}/projects/${id}`);
 }

You should only subscribe at the end when you actually need the data to do something. Data can flow between one observable to another using Rxjs operators. Switch map subscribes to first observable and returns another which we subscribe to at the end.

Let me know if this works for you.



标签: angular