Книга: Angular 2 Cookbook
Назад: Implementing a Publish-Subscribe model using Subjects
Дальше: Building a generalized Publish-Subscribe service to replace $broadcast, $emit, and $on

Creating an Observable authentication service using BehaviorSubjects

One of the most obvious and useful cases of the Observer Pattern is the one in which a single entity in your application unidirectionally communicates information to a field of listeners on the outside. These listeners would like to be able to attach and detach freely from the single broadcasting entity. A good initial example of this is the login/logout component.

Note

The code, links, and a live example of this are available at .

Getting ready

Suppose you have the following skeleton application:

[app/login.component.ts]      import {Component} from '@angular/core';      @Component({     selector: 'login',     template: `       <button *ngIf="!loggedIn"                (click)="loggedIn=true">         Login       </button>       <button *ngIf="loggedIn"                (click)="loggedIn=false">         Logout       </button>     `   })   export class LoginComponent {     loggedIn:boolean = false;   }   

As it presently exists, this component will allow you to toggle between the login/logout button, but there is no concept of shared application state, and other components cannot utilize the login state that this component would track.

You would like to introduce this state to a shared service that is operated using the Observer Pattern.

How to do it...

Begin by creating an empty service and injecting it into this component:

[app/authentication.service.ts]      import {Injectable} from '@angular/core';      @Injectable()   export class AuthService {     private authState_: AuthState;   }      export const enum AuthState {      LoggedIn,      LoggedOut   }   

Notice that you are using a TypeScript const enum to keep track of the user's authentication state.

Note

If you're new to ES6 and TypeScript, these keywords may feel a bit bizarre to you. The const keyword is from the ES6 specification, signifying that this value is read only once declared. In vanilla ES6, this will throw an error, usually SyntaxError, at runtime. With TypeScript compilation though, const will be caught at compile time.

The enum keyword is an offering of TypeScript. It is not dissimilar to a regular object literal, but note that the enum members do not have values.

Throughout the application, you will reference these via AuthState.LoggedIn and AuthState.LoggedOut. If you reference the compiled JavaScript that TypeScript generates, you will see that these are actually assigned integer values. But for the purposes of building large applications, this allows us to develop a centralized repository of possible AuthState values without worrying about their actual values.

Injecting the authentication service

As the skeleton service currently exists, you are going to instantiate a Subject that will emit AuthState, but there is no way available currently to interact with it. You will set this up in a bit. First, you must inject this service into your component:

[app/app.module.ts]      import {NgModule} from '@angular/core';   import {BrowserModule} from '@angular/platform-browser';   import {LoginComponent} from './login.component';   import {AuthService} from './authentication.service';      @NgModule({     imports: [       BrowserModule     ],     declarations: [       LoginComponent     ],     providers: [       AuthService       ],     bootstrap: [       LoginComponent     ]   })   export class AppModule {}   

This is all well and good, but the service is still unusable as is.

Tip

Note that the path you import your AuthService from may vary depending on where it lies in your file tree.

Adding BehaviorSubject to the authentication service

The core of this service is to maintain a global application state. It should expose itself to the rest of the application by letting other parts say to the service, "Let me know whenever the state changes. Also, I'd like to know what the state is right now." The perfect tool for this task is BehaviorSubject.

Note

RxJS Subjects also have several subclasses, and BehaviorSubject is one of them. Fundamentally, it follows all the rhythms of Subjects, but the main difference is that it will emit its current state to any observer that begins to listen to it, as if that event is entirely new. In cases like this, where you want to keep track of the state, this is extremely useful.

Add a private BehaviorSubject (initialized to the LoggedOut state) and a public Observable to AuthService:

[app/authentication.service.ts]      import {Injectable} from '@angular/core';   import {BehaviorSubject} from 'rxjs/BehaviorSubject';   import {Observable} from 'rxjs/Observable';      @Injectable()   export class AuthService {     private authManager_:BehaviorSubject<AuthState>       = new BehaviorSubject(AuthState.LoggedOut);     private authState_:AuthState;     authChange:Observable<AuthState>; constructor() {       this.authChange = this.authManager_.asObservable();     }   }      export const enum AuthState {      LoggedIn,      LoggedOut   }   

Adding API methods to the authentication service

Recall that you do not want to expose the BehaviorSubject instance to outside actors. Instead, you would like to offer only its Observable component, which you can openly subscribe to. Furthermore, you would like to allow outside actors to set the authentication state, but only indirectly. This can be accomplished with the following methods:

[app/authentication.service.ts]      import {Injectable} from '@angular/core';   import {BehaviorSubject} from 'rxjs/BehaviorSubject';   import {Observable} from 'rxjs/Observable';      @Injectable()   export class AuthService {     private authManager_:BehaviorSubject<AuthState>       = new BehaviorSubject(AuthState.LoggedOut);     private authState_:AuthState;     authChange:Observable<AuthState>;        constructor() {       this.authChange = this.authManager_.asObservable();     }     login():void {       this.setAuthState_(AuthState.LoggedIn);     }     logout():void {       this.setAuthState_(AuthState.LoggedOut);     }     emitAuthState():void {       this.authManager_.next(this.authState_);     }     private setAuthState_(newAuthState:AuthState):void {       this.authState_ = newAuthState;       this.emitAuthState();     }   }      export const enum AuthState {      LoggedIn,      LoggedOut   }   

Outstanding! With all of this, outside actors will be able to subscribe to authChange Observable and will indirectly control the state via login() and logout().

Tip

Note that the Observable component of BehaviorSubject is named authChange. Naming the different components of the elements in the Observer Pattern can be tricky. This naming convention was selected to represent what an event emitted from the Observable actually meant. Quite literally, authChange is the answer to the question, "What event am I observing?". Therefore, it makes good semantic sense that your component subscribes to authChanges when the authentication state changes.

Wiring the service methods into the component

LoginComponent does not yet utilize the service, so add in its newly created methods:

[app/login.component.ts]      import {Component} from '@angular/core';   import {AuthService, AuthState} from './authentication.service';      @Component({     selector: 'login',     template: `       <button *ngIf="!loggedIn"                (click)="login()">         Login       </button>       <button *ngIf="loggedIn"                (click)="logout()">         Logout        </button>     `   })   export class LoginComponent {     loggedIn:boolean;        constructor(private authService_:AuthService) {       authService_.authChange.subscribe(         newAuthState =>           this.loggedIn = (newAuthState === AuthState.LoggedIn));     }        login():void {       this.authService_.login();     }        logout():void {       this.authService_.logout();     }   }   

With all of this in place, you should be able to see your login/logout buttons function well. This means you have correctly incorporated Observable into your component.

Tip

This recipe is a good example of conventions you're required to maintain when using public/private. Note that the injected service is declared as a private member and wrapped with public component member methods. Anything that another part of the application calls or anything that is used inside the template should be a public member.

How it works...

Central to this implementation is that each component that is listening to Observable has an idempotent handling of events that are emitted. Each time a new component is connected to Observable, it instructs the service to emit whatever the current state is, using emitAuthState(). Necessarily, all components don't behave any differently if they see the same state emitted multiple times in a row; they will only alter their behavior if they see a change in the state.

Notice how you have totally encapsulated the authentication state inside the authentication service, and at the same time, have exposed and utilized a reactive API for the entire application to build upon.

There's more...

Two critical components of hooking into services such as these are the setup and teardown processes. A fastidious developer will have noticed that even if an instance of LoginComponent is destroyed, the subscription to Observable will still persist. This, of course, is extremely undesirable!

Fortunately, the subscribe() method of Observables returns an instance of Subscription, which exposes an unsubscribe() method. You can therefore capture this instance upon the invocation of subscribe() and then invoke it when the component is being torn down.

Similar to listener teardown in Angular 1, you must invoke the unsubscribe method when the component instance is being destroyed. Happily, the Angular 2 life cycle provides you with such a method, ngOnDestroy, in which you can invoke unsubscribe():

[app/login.component.ts]      import {Component, ngOnDestroy} from '@angular/core';   import {AuthService, AuthState} from './authentication.service';   import {Subscription} from 'rxjs/Subscription';      @Component({     selector: 'login',     template: `       <button *ngIf="!loggedIn"                (click)="login()">         Login       </button>       <button *ngIf="loggedIn"                (click)="logout()">         Logout       </button>     `   })   export class LoginComponent implements OnDestroy {     loggedIn:boolean;     private authChangeSubscription_: Subscription;        constructor(private authService_:AuthService) {       this.authChangeSubscription_ =          authService_.authChange.subscribe(           newAuthState =>             this.loggedIn = (newAuthState === AuthState.LoggedIn));     }        login():void {       this.authService_.login();     }     logout():void {       this.authService_.logout();     }     ngOnDestroy() {       this.authChangeSubscription_.unsubscribe();     }   }   

Now your application is safe from memory leaks should any instance of this component ever be destroyed in the lifetime of your application.

See also

  • Basic utilization of Observables with HTTP demonstrates the basics of how to use an observable interface
  • Implementing a Publish-Subscribe model using Subjects shows you how to configure input and output for RxJS Observables
  • Building a generalized Publish-Subscribe service to replace $broadcast, $emit, and $on assembles a robust PubSub model for connecting application components with channels
  • Building a fully featured AutoComplete with Observables gives you a broad tour of some of the utilities offered to you as part of the RxJS library
Назад: Implementing a Publish-Subscribe model using Subjects
Дальше: Building a generalized Publish-Subscribe service to replace $broadcast, $emit, and $on

thank you
Flame
cant read the code since it is all on a single line. Also this comments section is russian
Rakuneque
DATA COLLECTION AND ANALYSIS Two reviewers extracted data and assessed methodological quality independently lasix torsemide conversion Many others were in that space already