// Import the core angular services. import { BehaviorSubject } from "rxjs/BehaviorSubject"; import { Observable } from "rxjs/Observable"; // Import the application components and services. import { Message } from "./message.gateway"; import { MessageGateway } from "./message.gateway"; interface MessageDTO { id: string; text: string; createdAt: number; } export class LocalStorageMessageGateway extends MessageGateway { private storageKey: string; private subject: BehaviorSubject<Message[]>; // I initialize the localStorage message gateway implementation. constructor() { super(); this.storageKey = "ng4-demo-firebase-encapsulation"; // In this implementation, we're going to use a BehaviorSubject rather than // a normal Subject so that we can replay the last value when the user first // subscribes to the observable stream. This way, the subscription itself will // act as the first read / load of data. this.subject = new BehaviorSubject( [] ); // When the localStorage object is updated from ANOTHER WINDOW, pertaining to // this origin, a "storage" event is triggered. This event, however, is NOT // TRIGGERED if the current window updates the localStorage object. As such, // we can use this event to update our in-memory cache of the localStorage // messages content. window.addEventListener( "storage", ( event: StorageEvent ) : void => { // Since this event fires for all localStorage events, we want to ignore // any event that may be triggered by a different application. Make sure // it pertains to our localStorage key. if ( event.key === this.storageKey ) { this.emitMessages(); } } ); } // --- // PUBLIC METHODS. // --- // I add a new message to the collection. Returns a Promise with the new ID. public createMessage( message: Message ) : Promise<string> { var promise = new Promise<string>( ( resolve, reject ) : void => { var dto = { id: this.newID(), text: message.text, createdAt: message.createdAt.getTime() }; var messages = this.getMessagesFromLocalStorage(); messages.push( dto ); this.setMessagesToLocalStorage( messages ); this.emitMessages(); resolve( dto.id ); } ); return( promise ); } // I delete the message with the given ID. Returns a Promise. public deleteMessage( id: string ) : Promise<void> { var promise = new Promise<void>( ( resolve, reject ) : void => { var messages = this.getMessagesFromLocalStorage().filter( ( message: MessageDTO ) : boolean => { return( message.id !== id ); } ); this.setMessagesToLocalStorage( messages ); this.emitMessages(); resolve(); } ); return( promise ); } // I read the messages collection. Returns a Promise. public readMessages() : Promise<Message[]> { var promise = new Promise<Message[]>( ( resolve, reject ) : void => { var messages = this.getMessagesFromLocalStorage().map( ( message: MessageDTO ) : Message => { return({ id: message.id, text: message.text, createdAt: new Date( message.createdAt ) }); } ); resolve( messages ); } ); return( promise ); } // I read the messages collection as a stream. Returns an Observable stream. public readMessagesAsStream() : Observable<Message[]> { // Push messages into the BehaviorSubject. This will ensure that the Subject has // been primed with data by the time the calling context goes to subscribe to the // stream. Which will, in turn, ensure that the messages collection is pushed to // the observer upon subscription. this.emitMessages(); return( this.subject.asObservable() ); } // --- // PRIVATE METHODS. // --- // I read and emit the latest messages collection on the read-stream. private emitMessages() : void { this.readMessages().then( ( messages: Message[] ) : void => { this.subject.next( messages ); } // CAUTION: To keep this demo simply, I'm not caring about any errors that // could be thrown from this Promise. ); } // I read the messages out of localStorage. private getMessagesFromLocalStorage() : MessageDTO[] { var data = localStorage.getItem( this.storageKey ); if ( ! data ) { return( [] ); } return( JSON.parse( data ) ); } // I generate a new ID for a new Message object. private newID() : string { // Since the localStorage is shared by all tabs on the same domain, we have to // be more careful about how we generate the ID. Let's use a bunch of random // character to help prevent conflicts. var validChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; var chars = []; for ( var i = 0 ; i < 30 ; i++ ) { chars.push( validChars.charAt( ( Math.floor( Math.random() * Date.now() ) % validChars.length ) ) ); } return( "m" + chars.join( "" ) ); } // I store the given messages in localStorage. private setMessagesToLocalStorage( messages: MessageDTO[] ) : void { localStorage.setItem( this.storageKey, JSON.stringify( messages ) ); } }