'Cannot read property 'subscribe' of undefined angular

I have a service UserService that has a method getUsers() and this service is shared among different angular components.

The method calls an API and returns the data. I want to hold the result/state of last API call so I used BehaviorSubject to hold the last state so that if different components call getUsers() there should be only one API call otherwise hold state must be returned.

This is how I am implementing this

private _users$: BehaviorSubject<User[]>;

constructor(private http: HttpClient) {
    this._users$ = new BehaviorSubject([]);
}

getUsers(): BehaviorSubject<User[]> {
    if (!this._getUsersState().length) {
        this._loadUsers();
        this._users$.subscribe(() => {
            return this._users$;
        });
    } else {
        return this._users$;
    }
}

_loadUsers() {
    let _users$ = this.http.get(`${this._context}/users/`);
    _users$.subscribe((users: User[]) => {
        this._users$.next(Object.assign([], users));
    });
}

_getUsersState(): User[] {
    return this._users$.getValue();
}

And in the component

this.userService.getUsers().subscribe((users: any) => {
    console.log(users);
});

but I am getting the following error

ERROR TypeError: Cannot read property 'subscribe' of undefined

I guess get users returns automatically if the length is 0.

How can I solve this issue? And is this approach good?



Solution 1:[1]

Edit your service like this :

private _users$ = new BehaviorSubject<User[]>(null);
public usersChanged = this._users$.asObservable();

constructor(private http: HttpClient) {
}

getUsers(): BehaviorSubject<User[]> {
    if (!this._getUsersState().length) {
        this._loadUsers();
        this._users$.subscribe(() => {
            return this._users$;
        });
    } else {
        return this._users$;
    }
}

_loadUsers() {
    let _users$ = this.http.get(`${this._context}/users/`);
    _users$.subscribe((users: User[]) => {
        this._users$.next(Object.assign([], users));
    });
}

_getUsersState(): User[] {
    return this._users$.getValue();
}

edit yout component logic like this:

this.userService.usersChanged
.subscribe((users: any) => {
  if(!!users){
    console.log(users);
  } else {
    console.log("No user found!");
  }
});

It's a good practise to have a variable that is an observable of your private behaviorSubject. So you just subscribe to it and whenever its state change, it'll emit.

Solution 2:[2]

getUsers method should return a observable and it's not good practice to subscribe observable in service. If you want to maintain state of users then use a singleton service. Declare property in service like this:

public users: Users[] = [];


private getUsers(): Observable<Users[]> {
 if (!this.users.length) {
   return this.http.get(`${this._context}/users/`);
 }
 return Observable.of(this.users);
}

In your component

this.userService.getUsers().subscribe((users: User[]) => {
    this.userService.users = users; // (or users.slice(0)
    console.log(users);
});

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jacopo Sciampi
Solution 2 Sandip Jaiswal