- MEAN Blueprints
- Robert Onodi
- 1353字
- 2021-07-16 10:40:20
Granting access to our application
We have restricted access to our API's endpoints, so now we have to grant users sign-in functionality from the client application. I like to group the Angular 2 application files based on their domain context. So, for example, all our authentication, registration, and business logic should go into a separate folder; we can call it auth
.
If your module directory grows, it's good practice to break it down into separate folders based on their context by type. There is no magic number for the file count. Usually, you will get a good feeling when it's time to move files around. Your files should always be easy to locate and give you enough information from their placement in a certain context.
AuthService
We are going to use AuthService
to implement the data access layer and make calls to the backend. This service is going to be the bridge between our API's sign-in and register features. Create a new file called contact-manager/src/auth/auth.service.ts
, and add the following TypeScript code into it:
import { Injectable } from 'angular2/core'; import { Http, Response, Headers } from 'angular2/http'; import { contentHeaders } from '../common/headers'; @Injectable() export class AuthService { private _http: Http; constructor(http: Http) { this._http = http; } }
We import the necessary modules, define the AuthService
class, and export it. The Injectable marker metadata will mark our class to be available to be injected. In order to communicate with the backend, we use the HTTP service. Don't forget to add the HTTP_PROVIDERS
when bootstrapping the application so that the service is available to be injected in the whole application.
To sign in a user, we are going to add the following method:
public signin(user: any) { let body = this._serialize(user); return this._http .post('/auth/signin', body, { headers: contentHeaders }) .map((res: Response) => res.json()); }
We can use the .map()
operator to transform the response into a JSON file. When performing HTTP requests, this will return an Observable
. You have probably already figured it out—we are going to use RxJs (Reactive Extensions) heavily, which is a third-party library favored by Angular.
RxJs implements asynchronous observable pattern. In other words, it enables you to work with asynchronous data streams and apply different operators. Observables are used widely in Angular applications. At the time of writing this book, Angular 2 exposes a stripped-down version of the Observable
module from RxJs.
Don't worry; we'll get familiar with this technique and the benefits of it as we dive further into the book. Now let's continue with the rest of the missing methods we want to expose:
public register(user: any) { let body = this._serialize(user); return this._http .post('/auth/register', body, { headers: contentHeaders }) .map((res: Response) => res.json()); } private _serialize(data) { return JSON.stringify(data); }
We added the register()
method to our service, which will handle user registration. Also note that we moved our serialization into a separate private method. I've left this method in the same class so that it's easier to follow, but you can move it into a helper class.
User sign-in component
For a start, we are going to implement the sign-in component. Let's create a new file called contact-manager/public/src/auth/sigin.ts
and add the following lines of TypeScript code:
import { Component } from 'angular2/core'; import { Router, RouterLink } from 'angular2/router'; import { AuthService } from './auth.service'; export class Signin { private _authService: AuthService; private _router: Router; constructor( authService: AuthService, router: Router ) { this._authService = authService; this._router = router; } signin(event, email, password) { event.preventDefault(); let data = { email, password }; this._authService .signin(data) .subscribe((user) => { this._router.navigateByUrl('/'); }, err => console.error(err)); } }
We still need to add the Component
annotation before our Signin
class:
@Component({ selector: 'signin', directives: [ RouterLink ], template: ` <div class="login jumbotron center-block"> <h1>Login</h1> <form role="form" (submit)="signin($event, email.value, password.value)"> <div class="form-group"> <label for="email">E-mail</label> <input type="text" #email class="form-control" id="email" placeholder="enter your e-mail"> </div> <div class="form-group"> <label for="password">Password</label> <input type="password" #password class="form-control" id="password" placeholder="now your password"> </div> <button type="submit" class="button">Submit</button> <a href="#" [routerLink]="['Register']">Click here to register</a> </form> </div> ` })
The Signin
component is going to be our sign-in form and it uses the AuthService
to communicate with the backend. In the component's template, we are using local variables marked with a #
sign for the email and password fields.
As we said earlier, the HTTP service returns an Observable
when making a request. This is the reason we can subscribe to the response generated by the requests made from our AuthService
. On successful authentication, the user is redirected to the default home path.
The Register
component will look similar to the Signin
component, so there is no need to detail this scenario. The final version of the auth
module will be available in the source code.
Custom HTTP service
In order to restrict access to our API endpoints, we have to make sure that, if a request is unauthorized, we redirect the user to the sign-in page. Angular 2 has no support for Interceptors and we don't want to add a handler for each request we integrate into our services.
A more convenient solution would be to build our own custom service on top of the built-in HTTP service. We could call it AuthHttp
, from authorized HTTP requests. Its purpose would be to check whether a request returned a 401 Unauthorized HTTP status
code.
I would like to take this thought even further and bring a hint of reactive programming, because we are already using RxJS. So, we can benefit from the full set of functionalities it provides. Reactive programming is oriented around data. Streams of data propagate in your application and it reacts to those changes.
Let's get to business and start building our custom service. Create a file called contact-manager/public/src/auth/auth-http.ts
. We are going to add a few lines of code:
import { Injectable } from 'angular2/core'; import { Http, Response, Headers, BaseRequestOptions, Request, RequestOptions, RequestOptionsArgs, RequestMethod } from 'angular2/http'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; import { BehaviorSubject } from 'rxjs/Subject/BehaviorSubject'; @Injectable() export class AuthHttp { public unauthorized: Subject<Response>; private _http: Http; constructor(http: Http) { this._http = http; this.unauthorized = new BehaviorSubject<Response>(null); } }
There are a few things we imported at the top of the file. We'll need all of them in this module. We defined a public property named unauthorized
, which is a Subject. A Subject is both an Observable
and Observer
. This means that we can subscribe our subject to a backend data source and also all observers can subscribe to the subject.
In our case, the subject will be a proxy between our data source and all the subscribed observers. If a request is unauthorized, all subscribers get notified with the change. This enables us to just subscribe to the subject and redirect the user to the sign-in page when we detect an unauthorized request.
To succeed in doing this, we have to add a few more methods to our AuthHttp
service:
private request(requestArgs: RequestOptionsArgs, additionalArgs?: RequestOptionsArgs) { let opts = new RequestOptions(requestArgs); if (additionalArgs) { opts = opts.merge(additionalArgs); } let req:Request = new Request(opts); return this._http.request(req).catch((err: any) => { if (err.status === 401) { this.unauthorized.next(err); } return Observable.throw(err); }); }
The preceding method creates a new request with the desired RequestOptions
and invokes the request
method from the base HTTP service. Additionally, the catch
method captures all requests with status code not 200-level.
Using this technique, we can send the unauthorized request to all subscribers by using our unauthorized
subject. Now that we have our private request
method, we just need to add the rest of the public HTTP methods:
public get(url: string, opts?: RequestOptionsArgs) { return this.request({ url: url, method: RequestMethod.Get}, opts); } public post(url: string, body?: string, opts?: RequestOptionsArgs) { return this.request({ url: url, method: RequestMethod.Post, body: body}, opts); } public put(url: string, body?: string, opts?: RequestOptionsArgs) { return this.request({ url: url, method: RequestMethod.Put, body: body}, opts); } // rest of the HTTP methods ...
I've added only the most commonly used methods; the rest is available in the full version. The preceding code calls our request method and sets the necessary options for each request type. Theoretically, we have created a fa?ade to handle unauthorized requests.
I think we've made good progress and it's time to move on to the rest of the modules of our contact manager application.
- Microsoft Exchange Server PowerShell Cookbook(Third Edition)
- Android Studio Essentials
- WebRTC技術詳解:從0到1構建多人視頻會議系統
- MATLAB for Machine Learning
- The Professional ScrumMaster’s Handbook
- Mastering C++ Multithreading
- Domain-Driven Design in PHP
- 時空數據建模及其應用
- Python Data Science Cookbook
- 軟件工程基礎與實訓教程
- Delphi開發典型模塊大全(修訂版)
- Java自然語言處理(原書第2版)
- Kotlin程序員面試算法寶典
- 新手學ASP.NET 3.5網絡開發
- Java語言程序設計與實現(微課版)