Front-End Architectures

All Rights Reserved 

Copyright 2021

Mindspace, LLC

Reactive Solutions

@ThomasBurleson

Reactive Architectures

  • Reactive Programming
  • Reactive Stores
  • Reactivity + Immutability
  • React with DI
  • Reactivity with Facades + RxJS

4-Part Series

Reactive Architectures

Reactive Programming

Part 1 of 5

What is

"A program is said to be reactive when an input change leads to a corresponding change in output without any need to update the output change manually."

Reactive Programming

Reactive features for UI & Services

With  raw Javascript or off-the-shelf React or Angular, we can easily make solutions that react to user interactions and component state changes....

React

Angular

JavaScript / TypeScript

Reactive Programming

  • Business Layer Updates
  • Server Data Changes
  • Service - to -Service Changes
  • Stream / Push Activity

Reactive Systems

Reactive Programming

React / Angular UI

  • UI Prop Changes
  • UI State Changes
  • UI Change Detection
  • Request-Responses Activity

+

Web protocols use:

Request - Response

Client

Server

HTTP Request

HTTP Response

  • Web apps use request-response for loading SPAs, pages, etc.
  • REST API uses request-response for remote data services
  • GraphQL API uses request-response for remote JSON

2 Protocols:

aka  Pull-based

  • Request-Response     
  • Reactive

aka  Push-based

Protocols:

Pull and Push are two different protocols that describe how a data Producer can communicate with a data Consumer.

 

 

Active

Active

Protocols:

Pull and Push are two different protocols that describe how a data Producer can communicate with a data Consumer.

 

 

Active

Active

Protocols:

Pull and Push are two different protocols that describe how a data Producer can communicate with a data Consumer.

 

 

Active

Active

Protocols:

Pull and Push are two different protocols that describe how a data Producer can communicate with a data Consumer.

 

 

Active

Active

Protocols:

Pull and Push are two different protocols that describe how a data Producer can communicate with a data Consumer.

 

 

Active

Active

Pull Protocol

What is 

  • the Consumer determines when it receives data from the data Producer.
  • the Producer itself is unaware of when the data will be delivered to the Consumer.
  • Producer sends a single (1x) response

 

pull data

Producer

Consumer

Every JavaScript Function is a Request-Response (Pull) system.

 

 

The function is a Producer of data, and the code that calls the function is consuming it by “pulling” out a single return value from its call.

map() is an built-in synchronous, request-reponse function.

  • map() extracts or PULLs the data from an array items.
  • reduce() extracts or PULLs the data from an array.

Request-Response

Request-Response

setPageSize() is a custom synchronous request-response function.

Asynchronous

Data service request is synchronous

The response is a promise of a SINGLE future value... asynchronous

const pending: Promise<User[]> = userService.loadAllUsers();
const onResponse = (users: User[]) => console.table(users);

pending.then(onResponse);

Request-Response

async function loadUsers() {
  const users: User[] = await userService.loadAllUsers();  
  return users;
}

Asynchronous

Request-Response

Using async/await allows us to consider asynchronous requests asa synchronous responses...

Protocols

  • Easy
  • 1 request == 1 response
  • Traditional... what you learned!

View1

Service1

UserService

loadUsers()

Pull-based

Request-Response

  • Does not scale
  • Does not promote separation of concerns
  • Hard to implement correctly

SearchTerm

findAllUsers( term)

Protocols

Request-Response

Live Demo

  • Danger:
    • criteria and pagination can change without changes to users list
  • Problem:
    • If criteria, pagination, or users changes, how are the views notified?
    • What about replay features so multiple calls to findAllUsers() can share results?

Horrible UX

Protocols

Request-Response

Protocols

Request-Response

... another Horrible UX

Reactive Approach ?

  • Configure your code to watch for 1..n changes.
  • Your code 'reacts' to changes in state, data, and user interactions...
  • Your code avoids global re-renders and updates only components affected by changes.
  • Your code could react without UI components...
  • When parent UI changes, react ...
  • When state changes, react ...

UI

Hooks

React:  Reactive UI


export type FiltersProps = {
  selectedFilter: string;
};

export const Filters: React.FC<FiltersProps> = ({ selectedFilter }) => {
  return (
    <IonItem style={inlineItem}>
      <IonLabel>Show:</IonLabel>
      <IonSelect value={selectedFilter} >
        <IonSelectOption value="SHOW_ALL">All</IonSelectOption>
        <IonSelectOption value="SHOW_ACTIVE">Active</IonSelectOption>
        <IonSelectOption value="SHOW_COMPLETED">Completed</IonSelectOption>
      </IonSelect>
    </IonItem>
  );
};

Prop changes will trigger the Filters component to react and update its associated DOM

React:  Reactive UI

Parent UI          Child UI

Prop changes will trigger the ToggleFilters component to react and update its associated DOM

Angular:  Reactive UI

Parent UI          Child UI

React:  Reactive UI

export function Counter({onIncrement}) {
  return (<div>

      <button onClick={OnIncrement}> + </button>

  </div>);
}

Here the onClick handler will react to user clicks to trigger increment action processing.

User Interaction           Parent UI

User Interaction           Parent UI

Angular:  Reactive UI

Even Redux should be considered a Request-Reponse (pull) approach.

Redux

A solution for state management

Let's see why...

React + Redux

import { useReducer } from 'react';

const initialState = { count: 0 };
const reducer = (state, action) => {
  swith(action.type) {
    case 'increment': return { count: state.count + action.payload }
  }
};

export function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const onIncrement = () => dispatch({ type: 'increment', payload: 1 });
  
  return (<div>
      Count: {state.count}
      <button onClick={OnIncrement}> + </button>
  </div>);
}
export function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const onIncrement = () => dispatch({ 
    type: 'increment', 
    payload: 1 
  });
  
  return (<div>
      Count: {state.count}
      <button onClick={OnIncrement}> + </button>
  </div>);
}

We consider OTS Redux to be a request-response solution. With the React Hook, however, it becomes reactive.

React + Redux

Reactive 

We want to build

Components

{

UI

Services

React

Angular

JavaScript / TypeScript

Reactive UI Components

With event listeners and react hooks, we can build Reactive UIs.

 

 

This is only a partial solution!

UI

Hooks

Reactive UI Components

With event listeners and CD, we can build Reactive UIs.

 

 

This is only a partial solution!

UI

Hooks

UI

Change

Dectection

Reactive Pathways

  • UI  <== reacts to === user
  • UI  <== reacts to === UI

?

?

  • UI  <== reacts to === Service
  • Service <== reacts to === Service

What are the paths to building Reactive solutions ?

{

React framework makes this "easy"

{

Where the hard architecture work is done

Reactivity for All Layers

Business

Data

Services

Business

UI

Reactivity for All Layers

Business

Data

Services

Business

UI

Change

Detection

Reactivity for All Layers

Business

Data

Services

Business

UI

View Models

Change

Detection

  • Application startup
  • Forms are dirty
  • URL or params change
  • Props change
  • State changes
  • Data changes
  • Service changes
  • etc.

Reactive Solutions

We want to "react" when:

We want to be smart and react only for specific changes in specific areas... we want use selectors to decide what changes are important.

Push Protocol

... the secret to Reactivity!

Push Protocol

What is

  • the Producer determines when it to send data to the Consumer.
  • The Consumer itself is unaware of when it will receive the data.
  • Multiple values can be pushed...

 

Push

Producer

Consumer

(active)

(passive)

  • Listen, or
  • Observe

Reactive Solutions

Before we can react to 1..n notifications/changes , we must first:

Before we can react to 1..n notifications/changes , we must first:

... and we must be able to observe multiple events without extra work.

Observer Pattern

Registration

Notifications

pingBtn.addEventListener('click', (e: MouseEvent => {
  console.log('Clicked Ping');
})

Source

(Producer)

Destination

(Consumer)

Build a long-term, multi-event connection...

Simple

Observer Pattern

export function Counter() {
  const onIncrement = (e: MouseEvent<HTMLButtonElement>) => {
    console.log('Clicked');
  }
  
  return (<div>
      <button onClick={OnIncrement}> Ping </button>
  </div>);
}

React  with event listeners

Build a long-term, multi-event connection...

Registration

Notifications

Source

(Producer)

Destination

(Consumer)

Registration

Notifications

Source

<<Observable>>

Destination

<<Observer>>

Observer Pattern

Simple API using

An Observable is a 'wrapper' around a Producer of multiple values, "pushing" them to Consumers

Observer Pattern

RxJS Implementation of

Producer

Consumer

push notifications

<<Observable>>

An Observer is a 'proxy' around a Consumer ... used to connect to a pipe connecting to an Observer

Producer

Consumer

push notifications

<<Observable>>

<<Observer>>

Observer Pattern

RxJS Implementation of

Producer

Consumer

subcribe()

next()

<<Observer>>

<<Observable>>

Observer Pattern

RxJS API using the

Producer

Consumer

subcribe()

next()

<<Observer>>

<<Observable>>

Observer Pattern

RxJS API using the

Create observable around 'document' producer

Normally you register event listeners.

document.addEventListener('click', () => console.log('Clicked!'));

Using RxJS you create an observable instead.

import { fromEvent } from 'rxjs';

fromEvent(document, 'click').subscribe(() => console.log('Clicked!'));

Observer Pattern

Comparisons using

Build connection to events for 'clicks'

Normally you register event listeners.

document.addEventListener('click', () => console.log('Clicked!'));

Using RxJS you create an observable instead.

import { fromEvent } from 'rxjs';

fromEvent(document, 'click').subscribe(() => console.log('Clicked!'));

Observer Pattern

Comparisons using

React and log click to Console

Note: the consumer is Console 

Normally you register event listeners.

document.addEventListener('click', () => console.log('Clicked!'));

Using RxJS you create an observable instead.

import { fromEvent } from 'rxjs';

fromEvent(document, 'click').subscribe(() => console.log('Clicked!'));

Observer Pattern

Comparisons using

RxJS Observer Pattern

Producer

Consumer

<<Observer>>

<<Observable>>

Why is the RxJS version so powerful?

Transformations

(using Operators)

Stream

Stream

RxJS Observer Pattern

Producer

Consumer

Why is the RxJS version so powerful?

Transformations

(using Operators)

Producer

Combine

Streams

 

Stream

Stream

Stream

<<Observer>>

<<Observable>>

<<Observable>>

Observables allow us to change the way we build applications!

 

Instead of `pulling` data when needed, we will build applications that configure stream connections. With stream connections, data is `pushed` whenever it updates.

 

 

 

RxJS Observer Pattern

Demo #1

Observer Push

Visualize the 1-way data flows with Push-based Observer architecture:

Demo #1

Observer Push

Demo #2

Observer Push

dashboard.component.html

Demo #3

Observer Push

import { useState } from 'react';
import { useObservable } from '@mindspace-io/react';
import { makeFacade, TodosFacade } from './todos.facade';
import { VISIBILITY_FILTER as v, Todo } from './todo.model';

export type TodoHookTuple = [string, Todo[], TodosFacade];

export function useTodosFacade(): TodoHookTuple {
  const [facade] = useState(() => makeFacade());
  const [filter] = useObservable(facade.filter$, v.SHOW_ALL);
  const [todos] = useObservable(facade.todos$, []);

  return [filter, todos, facade];
}
export const TodosPage: React.FC = () => {
  const [filter, todos, facade] = useTodosFacade();
  
  const addItem = useMemo(facade.addTodo(item), [facade]);
  const updateFilter = useMemo(facade.updateFilter, [facade]);

  return (
    <>
      <div style={todoBar}>
        <AddTodo onAdd={ addItem } showHint={!todos.length} />
        <Filters onChange={ updateFilter } selectedFilter={filter} />
      </div>

      <TodoList
        todos={todos}
        onToggle={(item) => facade.toggleComplete(item)}
        onDelete={(item) => facade.deleteTodo(item)}
      />

    </>
  );
};

Observer Push

Demo #1

Consider the ObservableHq which builds on concepts of 'reactive' everything to enable visualizations and live notebooks.

Reactive SaaS

  • MobX
  • Recoil
  • Zustand

Reactive Libraries

  • NgRx
  • @ngrx/component-store
  • Reactive Store

 

  • Facades + RxJS
  • SolidJS

Part 2 of 5

Next:

Reactive Stores

Reactive Architectures

React Developers

Reactive Architectures - Part 1

By Thomas Burleson

Private

Reactive Architectures - Part 1

Learn about Reactive architectures, presented in a 5-part series. This Part 1: Reactive Programming contrasts traditional request-response solutions with modern Reactive approaches that leverage the Observer pattern and RxJS.