0

I'm learning to write angular applications in a declarative and I'm not sure what the best approach is when calling POST requests.

I have a login form with email and password forms which when a user enters and clicks submit will call the subject.next()

onLogin() {
    if (this.loginForm?.valid) {
      this.userService.loginUserSubject.next(this.loginForm?.value)
    }
  }

Inside my user.service class I have following implemented:

 loginUserSubject = new BehaviorSubject<IUser | null>(null);

  loginInfo$ = this.loginUserSubject.asObservable();

  $login = this.loginInfo$.pipe(
    switchMap(user => this.http.put<IUser>('/user/login', user).pipe(
      tap(response => localStorage.setItem('token', response.email)),
      catchError(error => throwError(error))
      )
    )
 )

Bu then the user clicks submit nothing will happen, the form.value will emit to the subject but the HTTP call won't be executed because there is no subscription happening for the login$ Observable. Is there a way I can call the HTTP POST request when the subject gets the value without subscribing to the Observable in my component or using the async pipe?

Aldin Muratovic
  • 87
  • 1
  • 1
  • 12

2 Answers2

1

I'm not sure why you'd need the BehaviorSubject here. IMO, you could make do without it.

As for why it isn't working for you at the moment, most probably you haven't subscribed to the $login observable.

Service

public login(user: IUser): Observable<any> {
  return this.http.put<IUser>('/user/login', user).pipe(
    tap(response => localStorage.setItem('token', response.email)),
    catchError(error => throwError(error))
  );
}

Component

onLogin() {
  if (this.loginForm?.valid) {
    this.userService.login(this.loginForm?.value).subscribe({
      next: (response: any) => { },
      error: (error: any) => { }
    });
  }
}
ruth
  • 29,535
  • 4
  • 30
  • 57
  • This is what I'm trying to avoid, like I said I'm trying to use the declarative way, which does not have any .subscribe inside the component. You can read more here: https://betterprogramming.pub/rxjs-declarative-pattern-in-angular-cafba3983d21 – Aldin Muratovic May 25 '22 at 10:06
  • @AldinMuratovic: The author's proposal from the article to use `async` pipe instead of an explicit subscription would be possible if you were also subscribing _without_ any implied condition. But in your case, you'd only need to subscribe when the button is pressed, which negates the usage of the `async` pipe. If you handle closure of the subscription correctly, there isn't any issue in subscribing explicitly. (1/2) – ruth May 25 '22 at 11:23
  • @AldinMuratovic: In fact, `async` pipe [internally subscribes](https://github.com/angular/angular/blob/main/packages/common/src/pipes/async_pipe.ts#L23) to the observable as well with one big advantage that it avoids dangling (open) subscriptions. See [here](https://stackoverflow.com/a/60223749/6513921) to know how to handle a subscription elegantly. (2/2) – ruth May 25 '22 at 11:24
1

Just because you doesn't subscribe your Subject. The code should be like.

loginUserSubject = new BehaviorSubject<IUser>({} as IUser);
loginInfo$ = this.loginUserSubject.asObservable();

$login = this.loginUserSubject.pipe(
  filter((user: IUser) => (Object.keys(user).length > 0)), // prevent object init triggered.
  switchMap(user => this.http.put<IUser>('/user/login', user)
    .pipe(
      tap(response => localStorage.setItem('token', response.email)),
      catchError(error => throwError(error))
    )
  )
).subscribe(() => {
  console.log('Im subscribing');
  // logic might need to be here.....
});
paranaaan
  • 1,626
  • 2
  • 9
  • 17