In previous posts, we made an API with Flask and an Angular application. In this tutorial, we’ll make requests to the API through Angular’s HTTP service.
Firstly, to keep our code clean and easy to change later, we will utilize Angular’s environment files. In src/environments
we will open environment.ts
. Inside, we will find the following.
export const environment = {
[tab]production: false
};
Let’s add another property to the environment constant.
export const environment = {
[tab]production: false,
[tab]apiServer: 'http://localhost:5000/api/'
};
This will be important later, but just know that we put it here because in the case that we need to change the API URL, we only need to change it once here instead of multiple times throughout the project.
Now let’s make a service. Services are important because they have reusable code we can use in components and other services without needing the overhead of a component. We make components with either
ng generate service SERVICE_NAME
or
ng g s SERVICE_NAME
We’ll name our service auth
If we open the newly created service (auth.service.ts), you’ll see that it only has a constructor in the body.
constructor() { }
Let’s import the HTTP Client so we can make our requests.
constructor(private http: HttpClient) { }
Let’s also make a readonly variable called authBase
private readonly authbase = environment.apiServer + 'auth/';
Because we’re following the Flask API example, we need to create some interfaces so we can use TypeScript’s type handling. We do that with the either
ng generate interface INTERFACE_NAME
or
ng g i INTERFACE_NAME
We’ll make three interfaces, one called user
, another called id
, and another called token_pair
Let’s make a function to register the user.
public signup(email: string, password: string): Observable<Id> {
[tab]return this.http.post<Id>(this.authBase + 'signup', {email, password});
}
The http.post function requires the first parameter be the location of the POST request, and the second parameter will be the JSON body we will send in the request body. There is also an optional third field for sending things like custom headers, but we will get into that later.
Let’s keep going and make a login function.
public signup(email: string, password: string): Observable<TokenPair> {
[tab]return this.http.post<TokenPair>(this.authBase + 'login', {email, password});
}
For now that will be all we need to get started. This isn’t too useful for us as programmers because we don’t have type hinting for the returned values. All TypeScript knows is that we’re returning an Observable with a custom interface type, but there’s nothing in the interfaces, so let’s fix that. In the generated user.ts
file we will add the following properties:
id?: string;
email?: string;
We add a question mark after the property name to tell TypeScript that it isn’t a required field to make an object with this type. The only downside of doing this is that we won’t be 100% sure that the field exists, so we will need to add extra checking.
Now, in id.ts
we will add id: string;
as the only property.
And finally, in token-pair.ts
we will add the following properties.
accessToken: string;
refreshToken: string;
I am going to assume that you have a simple component for registering and/or logging in. It doesn’t need to be complicated. It just needs a form that sends the provided email and password to a function. In that component, we need to import auth.service.ts
by adding the following inside the parenthesis of the constructor: private auth: AuthService
With that, we have access to all of the public functions of the auth service without needing to import the HTTP Client or remembering all of the complicated API routes, we just need to run the functions. Inside of the log in function of the component, we will simply run the login function.
loginFunction(...): void {
[tab]this.auth.login(EMAIL, PASSWORD).toPromise().then(tokens => {
[tab][tab]console.log(tokens);
[tab]});
}
This will simply log our access and refresh tokens to the console. We can save them with cookies or store them in localStorage for future use (Store them in cookies if you are using server-side rendering so they are available to the server).
Next we’ll make our register function. This is a little more complicated because we need to chain requests together, but it’s only one extra call, so it isn’t too bad.
loginFunction(...): void {
[tab]this.auth.register(EMAIL, PASSWORD).toPromise().then(id => {
[tab][tab]this.auth.login(EMAIL, PASSWORD).toPromise().then(tokens => {
[tab][tab][tab]console.log(tokens);
[tab][tab]});
[tab]});
}
This will again simply log our tokens to the console, but it also created a new user.
Now that we’ve seen how to do basic API requests with the HTTP Client, we will move on to authenticated requests. This is very simple to do. You’ll remember in Flask we used the @jwt_required
decorator to tell Flask that this is a protected route and the requester will need to be authenticated. We do this by adding the access token to our headers.
I’ll show an example with the getUser
function in auth.service.ts
.
public getUser(): Observable<User> {
[tab]const accessToken = ACCESS_TOKEN; // Get the access token from wherever you stored it.
[tab]if (accessToken) {
[tab][tab]const headers = new HttpHeaders().append('Authorization', 'Bearer ' + accessToken);
[tab][tab]return this.http.get<User>(this.authBase + 'user', {headers});
[tab]} else {
[tab][tab]return new Observable<User>();
[tab]}
}
We had to add a little more code than the simpler login
and signup
functions, but not much when we break it down. First we get the access token from wherever it is stored, then we check that it exists (we are signed in). If we’re signed in, we create new headers and add the access token using the Authorization Bearer format, and send it off in a get request to the API. If we’re not signed in, we simply return an empty Observable. This empty Observable doesn’t have a value, so it doesn’t fire the internals of the promise code (.toPromise().then(res => { THIS STUFF });
).
We now have the tools to make any kind of request we need to. For example, our SEPHIRA content management system uses HTTP services to efficiently perform any kind of task, from authentication, to administrative, to user-level page fetching and checkout.