Modern Front-End Development...Timeline of JS UI Libs dom manipulation mvc/mvvm component based...
Transcript of Modern Front-End Development...Timeline of JS UI Libs dom manipulation mvc/mvvm component based...
-
Modern Front-End Development
Nov 2020
-
@ddprrtProduct Architect @ Dynatrace Web dev since 1997 JKU 2006 Alumni
podcasting ! workingdraft.de
writing ! fettblog.eu
http://workingdraft.dehttp://fettblog.euhttp://workingdraft.dehttp://fettblog.eu
-
typescript-book.com
http://typescript-book.comhttp://typescript-book.com
-
Table of Contents! Web Apps Then and Now ! Component based Front-End development ! Managing State with Flux based Architectures ! TypeScript ! Progressive Enhancement, SSR, Rehydration ! JAMStack + Serverless ! Outlook to future topics
-
What we don’t do" Learning HTML and HTML 5
" No CSS in here
" No basic JavaScript
" No HTTP, HTTP/2, HTTP/3 or other networking
" No performance optimisations
" No accessibility
All those things are highly important! Learn them!
-
Web Apps Then And Now
-
How good is your JavaScript?jQuery ma friend! I can translate my Java to JavaScript I feel comfortable with destructuring and Object mechanics I know all about Closures, Hoisting, Scope I know the inner workings of the Prototype Chain
Vote: https://www.strawpoll.me/21225842
-
Web requests
Browser
UI
Server
DB, ..
HTTP request
HTML+CSS
-
HTML CSS JAVASCRIPT
Semantics Presentation Behaviour
-
Web requests
Browser
UI
Server
DB, ..
HTTP request
HTML+CSS
-
AJAX
-
Asynchronous JavaScript and XML
Browser
UI
Server
DB, ..
HTTP request
HTML+CSS
Browser
UI
Server
DB, ..
HTTP request
HTML, CSS JSON, XML
AJAX Engine
JS call HTML
-
Why do we make it more complicated?
-
Benefits of AJAX! Tailored Requests
! Improved UX (Feedback, Error handling, etc.)
! Allows for Dynamic User Interfaces
-
Drawbacks! Needs JS
! Can be hard on Accessibility
! Tons of possibilities to hack stuff
! The URL doesn’t mirror the state of the application anymore
-
URLs
-
History APILet’s say we have a blog with two articles.
A
http://blog.site/first-article
B
http://blog.site/second-article
http://blog.site/first-articlehttp://blog.site/first-articlehttp://blog.site/first-articlehttp://blog.site/first-article
-
History APIWe call the URL of page two and have a full blown request
A
http://blog.site/first-article
B
http://blog.site/second-article
klick on Link server sends data
http://blog.site/first-articlehttp://blog.site/first-articlehttp://blog.site/first-articlehttp://blog.site/first-article
-
History APIBut we only need content B! Not the whole thing
A
http://blog.site/first-article
Please give me B
http://blog.site/first-articlehttp://blog.site/first-article
-
History APIBut we only need content B! Not the whole thing
B
http://blog.site/first-article
There you go
http://blog.site/first-articlehttp://blog.site/first-article
-
History APINow the URL and the content don’t fit together anymore
B
http://blog.site/first-article
☹
http://blog.site/first-articlehttp://blog.site/first-article
-
History APIThe history API can change this:
window.history.pushState(stateObj, title, url)
-
Spectrum of Web Apps
Only server side Only client side
-
Basic ingredients! JavaScript
! AJAX (XMLHttpRequest)
! Routing: The History API
Frameworks can help you, but are not mandatory
-
Component based Front-End Development
-
Did you work with a JS FW?React Angular Vue Ember something else
Vote: https://www.strawpoll.me/21225858
https://www.strawpoll.me/21225858https://www.strawpoll.me/21225858
-
Timeline of JS UI Libs
dom manipulation mvc/mvvm component based
jQuery Dojo YUI
Backbone Angular.js
First Ember
React Vue
Angular Ember now
-
Downsides of DOM Manip. approaches! DOM based libraries are always tightly coupled to the
markup underneath
! Hard to introduce changes
! No sense of global state or interconnection between manipulated DOM elements
! Less descriptive, more imperative
-
Tags:
var availableTags = [ "ActionScript", “AppleScript” ]; $( "#tags" ).autocomplete({ source: availableTags });
Example: jQuery UI
-
Downsides of MVVM libraries! All MVVM libraries or frameworks come with their own
architecture.
! High learning curve, lots of concepts to ingest
! Lots of boilerplate code needed to get started, hard to extend (this was also a sign of their times)
! Actual templating and Markup creation was often externalised in its own library
-
Example: Backbone
https://backbonejs.org/docs/todos.html
https://backbonejs.org/docs/todos.htmlhttps://backbonejs.org/docs/todos.html
-
Component based libaries! Components encapsulate functionality and view
! They are composable, nestable and reusable
! Don’t use existing markup and enhance, but let the component create the markup itself.
! In theory: Framework agnostic (See JSX)
! Scope can vary a lot
-
JS frameworks
More a library Primitives “Bazaar”
Frameworks Abstractions “Cathedral”
-
JS frameworks
-
Small scope pros! Fewer concepts to get started with
! More flexibility and more userland opportunities: active ecosystem
! Smaller maintenance surface for the team
-
Small scope cons! More plumbing work needed when solving inherent
complex problems with simple concepts
! Patterns naturally emerge over time and become semi-required knowledge, but often not officially documented
! Ecosystem moving too fast can lead to fragmentation and constant churn
-
Large scope pros! Most common problems can be solved with built-in
abstractions
! Centralised design process ensures consistent and coherent ecosystem
-
Large scope cons! Higher upfront learning barrier
! Inflexible if built-in solution doesn’t fit the use case
! Larger maintenance surfaces makes introducing fundamental new ideas much more costly
-
Template basedProducts {{ product.name }}
https://angular.io/api/common/NgForOfhttps://angular.io/api/common/NgForOf
-
Template based! Template based JavaScript frameworks usually use their
own template language to bind data to output
! The power of templates can vary a lot: Logic-free to Turing complete
! Templates get compiled at build time or at runtime into render engine instructions
-
{{title}}
template: function AppComponent_Template(rf, ctx) { if (rf & 1) { i0.ɵɵelementStart(0, "div"); i0.ɵɵelementStart(1, "span"); i0.ɵɵtext(2); i0.ɵɵelementEnd(); i0.ɵɵtemplate(3, AppComponent_app_child_3_Template, 1, 0, "app-child", _c0); i0.ɵɵelementEnd(); } if (rf & 2) { i0.ɵɵselect(2); i0.ɵɵtextBinding(2, i0.ɵɵinterpolation1("", ctx.title, "")); i0.ɵɵselect(3); i0.ɵɵproperty("ngIf", ctx.show); } }
-
Template based! Template based is really performant when they’re compiled
ahead of time
! Compiling templates at runtime is more costy
! Templates can be extended through the framework (or library). So called “Directives” allow for more logic inside templates
! Templates always introduce their own domain
-
Template based! Angular was always Template based
! Ember is built on top of Handlebars
! Vue.js has a similar template syntax to Angular
-
JSX! JSX is syntactic sugar for function calls, that mimics HTML
! Everything that is a tree structure, can be done with JSX
class ShoppingList extends React.Component { render() { return ( Shopping List for {this.props.name} Instagram ); } }
-
JSX! lowercase elements are rendered to strings, uppercase
elements are rendered to components
! This goes directly to the render engine!
! You get the full expressiveness of JavaScript
! JSX became the defacto standard: Dojo, Vue, Preact, React, Stencil, …
Hello World
React.createElement(‘h1’, { className: “headline” }, “hello World”)
-
JSX! JSX is not part of JavaScript
! You do need a compile time step to transpile JSX to function calls
! Runtime compilation of JSX is not the norm anymore (you would need to run a full JavaScript transpiler for that)
! Babel, TypeScript can compile JSX easily.
-
Props! Props are a JSX element’s attributes. As in: Stuff that’s
controlled from the outside. You can react inside a component
! In Angular, those props are called inputs.
-
Setting state
-
State! Just rendering UI is only one thing that’s crucial. We need to
bind our templates or JSX to actual data and allow to change data
! There’s again two ways: Actively pushing for new state (setState calls)
! Dirty checking: Changing properties and letting the engine figure out what has changed
-
Change Detection
-
Change Detection
The basic task of change detection is to take the internal state of a programand make it somehow visible to the user interface
blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html
-
Change Detection
@Component({ selector: 'greeter-component', templateUrl: ‘greeter.component'})class GreeterComponent { name: string = 'Anonymous';}
Hello {{name}}
greeter.component.ts
greeter.component.html
Hello Anonymous
updateName() { this.name = 'World';}
greeter.component.ts
Hello World
?
Change Detection
When does it change? What changed? Tell view to update!
-
Developer tells the framework when something has change and what needs to be updated by calling functions like setState or by using hooks.
Angular offers 2 way for CD:
Change Detection Various approaches
React Vue Angular
Vue overrides state with getters and setters that include logic to tell the framework when something has changed (marking them as “touched”).
It detects changes automatically without changing your code or the need to call certain functions. You can Opt-Out of this automatism on global or
You are able call methods like markForCheck or detectChanges on the ChangeDetectorRef of a component if you want to trigger it by hand.
-
Change Detection
When does the the state of a component change?
-
Change Detection
When does the the state of a component change?
Timeout & Interval
Promise
async *
Http request
User event
Execution context
-
Change Detection
When does the the state of a component change?
Timeout & Interval
Promise
async *
Http request
User event
Zone
zone.js
-
Change Detection
C AppComponent
C HeaderComponent
C SettingsComponent
C ButtonComponent
C AppModule
A ApplicationRef
@Component({ selector: 'app-settings', templateUrl: 'settings.component'})class SettingsComponent { featureFlag: boolean = false;
}
toggleFeatureFlag() { this.featureFlag = !this.featureFlag; }
Feature flag {{featureFlag ? 'enabled' : 'disabled'}}
Toggle(click)=“toggleFeatureFlag()”
featureFlag:Output: Feature flag
Result
disabledfalse
Zone
-
Change Detection
C AppComponent
C HeaderComponent
C SettingsComponent
C ButtonComponent
C AppModule
A ApplicationRef
Feature flag {{featureFlag ? 'enabled' : 'disabled'}}
Toggle(click)=“toggleFeatureFlag()”
featureFlag:Output: Feature flag
Result
disabledfalse
click
true disabled
Zone
@Component({ selector: 'app-settings', templateUrl: 'settings.component'})class SettingsComponent { featureFlag: boolean = false;
toggleFeatureFlag() { this.featureFlag = !this.featureFlag; }}
-
Change Detection
C AppComponent
C HeaderComponent
C SettingsComponent
C ButtonComponent
C AppModule
A ApplicationRef
Feature flag {{featureFlag ? 'enabled' : 'disabled'}}
Toggle(click)=“toggleFeatureFlag()”
featureFlag:Output: Feature flag
Result
disabled
click
true
Zone
@Component({ selector: 'app-settings', templateUrl: 'settings.component'})class SettingsComponent { featureFlag: boolean = false;
toggleFeatureFlag() { this.featureFlag = !this.featureFlag; }}
ChangeDetection!
-
Change Detection
When does the the state of a component change?
-
Change Detection
When does the the state of a component change?
Timeout & Interval
Promise
async *
Http request
User event
Zone
-
Change Detection
C AppComponent
C HeaderComponent
C SettingsComponent
C ButtonComponent
C AppModule
A ApplicationRef
click
Zone
Toggle
// NOTE: Not actual code!!!buttonEl.addEventListener(() => { settingsComponent.toggleFeatureFlag();});
New Task
State has changed!
-
Change Detection
What exactly has been changed?
-
Change Detection
C AppComponent
C HeaderComponent
C SettingsComponent
C ButtonComponent
C AppModule
A ApplicationRefZone
ngZone.onMicrotasksEmpty() .subscribe(() => tick());
detectChanges() Dirty!
-
Change Detection
C AppComponent
C HeaderComponent
C SettingsComponent
C ButtonComponent
C AppModule
A ApplicationRefZone
ngZone.onMicrotasksEmpty() .subscribe(() => tick());
detectChanges()
Dirty!
-
Change Detection
C AppComponent
C HeaderComponent
C SettingsComponent
C ButtonComponent
C AppModule
A ApplicationRefZone
ngZone.onMicrotasksEmpty() .subscribe(() => tick());
detectChanges()
Dirty!Render View
featureFlag: trueOutput: Feature flag
Result
disabled enabled
-
Change Detection
C AppComponent
C HeaderComponent
C TableComponent
C ChartComponent
C AppModule
A ApplicationRefZone
CSliderComponent
x 1000
click
detectChanges()
-
Change Detection
C AppComponent
C HeaderComponent
C TableComponent
C ChartComponent
C AppModule
A ApplicationRefZone
CSliderComponent
x 1000
$ Performance issues
-
Change Detection
ChangeDetectionStrategy.OnPush
• Runs CD only if an input changes• Is inherited down the component tree• Does not remove a component from the ngZone
-
Change Detection
C AppComponent
C HeaderComponent
C TableComponent
C ChartComponent
C AppModule
A ApplicationRefZone
CSliderComponent
@Component({ selector: 'demo-table', templateUrl: 'table.component', changeDetection: ChangeDetectionStrategy.OnPush,})class TableComponent { }
-
Change Detection
C AppComponent
C HeaderComponent
C TableComponent
C ChartComponent
C AppModule
A ApplicationRefZone
CSliderComponent
OnPush
OnPushOnPush
click
-
Change Detection
C AppComponent
C HeaderComponent
C TableComponent
C ChartComponent
C AppModule
A ApplicationRefZone
CSliderComponent
OnPush
OnPushOnPush
detectChanges()
-
Change Detection
C AppComponent
C HeaderComponent
C TableComponent
C ChartComponent
C AppModule
A ApplicationRefZone
CSliderComponent
OnPush
OnPushOnPushdrag
@Component({ selector: 'demo-slider', templateUrl: 'slider.component',})class SliderComponent { handleDrag() { // do something }}
-
Change Detection
C AppComponent
C HeaderComponent
C TableComponent
C ChartComponent
C AppModule
A ApplicationRefZone
CSliderComponent
OnPush
OnPushOnPush
detectChanges()
@Component({ selector: 'demo-slider', templateUrl: 'slider.component',})class SliderComponent { handleDrag() { // do something }}
-
We need to tell Angular manually when the internal state has changed
Change Detection
-
Change Detection
ChangeDetectorRef
-
Change Detection
C AppComponent
C HeaderComponent
C TableComponent
C ChartComponent
C AppModule
A ApplicationRefZone
CSliderComponent
OnPush
OnPushOnPush
@Component({ selector: 'demo-slider', templateUrl: 'slider.component',})class SliderComponent {
handleDrag() { // do something
}}
this._changeDetectorRef.markForCheck();
constructor(private _changeDetectorRef: ChangeDetectorRef) {}
Dirty!
-
Change Detection
Key Takeaways
-
Change Detection
Key Takeaways
• Angular use ngZone to determine when to run CD• CD tries to find state/components that have changed• Set ChangeDetectionStrategy to OnPush when hitting performance issues• OnPush only runs CD when an input has changed (not internal state)• ChangeDetectionStrategy is inherited• Trigger CD manually using ChangeDetectorRef.markForCheck
-
Virtual DOM
-
Virtual DOM! JavaScript is fast (seriously), the browser’s DOM isn’t
! Updates to the actual DOM are costly and expensive
! React introduced the concept of the Virtual DOM, a data structure with operations that mimics the DOM, but runs in memory only
! Here you can do fast updates and later on batch updates to the actual DOM
-
h(‘div’, { className: “container” }, h(‘List’, { items: {this.state.items })); div
List
JSX
JS VDOM
DOM
transform
executerender
{ "nodeName": "", "attributes": {}, "children": [] }
-
div
List
div
Diff! Apply patch
List
-
Virtual DOM: Updates! With every event, React (Preact, Vue) builds a new Virtual
DOM representation
! React diffs this representation with the actual DOM
! It computes the minimal DOM mutation and does an update
! The key to performance is that the updates are minimal, and batch execution safe a ton of time.
! Other than that, Virtual DOM can be really slow!
-
ComparisonReact Vue Angular
JSX ✅ ✅ ❌
Templates ❌ ✅ ✅
VDOM ✅ ✅ ❌
Routing ❌ ✳ ✅
Compiler ✅ ✳ ✅
State change Active Active Dirty checking
-
Short recap! Almost everything now is component based.
! Props: Input from outside
! State: Every component has it’s own state, that can be changed
! Events: Used to change state, talk to components outside
-
But what if we need to manage a lot of state
-
Flux based architectures
-
Statemanagement & NgRx
-
Statemanagement & NgRx
Flux
-
Statemanagement & NgRx
Flux
• Flux pattern - is widely adopted convention
• One directional data flow
• Great Tooling – (time travel debugging, inspect state – single source of truth)
• State is handled as an async reactive stream with default states, reinforcing app responsiveness
-
Statemanagement & NgRx
Flux
Action Reducer State View
-
Statemanagement & NgRx
Redux
-
Statemanagement & NgRxAction Reducer State View
const store = createStore(counter) const rootEl = document.getElementById('root')
const render = () => ReactDOM.render( store.dispatch({ type: 'INCREMENT' })} onDecrement={() => store.dispatch({ type: 'DECREMENT' })} />, rootEl )
render() store.subscribe(render)
-
Statemanagement & NgRxAction Reducer State View
const store = createStore(counter) const rootEl = document.getElementById('root')
const render = () => ReactDOM.render( store.dispatch({ type: 'INCREMENT' })} onDecrement={() => store.dispatch({ type: 'DECREMENT' })} />, rootEl )
render() store.subscribe(render)
-
Statemanagement & NgRxAction Reducer State View
export default (state = 0, action) => { switch (action.type) { case 'INCREMENT': return state + 1 case 'DECREMENT': return state - 1 default: return state } }
-
Statemanagement & NgRxAction Reducer State View
const store = createStore(counter) const rootEl = document.getElementById('root')
const render = () => ReactDOM.render( store.dispatch({ type: 'INCREMENT' })} onDecrement={() => store.dispatch({ type: 'DECREMENT' })} />, rootEl )
render() store.subscribe(render)
-
Statemanagement & NgRxAction Reducer State View
const store = createStore(counter) const rootEl = document.getElementById('root')
const render = () => ReactDOM.render( store.dispatch({ type: 'INCREMENT' })} onDecrement={() => store.dispatch({ type: 'DECREMENT' })} />, rootEl )
render() store.subscribe(render)
-
Statemanagement & NgRx
Redux
• A more elaborate example: https://github.com/reduxjs/redux/tree/master/examples/todomvc/src
• Good for big apps with lots of modifications
https://github.com/reduxjs/redux/tree/master/examples/todomvc/srchttps://github.com/reduxjs/redux/tree/master/examples/todomvc/srchttps://github.com/reduxjs/redux/tree/master/examples/todomvc/srchttps://github.com/reduxjs/redux/tree/master/examples/todomvc/srchttps://github.com/reduxjs/redux/tree/master/examples/todomvc/srchttps://github.com/reduxjs/redux/tree/master/examples/todomvc/src
-
Statemanagement & NgRx
Vuex
• Vue has something similar called Vuex
-
Statemanagement & NgRx
Flux
Action Reducer State View
-
Statemanagement & NgRx
Action Reducer State Selectors Facade View
Effect
@Component({ selector: 'app-pizza-item', animations: [DROP_ANIMATION], changeDetection: ChangeDetectionStrategy.OnPush, styleUrls: ['pizza-item.component.scss'], templateUrl: 'pizza-item.component.html', }) export class PizzaItemComponent { @Input() pizza: Pizza; }
-
Statemanagement & NgRx
Action Reducer State Selectors Facade View
Effect
@Component({ selector: 'app-pizza-item', animations: [DROP_ANIMATION], changeDetection: ChangeDetectionStrategy.OnPush, styleUrls: ['pizza-item.component.scss'], templateUrl: 'pizza-item.component.html', }) export class PizzaItemComponent { @Input() pizza: Pizza; }
@Component({ selector: 'app-products', templateUrl: './products.component.html', styleUrls: ['./products.component.scss'] }) export class ProductsComponent implements OnInit { pizzas$: Observable;
Smart Dumb
}
-
Statemanagement & NgRx
Action Reducer State Selectors Facade View
Effect
@Component({ selector: 'app-pizza-item', animations: [DROP_ANIMATION], changeDetection: ChangeDetectionStrategy.OnPush, styleUrls: ['pizza-item.component.scss'], templateUrl: 'pizza-item.component.html', }) export class PizzaItemComponent { @Input() pizza: Pizza; }
@Component({ selector: 'app-products', templateUrl: './products.component.html', styleUrls: ['./products.component.scss'] }) export class ProductsComponent implements OnInit { pizzas$: Observable;
Container Component
}
-
Statemanagement & NgRx
Action Reducer State Selectors Facade View
Effect
@Component({ selector: 'app-pizza-item', animations: [DROP_ANIMATION], changeDetection: ChangeDetectionStrategy.OnPush, styleUrls: ['pizza-item.component.scss'], templateUrl: 'pizza-item.component.html', }) export class PizzaItemComponent { @Input() pizza: Pizza; }
@Component({ selector: 'app-products', templateUrl: './products.component.html', styleUrls: ['./products.component.scss'] }) export class ProductsComponent implements OnInit { pizzas$: Observable;
Logical Presentational
}
-
Statemanagement & NgRx
Action Reducer State Selectors Facade View
Effect
@Injectable() export class PizzasFacade { constructor(private store: Store) {}
loadAll() { } }
@Component({ selector: 'app-products', templateUrl: './products.component.html', styleUrls: ['./products.component.scss'] }) export class ProductsComponent implements OnInit { pizzas$: Observable; }
-
Statemanagement & NgRx
Action Reducer State Selectors Facade View
Effect
@Injectable() export class PizzasFacade { constructor(private store: Store) {}
loadAll() { } }
@Component({ selector: 'app-products', templateUrl: './products.component.html', styleUrls: ['./products.component.scss'] }) export class ProductsComponent implements OnInit { pizzas$: Observable;
}
constructor(private _pizzaFacade: PizzasFacade) {}
ngOnInit(): void { this._pizzaFacade.loadAll(); }
-
Statemanagement & NgRx
Action Reducer State Selectors Facade View
Effect
@Injectable() export class PizzasFacade { constructor(private store: Store) {}
loadAll() { } }
-
Statemanagement & NgRx
Action Reducer State Selectors Facade View
Effect
@Injectable() export class PizzasFacade { constructor(private store: Store) {}
loadAll() { } }
export type PizzasAction = LoadPizzas;
export const fromPizzasActions = { LoadPizzas, };
this.store.dispatch(new LoadPizzas());
export enum PizzasActionTypes { LoadPizzas = '[Pizzas] Load Pizzas', }
export class LoadPizzas implements Action { readonly type = PizzasActionTypes.LoadPizzas; }
-
Statemanagement & NgRx
Action Reducer State Selectors Facade View
Effect
@Injectable() export class PizzasEffects { @Effect() loadPizzas$ = this.actions$.pipe( ofType(PizzasActionTypes.LoadPizzas), switchMap(() => this._pizzaService.getPizzas()),
)
constructor( private actions$: Actions, private _pizzaService: PizzaService, ) {} }
export type PizzasAction = LoadPizzas;
export const fromPizzasActions = { LoadPizzas, };
export enum PizzasActionTypes { LoadPizzas = '[Pizzas] Load Pizzas', }
export class LoadPizzas implements Action { readonly type = PizzasActionTypes.LoadPizzas; }
-
Statemanagement & NgRx
Action Reducer State Selectors Facade View
Effect
export enum PizzasActionTypes { LoadPizzas = '[Pizzas] Load Pizzas', }
export class LoadPizzas implements Action { readonly type = PizzasActionTypes.LoadPizzas; }
@Injectable() export class PizzasEffects { @Effect() loadPizzas$ = this.actions$.pipe( ofType(PizzasActionTypes.LoadPizzas), switchMap(() => this._pizzaService.getPizzas()),
)
constructor( private actions$: Actions, private _pizzaService: PizzaService, ) {} }
export class PizzasLoadError implements Action { readonly type = PizzasActionTypes.PizzasLoadError; }
export class PizzasLoaded implements Action { readonly type = PizzasActionTypes.PizzasLoaded; constructor(public payload: Pizza[]) {} } export type PizzasAction = LoadPizzas | PizzasLoaded | PizzasLoadError;
export const fromPizzasActions = { LoadPizzas, PizzasLoaded, PizzasLoadError, };
-
Statemanagement & NgRx
Action Reducer State Selectors Facade View
Effect
export enum PizzasActionTypes { LoadPizzas = '[Pizzas] Load Pizzas', }
export class LoadPizzas implements Action { readonly type = PizzasActionTypes.LoadPizzas; }
map((pizzas) => new fromPizzasActions.PizzasLoaded(pizzas)), catchError(() => of(new PizzasLoadError()))
@Injectable() export class PizzasEffects { @Effect() loadPizzas$ = this.actions$.pipe( ofType(PizzasActionTypes.LoadPizzas), switchMap(() => this._pizzaService.getPizzas()),
)
constructor( private actions$: Actions, private _pizzaService: PizzaService, ) {} }
export class PizzasLoadError implements Action { readonly type = PizzasActionTypes.PizzasLoadError; }
export class PizzasLoaded implements Action { readonly type = PizzasActionTypes.PizzasLoaded; constructor(public payload: Pizza[]) {} } export type PizzasAction = LoadPizzas | PizzasLoaded | PizzasLoadError;
export const fromPizzasActions = { LoadPizzas, PizzasLoaded, PizzasLoadError, };
-
Statemanagement & NgRx
Action Reducer State Selectors Facade View
Effect
export enum PizzasActionTypes { LoadPizzas = '[Pizzas] Load Pizzas', }
export class LoadPizzas implements Action { readonly type = PizzasActionTypes.LoadPizzas; }
map((pizzas) => new fromPizzasActions.PizzasLoaded(pizzas)), catchError(() => of(new PizzasLoadError()))
@Injectable() export class PizzasEffects { @Effect() loadPizzas$ = this.actions$.pipe( ofType(PizzasActionTypes.LoadPizzas), switchMap(() => this._pizzaService.getPizzas()),
)
constructor( private actions$: Actions, private _pizzaService: PizzaService, ) {} }
export class PizzasLoadError implements Action { readonly type = PizzasActionTypes.PizzasLoadError; }
export class PizzasLoaded implements Action { readonly type = PizzasActionTypes.PizzasLoaded; constructor(public payload: Pizza[]) {} } export type PizzasAction = LoadPizzas | PizzasLoaded | PizzasLoadError;
export const fromPizzasActions = { LoadPizzas, PizzasLoaded, PizzasLoadError, };
-
Statemanagement & NgRx
Action Reducer State Selectors Facade View
Effect
export enum PizzasActionTypes { LoadPizzas = '[Pizzas] Load Pizzas', }
export class LoadPizzas implements Action { readonly type = PizzasActionTypes.LoadPizzas; }
export class PizzasLoadError implements Action { readonly type = PizzasActionTypes.PizzasLoadError; }
export class PizzasLoaded implements Action { readonly type = PizzasActionTypes.PizzasLoaded; constructor(public payload: Pizza[]) {} } export type PizzasAction = LoadPizzas | PizzasLoaded | PizzasLoadError;
export const fromPizzasActions = { LoadPizzas, PizzasLoaded, PizzasLoadError, };
export interface PizzasState { list: Pizza[]; // list of Pizzas; onlyVeggy: boolean; error?: any; // last none error (if any) }
export function pizzasReducer( state: PizzasState = initialState, action: PizzasAction ): PizzasState { switch (action.type) { case PizzasActionTypes.PizzasLoaded: { state = { ...state, list: action.payload, }; break; } } return state; }
-
Statemanagement & NgRx
Action Reducer State Selectors Facade View
Effect
export interface PizzasState { list: Pizza[]; // list of Pizzas; onlyVeggy: boolean; error?: any; // last none error (if any) }
export function pizzasReducer( state: PizzasState = initialState, action: PizzasAction ): PizzasState { switch (action.type) { case PizzasActionTypes.PizzasLoaded: { state = { ...state, list: action.payload, }; break; } } return state; }
-
Statemanagement & NgRx
Action Reducer State Selectors Facade View
Effect
export interface PizzasState { list: Pizza[]; // list of Pizzas; onlyVeggy: boolean; error?: any; // last none error (if any) }
export function pizzasReducer( state: PizzasState = initialState, action: PizzasAction ): PizzasState { switch (action.type) { case PizzasActionTypes.PizzasLoaded: { state = { ...state, list: action.payload, }; break; } } return state; }
const getPizzasState = createFeatureSelector('pizzas');
const getLoaded = createSelector( getPizzasState, (state: PizzasState) => state.loaded );
const getAllPizzas = createSelector( getPizzasState, getLoaded, (state: PizzasState, isLoaded) => { return isLoaded ? state.list : []; } );
export const pizzasQuery = { getLoaded, getAllPizzas, };
-
Statemanagement & NgRx
Action Reducer State Selectors Facade View
Effect
const getPizzasState = createFeatureSelector('pizzas');
const getLoaded = createSelector( getPizzasState, (state: PizzasState) => state.loaded );
const getAllPizzas = createSelector( getPizzasState, getLoaded, (state: PizzasState, isLoaded) => { return isLoaded ? state.list : []; } );
export const pizzasQuery = { getLoaded, getAllPizzas, };
@Injectable() export class PizzasFacade {
constructor(private store: Store) {}
loadAll() { this.store.dispatch(new LoadPizzas()); } }
allPizzas$ = this.store.pipe(select(pizzasQuery.getAllPizzas)); loaded$ = this.store.pipe(select(pizzasQuery.getLoaded));
-
@Component({ selector: 'app-products', templateUrl: './products.component.html', styleUrls: ['./products.component.scss'] }) export class ProductsComponent implements OnInit { pizzas$: Observable;
Statemanagement & NgRx
Action Reducer State Selectors Facade View
Effect
@Injectable() export class PizzasFacade {
constructor(private store: Store) {}
loadAll() { this.store.dispatch(new LoadPizzas()); } }
allPizzas$ = this.store.pipe(select(pizzasQuery.getAllPizzas)); loaded$ = this.store.pipe(select(pizzasQuery.getLoaded));
}
constructor(private _pizzaFacade: PizzasFacade) {}
ngOnInit(): void { this._pizzaFacade.loadAll(); }
-
@Component({ selector: 'app-products', templateUrl: './products.component.html', styleUrls: ['./products.component.scss'] }) export class ProductsComponent implements OnInit { pizzas$: Observable;
Statemanagement & NgRx
Action Reducer State Selectors Facade View
Effect
@Injectable() export class PizzasFacade {
constructor(private store: Store) {}
loadAll() { this.store.dispatch(new LoadPizzas()); } }
allPizzas$ = this.store.pipe(select(pizzasQuery.getAllPizzas)); loaded$ = this.store.pipe(select(pizzasQuery.getLoaded));
}
constructor(private _pizzaFacade: PizzasFacade) {
}
ngOnInit(): void { this._pizzaFacade.loadAll(); }
this.pizzas$ = this._pizzaFacade.allPizzas$;
-
Key Takeaways
• Keep reducers logic free• Use selectors to get sub state• Decouple components from store with facade• Smart vs dumb components
Statemanagement & NgRx
-
Elm & Elm lang