Modern Front-End Development...Timeline of JS UI Libs dom manipulation mvc/mvvm component based...

126
Modern Front-End Development Nov 2020

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