As per usual, @Tommertom has covered the questions actually asked, so I’m going to go off on a tangent that few people probably care about:
Computers are not the only ones reading your code
Pascal bothers to have two distinct keywords for defining functions: Procedure
and Function
, which is not the worst idea in the world. It’s important for readers to be able to look at a section of code and understand as quickly and viscerally as possible what it is intended to do, because a major class of bugs consists of when code doesn’t do what it’s intended to. Naming things is an important part of that.
In the Go language, naming enforces accessibility. Things that start with lowercase are private: all publicly accessible members of things must Start With Uppercase. JavaScript has no such feature, so it’s up to programmers to enforce conventions. Here’s Google’s set, which codifies what I would consider widely-accepted canon on the topic. api_service
should be apiService
, and login_clinician
should be loginClinician
.
Since we’re not in Pascal here, the only way to indicate the difference between a Procedure
and a Function
is by rigorously declaring return types for everything. You need to decide whether loginClinician
is a Procedure
or a Function
. The way you’ve written this method in the page suggests that you want the service method to be a Function
: you care about its result. I don’t know if there is a more logical thing for the service’s loginClinician
to return aside from success/failure. For example, maybe it could want to return the clinician id that it received from the API. To just do success/failure, I would simply use RxJS’s native error handling here, and return an Observable<any>
(any
here being an indication that callers can’t rely on anything the Observable
actually emits, just whether it does so or not):
interface Credentials {
studentNumber: string;
password: string;
}
loginClinician(creds: Credentials): Observable<any> {
return this.http.post(this.url + 'authenticate/clinicians', {
student_number: creds.studentNumber,
password: creds.password });
}
It’s very rare that one would want different error handling for different services that are all interacting with the network, so I think that is a task better centralized in an interceptor (along with what I presume is intended to be done with loginLoader
and friends).
At this point, it’s rather likely that your initial problem has been designed away: IOW, you no longer want to access success
from the page, which can simply do:
this.clinicianService.loginClinician(this.form.value).subscribe(() => {
// if we're in here, the login succeeded
});
However, there’s an outside chance that you want to know something about the currently logged-in clinician (such as their name to display a personalized greeting), or just whether somebody is logged in at all (to maybe redirect to a login page if a session has expired), without being in the context of actually logging in somebody right now. That changes loginClinician
from a Function
to a Procedure
, and decouples the information about the currently logged-in user out of it.
There are, broadly speaking, two major situations here that I’ll call imperative and reactive. Displaying the user’s name is an imperative need: we want something with a signature like getCurrentUserName(): string
. Knowing when a login or logout has happened for routing reasons, however, is reactive. We need to be notified whenever there is a change.
Fortunately, RxJS provides the BehaviorSubject
, which is capable of servicing both imperative and reactive needs:
interface Clinician {
studentNumber: string;
name: string;
}
private activeClinician$ = new BehaviorSubject<Clinician | null>(null);
watchActiveClinician(): Observable<Clinician | null> {
return this.activeClinician$;
}
peekActiveClinician(): Clinician | null {
return this.activeClinician$.value;
}
loginClinician(creds: Credentials): void {
this.activeClinician$.next(null);
this.http.post<Clinician>(this.url + 'authenticate/clinicians', {
student_number: creds.studentNumber,
password: creds.password })
.subscribe((clinician) => this.activeClinician$.next(clinician));
}
This effectively does let you access the success
you were looking for initially, but whenever you want and in two ways.