How to create Reactive applications in Angular 4 to Improve your UX

Why you should consider it, the pain points and how to write your own lightweight reactive store

Image for post
Image for post
Keeping multiple simultaneous clients updated can be a nightmare

An Introduction

Technologies Available

Firebase :

Socket.Io:

Read on to learn:

Building a Lightweight Front-End Store

Service setup

/* Rest of db.service.ts */// Todo: (Next)import { DbObject } from ‘./db-object’;private userDb = {};private pathError = (search, path) =>{ return {msg: ‘Error access value at path’, accessing: JSON.stringify(search), ‘path’: path}};// Accepts a JSON Object, e.g. userDb snd searches for value at url// Eg. ‘users/test/nickname’ ,// Returns error if invalid url, else the value at route.private Value(route, search): any {   if(route.length < 1) return search;
let next = route.shift(); let data = search[next];
let dataUndefined = data !== 0 && !data;
if(dataUndefined) throw this.pathError(search, next);
return this.Value(route, data);
}// Access Db implicitly without passing userDb variableDbValue(route): any { return this.Value(route, this.userDb);}
import { BehaviorSubject } from ‘rxjs’;export class DbObject < T > extends BehaviorSubject < T > {private dbPath : string[];private dbService: any;
constructor(state, path, dbService) {
super(state); this.dbPath = path; this.dbService = dbService;}// Called to update state of db && fire event to all subscribers Update(value) { this.next(value); }
Image for post
Image for post
https://stocksnap.io/photo/0A92VJXHAL
/* Rest of db.service.ts */private userDb = {};private pathError = (search, path) => {msg: ‘Error access value at path’, accessing: JSON.stringify(search), ‘path’: path};/* Accessing DbObjects*/private userDbSubjects = {};
Ref(url: string): DbObject< any > {
let subject: DbObject< any > = this.userDbSubjects[url]; if(!subject) { let route = url.trim().split(‘/’); let routeValue = this.Value(route.slice(), this.userDb); subject = new DbObject(routeValue, route.slice(), this); this.userDbSubjects[url] = subject; } return subject;}private Value(route, search): any { /* */ }
/* Some-Component */import { DbService } from ‘db.service’;import { DbObject } from ‘db-object’;obj: DbObject < any >constructor(db: DbService) {   this.obj = db.Ref(‘pathA/pathB/object’);     this.obj.subscribe(changes => console.log(“Obj: ” + changes));}// Assigned to some button (click)MakeChange() {obj.Update(“Test”, this.obj);}
First install the necessary modules:npm install express socket.io — -savenpm install youFavouriteDatabase// Build ng app to dist folderng build
// This script should encompass any db logic you haveconst db = require(‘./db’);const express = require(‘express’);
var app = express();
app.use(express.static(path.join( ____dirname , ‘dist’ ) ) ); // Return Ng4 app app.get(‘/*’, function(req, res){ res.sendFile(__dirname + ‘/dist/index.html’);});
// Listen on port / production port from Herokuvar server = app.listen(process.env.PORT || 3000);// Socket listenvar io = require(‘socket.io’).listen(server);
/* server.js continued… */var collection;var store = {};db.connect(url, function(err) {   if(err) {      console.log(“Error: ” + err);      return;   }   / * Implementation by database * /   collection = db.get().collection(‘COLLECTION NAME’);    db.open(collection).then(data => store = data);});
/* server.js continued… */io.on(‘connection’, function(socket) {     io.emit(‘db’, store);     socket.on(‘dbUpdate’, function (dbData) {         SetValue(dbData.path.slice(), dbData.data);         socket.broadcast.emit(‘clientUpdate’, dbData);    });});
var SetValue = function(route, data) {
UpdateValue(route, store, value);
/ * Custom per database implementation * /
db.update(collection, store);}
var UpdateValue = function(route, store, value) { var next = null; //Return condition (Assign value to store[next]) if(route.length <= 1) { next = route.shift(); store[next] = value; return; }; next = route.shift(); var subStore = store[next]; UpdateValue(route, subStore, value);}
/* db.service */// Socket client importimport * as io from ‘socket.io-client’;
private userDb = {};
/ * Accessing DbObjects * /private userDbSubjects = {};
// Socket
private socket: io.Socket;
constructor() {
// Declare socket and establish connection this.socket = io();
//Receive initial data payload
this.socket.on(‘db’, (data)=>{ this.userDb = data; this.socketLoaded = true; this.socketMonitor.next(this.__ socketLoaded); });
// Any updates broadcast from other clients
this.socket.on(‘clientUpdate’, (data: any)=>{ if(this.__ socketLoaded) this.UpdateSubjects(data.path, this.userDb, data.data); });}
// Updated implementation for socketRef(url: string): DbObject {   let subject: DbObject < any > = this.userDbSubjects[url];   if(!subject) {      let route = url.trim().split(‘/’);      if(this.__ socketLoaded) {      / * Code as previous * /      } else {      // New object awaiting socket data      subject = new DbObject({’socketLoading’: true}, route.slice(),   this);      }
}
return subject;}
// Update any active DbObjects + Write socket changes to userDbprivate UpdateSubjects(route, store, newValue) {   this.socketMonitor.next(this._socketLoaded);   let subject: DbObject = this.userDbSubjects[route.join(‘/’)];   this.UpdateDb(route, store, newValue);   if(subject) subject.QuietUpdate(newValue);}
// Same implementation as for backendprivate UpdateDb(route, store, value) { var next = null; //Return condition (Assign value to store[next]) if(route.length <= 1) { next = route.shift(); store[next] = value; return; }; next = route.shift(); var subStore = store[next];
this.UpdateDb(route, subStore, value);}
/ * db.object.ts * /constructor(state, path, dbService) {/ * … * /if(state.socketLoading){   this.dbService.ListenForSocketLoaded().subscribe((loaded) => {   if(loaded) {       let newValue = this.NewValue;      this.QuietUpdate(newValue);  }}); }}//Update all other clientsUpdate(value: any) {   this.QuietUpdate(value);   this.dbService.SocketUpdate(val, this.dbPath);}// Receive updates from other clientsQuietUpdate(value: any) { this.next(value); }   private get NewValue(): any {   return this.dbService.DbValue(this.dbPath.slice());}

UCD Statistics & ACM, Learning Data Science, Winning Team @ Citadel Dublin Data Open. www.hugodolan.com/linkedin | Mailing List: http://eepurl.com/gkV7ov

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store