0

I have a 'User' component and its associated service : userService. I would like to use the component form for both new and edit.

the User component is related to several other components (Country and State) and their Services while subscribing to the services subjects.

export class CountrystateService {

  private countries: Country[] = [];
  private countriesSubject = new Subject<Country[]>();
  countriesSubject$ = this.countriesSubject.asObservable();

  private allStates: State[];
  private states: State[];
  private statesSubject = new Subject<State[]>();
  statesSubject$ = this.statesSubject.asObservable();

  constructor(private apiService: ApiService) { }

  getCountriesAllStatesFromRest() {
    forkJoin({
        countries: this.apiService.getEntriesFromRest('countries').pipe(
            tap((countries: Country[])=> {this.countries=countries;})),
        states: this.apiService.getEntriesFromRest('countries').pipe(
            tap((states: State[])=> {this.allStates=states;}))
        }).subscribe(
            (results) => {
                this.countriesSubject.next(this.countries.slice());
            },
            (error) => {
                this.countriesSubject.next(error);
                return throwError(error);
            },
            () =>{}
        )
    }

    filterStatesForCountry(countryId?) {
    if (countryId) {
        this.states=this.allStates.filter(item => +item.country.split('/').pop() === countryId);
        this.statesSubject.next(this.states);
    }

  }

User Component ngOnInit:

  ngOnInit() {

    this.stateSubscription = this.countrystateService.statesSubject$.subscribe(
      (states: State[]) => {
        if (states) {
            this.states = states;       
        this.FooFunction();
        }
      },
      (error) => {..}
    );

    this.countrySubscription = this.countrystateService.countriesSubject$.subscribe(
      (countries: Country[]) => {
        if (countries) {
            this.countries = countries;         
        }
      },
      (error) => {...}
    );

    this.userSubscription = this.userService.userSubject$.subscribe(
      (user: User=> {
        if (user) {
            this.user = user;
        }
      },
      (error) => {...}
    );

in Edit context (user id in param), I need to order the requests: - get the user - get the states list regarding user.country.id

I then tried something like this :

this.userIdSubscription = this.route.paramMap.subscribe(
    (params: ParamMap) => {
        const userId = +params.get('id');
        if (userId) {
            ...
            this.userService.getUserFromRest(userId);
            this.countrystateService.getCountriesAllStatesFromRest();
            forkJoin({
                user: this.userService.userSubject$,
                states: this.countrystateService.statesSubject$,
            }).pipe(
                tap(results => console.log('FORKJOIN TRIGGERED')),
                tap(results => {
                    ...
                    this.countrystateService.filterStatesForCountry(results.user.country.id);
                }),
            );
        }
        else { this.countrystateService.getCountriesAllStatesFromRest(); }
    }
);

but it is not working with no error... thank you for your help,

Alexglvr
  • 427
  • 5
  • 18

2 Answers2

0

UPDATED

after investigation we found that Subjects$ aren't completed therefore forkJoin doesn't fit and the solution was to use combineLatest().pipe(first()) instead.

ORIGINAL

The right way is to have a single subscription and build pipes, try to use the next approach to follow rx style.

this.userIdSubscription = this.route.paramMap.pipe(
  map((params: ParamMap) => +params.get('id')),
  filter(userId => !!userId),
  tap(userId => {
    // ...
    this.userService.getUserFromRest(userId);
    this.countrystateService.getCountriesAllStatesFromRest();
  }),
  switchMap(userId => forkJoin({
    user: this.userService.userSubject$,
    states: this.countrystateService.statesSubject$,
  })),
  tap(results => {
    this.countrystateService.filterStatesForCountry(results.user.country.id);
  }),
).subscribe();

now it's more readable IMO.


Anyway looks like you forgot to subscribe.

            forkJoin({
                user: this.userService.userSubject$,
                states: this.countrystateService.statesSubject$,
            }).pipe(
                tap(results => console.log('FORKJOIN TRIGGERED')),
                tap(results => {
                    ...
                    this.countrystateService.filterStatesForCountry(results.user.country.id);
                }),
            ).subscribe(); // <- this place.

If it isn't an answer, then looks like the services aren't completed, so you can't use forkJoin, it expects all observables to complete.

Try to use combineLatest, it emits after the first emit.

            combineLatest([ // <- this change.
                user: this.userService.userSubject$,
                states: this.countrystateService.statesSubject$,
            ]).pipe(
                first(), // <- we need only the first result?
                tap(results => console.log('FORKJOIN TRIGGERED')),
                tap(([user]) => {
                    ...
                    this.countrystateService.filterStatesForCountry(user.country.id);
                }),
            ).subscribe(); // <- this change too.
satanTime
  • 12,631
  • 1
  • 25
  • 73
  • thank you for your help. I implemented your clean code proposal. I added console.log to my this.xxxSubscription = this.xxxeService.xxxsSubject$.subscribe(..) function and also checked the request list in firefox. all request are made now and terminated BUT the last tap (tap(result => { this.countrystateService.filterStatesForCountry(result.user.country.id); }), ) is not triggered (added a console.log inside to check) – Alexglvr May 10 '20 at 09:46
  • if they're terminated - then it means the subscription is killed, do you call unsubscribe anywhere? – satanTime May 10 '20 at 09:50
  • yeah - doesn't sound like a reason for the cancelation, to do you want load to happen once or every time when param has been changed? – satanTime May 10 '20 at 09:58
  • feel free to create a chat, we can discuss it there, the issue doesn't look complicated, we need just to figure out where the problem is :) – satanTime May 10 '20 at 10:02
  • how do you turn to chat? – Alexglvr May 10 '20 at 10:06
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/213544/discussion-between-satantime-and-alexglvr). – satanTime May 10 '20 at 10:07
-1

Use params from ActivatedRoute instead of paramMap fromRouter

constructor(private aroute:Activated, ...) { ... }

this.userIdSubscription = this.aroute.params.subscribe(
    (params) => {
      const userId = params.id;
      ...

}

Hope it helps!

T. Sunil Rao
  • 1,167
  • 5
  • 14