--- tags: rxjs, reactive, push title: Reactive Angular - RxJS + Facades description: Learn how to develop modern push-based, RxJS services instead of problematic pull-based services. --- # Reactive Angular: RxJS + Facades Developers should consider changing the traditional solutions they use to implement application service architectures. Instead of building traditional **'pull-based'** services and facades, developers should *flip* their mental perspectives and build solutions using **push-based services**. Before we dive into code, let's review concepts! <br/> ## Data Architectures ### Pull-based Architectures With **pull-based** architecture, the views will explicitly call service methods to force-reload (aka 'pull') the data. This is the traditional approach employed by most developers. ![](https://i.imgur.com/sTPxlwv.png) Now it is reasonable to load data asynchronous... using Observables or Promises. But those load requests are still a single-pull-request for a single-view. So even If, however, the data is shared between multiple view then a HUGE architectural concern must be considered: * "How do 1..n views know when the data is updated?" * "How do unrelated views get notified that new data is available?" * "Should uncoupled view components poll for updated data?" <br/> Pull-based solutions quickly become messy and complicated. > This is the WRONG approach! <br/> ### Push-based Architectures With RxJS and Observable streams, developers can implement architectures that PUSH data changes to all subscribers. ![](https://i.imgur.com/xFLNLiO.png) Views simply subscribe to desired data streams. When the 'remote' data changes that data will be auto-pushed through the stream [to 1..n subscribing, interested view components]. > This approach is a fundamental, HUGE paradigm shift from traditional pull-based architectures. <br/> ### Benefits What are the benefits of designing and using Push-based architectures? #### Passive Views With **push-based** services, developers can create applications using passive view components. But what are **passive** views? * Views that only render when the data arrives via a push-stream. * Views that delegate user interactions to a non-view layer * Views that require no business-logic testing * Views that require minimal isolated UX testing * Views that are performant using `ChangeDetectionStrategy.OnPush` #### NgRx not required More and more, developers are learning about NgRx and how it provides (quite elegantly) solutions for async data-push architectures. * But is NgRx always needed? * Can we just use RxJS for simple push-based services? > For simpler needs, perhaps we do not need NgRx. In such cases, we can use RxJS and still build elegant **push-based** solutions. In the following sections, let's first build a simple **pull-based** service. Afterwards, we will then convert that same service into an elegant **push-based service**. <br/> ---- ## Code Labs <br/> Let's use the online [**RandomUser**](https://randomuser.me/documentation#pagination) SaaS and build two (2) solutions: pull-based and push-based data architectures. We can then compare the implementations of **pull-based** vs **push-based** architectures. Consider an application scenario where a view component displays search options and will render display a list of users found (based on the search options). ![](https://i.imgur.com/uh1FXAy.png) As the user interacts with the view search options, the options will be used to re-query for new user data and - eventually - the view will re-render with the updated data. <br/> ### Build a *Pull*-based UserService ![](https://i.imgur.com/pWDD1Fy.png) #### 1) Define State ```typescript export interface User { gender: string; name: { first: string; last : string; } } export interface Pagination { selectedSize: number; currentPage: number; pageSizes: number[]; } export interface RandomUserResponse { results: User[]; } ``` #### 2) Implement Service ```typescript import {Observable} from 'rxjs'; import {map, tap} from 'rxjs/operators'; @Injectable() export class UserService { users: User[] = []; criteria: string = 'ngDominican'; pagination: Pagination = { selectedSize: 5, currentPage : 0, pageSizes : [5, 10, 20, 50] }; constructor(private http: HttpClient){} findAllUsers():Observable<User[]> { const url = buildUserUrl(this.criteria, this.pagination); const request$ = this.http.get<RandomUserResponse>(url).pipe( map(response => response.results), tap(list => this.users = list) ); return request$; } } function buildUserUrl(criteria:string, pagination:Pagination):string { const URL = 'https://randomuser.me/api/'; const currentPage = `page=${this.pagination.currentPage}`; const pageSize = `results=${this.selectedSize}&`; const searchFor = `seed=${this.criteria}`; return `${URL}?${searchFor}&${pageSize}&${currentPage}`; } ``` #### 3) Stackblitz Demo: Pull-Based Service <iframe width="100%" height="500" src="https://stackblitz.com/edit/facade-traditional-no-rxjs?embed=1&file=src/app/app.component.ts&view=editor" frameborder="0"></iframe> <br/> <br/> #### 4) Issues with *Pull* Service Now this ^ service has many issues. * **Danger**: * `users`, `criteria`, and `pagination` are *writable* * `criteria` and `pagination` can change WITHOUT changes to `users` * **Caution**: * `criteria` and `pagination` must be assigned before calls to `findAllUsers()` * assignments to `criteria` and `pagination` should be validated * `findAllUsers()` will return an observable AND assign to `users` * **Problem**: * If state (`criteria`,`pagination`,`users`) changes, how are consuming views notified? * What about replay features so multiple calls to `findAllUsers()` can share results? <br/> --- <br/> ### Build a *Push*-based UserService Let's redesign our service to be a push-based service. We will also want to manage internal state. Afterall, when the state [*search criteria* and *pagination*] changes, we want to: * push current state values to any 'listening' views. * auto-search for matching users and then auto-push that user list to any 'listening' views. ![](https://i.imgur.com/0Ts2ENV.png) > Let's also call these **facades**... since our service is the actual HttpClient used for REST calls. #### 1) Define the API ![](https://i.imgur.com/P9r4Rrn.png) ```typescript @Injectable() export class UserFacade { criteria$ : Observable<string>; pagination$: Observable<Pagination>; users$ : Observable<User[]>; constructor(private http: HttpClient){} updateSearchCriteria(criteria:string) {} updatePagination(selectedSize: number, currentPage: number = 0) {} } ``` #### 2) State Management ```typescript export interface UserState { users : User[]; pagination : Pagination; criteria : string; } let _state: UserState = { users : [], criteria : 'ngDominican', pagination: { currentPage : 0, selectedSize: 5, pageSizes : [5, 10, 20, 50] } }; @Injectable() export class UserFacade { private store = new BehaviorSubject<UserState>(_state); private state$ = this.store.asObservable(); } ``` #### 3) Implement the API The architecture will auto-load users whenever the `criteria` or the `pagination` settings change. We can merge the RxJS `criteria$` and `pagination$` streams to auto-load from cloud services: ![](https://i.imgur.com/DAPKjnn.png) While implementing our API with RxJS operators and streams, let's also use the lastest ES6 techniques to manage our state changes and keep our code concise and succinct. * es6 [Object Literals](https://www.sitepoint.com/es6-enhanced-object-literals/), * es6 [Spread Operators](https://basarat.gitbooks.io/typescript/docs/spread-operator.html), * es6 [Destructing](https://basarat.gitbooks.io/typescript/docs/destructuring.html), and * [Immutability Data](https://redux.js.org/faq/immutable-data) Principles <br/> ```typescript export interface UserState { users : User[]; pagination : Pagination; criteria : string; } // Internal state let _state: UserState = { users : [], criteria : 'ngDominican', pagination: { currentPage : 0, selectedSize: 5, pageSizes : [5, 10, 20, 50] } }; @Injectable() export class UserFacade { private store = new BehaviorSubject<UserState>(_state); private state$ = this.store.asObservable(); criteria$ = this.state$.pipe(map(state => state.criteria), distinctUntilChanged()); pagination$ = this.state$.pipe(map(state => state.pagination), distinctUntilChanged()); users$ = this.state$.pipe(map(state => state.users), distinctUntilChanged()); /** * Watch 2 streams to trigger user loads and state updates */ constructor(private http: HttpClient){ combineLatest(this.criteria$, this.pagination$).pipe( switchMap(([criteria, pagination])=> { return this.findAllUsers(criteria, pagination); }) ).subscribe(users => { this.store.next(_state = { ..._state, users }); }); } // ------- Public Methods ------------------------ updateSearchCriteria(criteria:string) { this.store.next(_state = { ..._state, criteria }); } updatePagination(selectedSize: number, currentPage: number = 0) { const pagination = { ..._state.pagination, currentPage, selectedSize }; this.store.next(_state = { ..._state, pagination }); } // ------- Private Methods ------------------------ private findAllUsers(criteria, pagination):Observable<User[]> { const url = buildUserUrl(criteria, pagination); return this.http.get<RandomUserResponse>(url).pipe( map(response => response.results) ); } } ``` #### 4) StackBlitz Demo: Push-Based Service <iframe width="100%" height="500" src="https://stackblitz.com/edit/facades-with-rxjs-only?embed=1&file=src/app/user.facade.ts&view=editor" frameborder="0"></iframe> <br/> <br/> --- ### Summary With this implementation of a push-based service, we establish a 1-way data flow: from view to service back to view. ![](https://i.imgur.com/VHuEtO4.png) > Note: it is critical to maintain a 1-way, cyclical-flow between views and services. And for performance reasons, it is also critical to have consistent use of immutable data structures. ![](https://i.imgur.com/zzJ04lW.png) Consider our **push-based** UserFacade illustration above; which shows the 'public API' for UserFacade. * The public **properties** (streams) provide live channels to output data. * The public **methods** enable updates to state; internally state changes will trigger auto-loads for a new user list. * Internal state (aka data) is **auto-pushed** to interested external listeners (view components, other services, etc.)! --- One of the surprising aspects of building push-based, reactive services is: *impact on UX*. ![](https://i.imgur.com/0uKnLTU.png) We reactive services, we also build reactive user experiences. Our view layers quickly evovle into passive, reactive views: * Auto-search for users for any criteria and pagination changes * UI auto-updates for pagination or criteria changes Reactive views, in turn means: * Full non-UI, business-layer testing * e2e is simply for UI layouts and style regressions <br/> ---- ### Presented at ng-Conf 2019 <iframe src="//slides.com/thomasburleson/reactive-angular-rxjs-facades/embed?token=effIpLEo" width="576" height="420" scrolling="no" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>