$30 off During Our Annual Pro Sale. View Details »

Framework-agnostic Web Applications with Redux

Framework-agnostic Web Applications with Redux

João Figueiredo

January 19, 2017
Tweet

More Decks by João Figueiredo

Other Decks in Programming

Transcript

  1. Framework-agnostic web
    applications with Redux
    (or how to fight Javascript Fatigue Fatigue)
    @lucalanca

    View Slide

  2. Angular 1

    View Slide

  3. React

    View Slide

  4. Angular 2

    View Slide

  5. Nobody cares.
    Is there a way to create framework-agnostic code?

    View Slide

  6. views
    how does it look
    application logic
    what happens if I click here
    business logic
    how do we change data
    Framework-agnostic Webapps
    routes
    what pages do we have,
    when do we navigate between pages.
    talking to a server
    get, store and update data.
    data validation
    what is valid data?
    what is invalid data?

    View Slide

  7. Today
    build a Kanban board in 3 frameworks and
    reuse as much code as possible.

    View Slide

  8. Today
    build a Kanban board in 3 frameworks and
    reuse as much code as possible.
    2

    View Slide

  9. How code is displayed from now on.
    vanilla react angular 2

    View Slide

  10. (change to browser now)

    View Slide

  11. Redux
    predictable state container for JavaScript apps.

    View Slide

  12. const state = {
    counter: 0
    };

    View Slide

  13. const state = {
    counter: 0
    };
    const action1 = { type: 'INCREMENT' }
    const action2 = { type: 'DECREMENT' }
    const action3 = { type: 'INCREMENT_BY', payload: 3}

    View Slide

  14. const state = {
    counter: 0
    };
    const action1 = { type: 'INCREMENT' }
    const action2 = { type: 'DECREMENT' }
    const action3 = { type: 'INCREMENT_BY', payload: 3}
    function reducer(state = 0, action) {
    switch (action.type) {
    case 'INCREMENT':
    return state + 1
    case 'DECREMENT':
    return state - 1
    case 'INCREMENT_BY':
    return state + payload
    default:
    return state
    }
    }

    View Slide

  15. reducer(state, action);
    // newState

    View Slide

  16. reducer(state, action);
    // newState
    reducer({ counter: 0 }, {
    type: ‘INCREMENT’
    });
    // { counter: 1 }

    View Slide

  17. reducer(state, action);
    // newState
    reducer({ counter: 0 }, {
    type: ‘INCREMENT’
    });
    // { counter: 1 }
    reducer({ counter: 5 }, {
    type: ‘INCREMENT_BY',
    payload: 10
    });
    // { counter: 15 }

    View Slide

  18. import { createStore } from 'redux'
    let store = createStore(reducer)
    // initial state set to { counter: 0 }

    View Slide

  19. import { createStore } from 'redux'
    let store = createStore(reducer)
    // initial state set to { counter: 0 }
    store.subscribe(() =>
    console.log('new state is', store.getState())
    )

    View Slide

  20. import { createStore } from 'redux'
    let store = createStore(reducer)
    // initial state set to { counter: 0 }
    store.subscribe(() =>
    console.log('new state is', store.getState())
    )
    store.dispatch({ type: 'INCREMENT' })
    // new state is { counter: 1 }

    View Slide

  21. import { createStore } from 'redux'
    let store = createStore(reducer)
    // initial state set to { counter: 0 }
    store.subscribe(() =>
    console.log('new state is', store.getState())
    )
    store.dispatch({ type: 'INCREMENT' })
    // new state is { counter: 1 }
    store.dispatch({ type: 'DECREMENT' })
    // new state is { counter: 0 }

    View Slide

  22. import { createStore } from 'redux'
    let store = createStore(reducer)
    // initial state set to { counter: 0 }
    store.subscribe(() =>
    console.log('new state is', store.getState())
    )
    store.dispatch({ type: 'INCREMENT' })
    // new state is { counter: 1 }
    store.dispatch({ type: 'DECREMENT' })
    // new state is { counter: 0 }
    store.dispatch({ type: ‘INCREMENT_BY', payload: 3})
    // new state is { counter: 3 }

    View Slide

  23. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import { MyComponent }
    from './components/MyComponent';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )

    View Slide

  24. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import { MyComponent }
    from './components/MyComponent';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import { MyComponent }
    from ‘./components/MyComponent';
    @NgModule({
    declarations: [ AppComponent ],
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [MyComponent]
    })
    export class AppModule { }

    View Slide

  25. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import { MyComponent }
    from './components/MyComponent';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import { MyComponent }
    from ‘./components/MyComponent';
    @NgModule({
    declarations: [ AppComponent ],
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [MyComponent]
    })
    export class AppModule { }

    View Slide

  26. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import { MyComponent }
    from './components/MyComponent';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import { MyComponent }
    from ‘./components/MyComponent';
    @NgModule({
    declarations: [ AppComponent ],
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [MyComponent]
    })
    export class AppModule { }

    View Slide

  27. import React from 'react'
    import { render } from 'react-dom'
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import reducer from './reducers';
    import { MyComponent }
    from './components/MyComponent';
    const store = createStore(reducer);
    render(


    ,
    document.getElementById('root')
    )
    import { BrowserModule }
    from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import reducer from ‘./reducers';
    import { MyComponent }
    from ‘./components/MyComponent';
    @NgModule({
    declarations: [ AppComponent ],
    imports: [
    BrowserModule,
    StoreModule.provideStore(reducer)
    ],
    bootstrap: [MyComponent]
    })
    export class AppModule { }

    View Slide

  28. import React, { Component } from 'react'
    import { connect } from 'react-redux'
    import * as actions from '../actions'
    export class MyComponent extends Component {
    render() {
    return counter is: { this.props.counter }
    }
    }
    const mapStateToProps = (state, ownProps) => ({ counter: state.counter })
    export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)

    View Slide

  29. import React, { Component } from 'react'
    import { connect } from 'react-redux'
    import * as actions from '../actions'
    export class MyComponent extends Component {
    render() {
    return counter is: { this.props.counter }
    }
    }
    const mapStateToProps = (state, ownProps) => ({ counter: state.counter })
    export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import 'rxjs/add/operator/map';
    import * as actions from '../actions';
    @Component({
    selector: 'my-component',
    template: `counter is: {{ counter }}`
    })
    export class MyComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => this.counter = state.counter);
    }
    }

    View Slide

  30. import React, { Component } from 'react'
    import { connect } from 'react-redux'
    import * as actions from '../actions'
    export class MyComponent extends Component {
    render() {
    return counter is: { this.props.counter }
    }
    }
    const mapStateToProps = (state, ownProps) => ({ counter: state.counter })
    export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import 'rxjs/add/operator/map';
    import * as actions from '../actions';
    @Component({
    selector: 'my-component',
    template: `counter is: {{ counter }}`
    })
    export class MyComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => this.counter = state.counter);
    }
    }

    View Slide

  31. import React, { Component } from 'react'
    import { connect } from 'react-redux'
    import * as actions from '../actions'
    export class MyComponent extends Component {
    render() {
    return counter is: { this.props.counter }
    }
    }
    const mapStateToProps = (state, ownProps) => ({ counter: state.counter })
    export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import 'rxjs/add/operator/map';
    import * as actions from '../actions';
    @Component({
    selector: 'my-component',
    template: `counter is: {{ counter }}`
    })
    export class MyComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => this.counter = state.counter);
    }
    }

    View Slide

  32. import React, { Component } from 'react'
    import { connect } from 'react-redux'
    import * as actions from '../actions'
    export class MyComponent extends Component {
    render() {
    return counter is: { this.props.counter }
    }
    }
    const mapStateToProps = (state, ownProps) => ({ counter: state.counter })
    export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import 'rxjs/add/operator/map';
    import * as actions from '../actions';
    @Component({
    selector: 'my-component',
    template: `counter is: {{ counter }}`
    })
    export class MyComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => this.counter = state.counter);
    }
    }

    View Slide

  33. import React, { Component } from 'react'
    import { connect } from 'react-redux'
    import * as actions from '../actions'
    export class MyComponent extends Component {
    render() {
    return counter is: { this.props.counter }
    }
    }
    const mapStateToProps = (state, ownProps) => ({ counter: state.counter })
    export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import 'rxjs/add/operator/map';
    import * as actions from '../actions';
    @Component({
    selector: 'my-component',
    template: `counter is: {{ counter }}`
    })
    export class MyComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => this.counter = state.counter);
    }
    }

    View Slide

  34. import React, { Component } from 'react'
    import { connect } from 'react-redux'
    import * as actions from '../actions'
    export class MyComponent extends Component {
    render() {
    return counter is: { this.props.counter }
    }
    }
    const mapStateToProps = (state, ownProps) => ({ counter: state.counter })
    export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import 'rxjs/add/operator/map';
    import * as actions from '../actions';
    @Component({
    selector: 'my-component',
    template: `counter is: {{ counter }}`
    })
    export class MyComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => this.counter = state.counter);
    }
    }

    View Slide

  35. import React, { Component } from 'react'
    import { connect } from 'react-redux'
    import * as actions from '../actions'
    export class MyComponent extends Component {
    render() {
    return counter is: { this.props.counter }
    }
    }
    const mapStateToProps = (state, ownProps) => ({ counter: state.counter })
    export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
    import { Component } from '@angular/core';
    import { Store } from '@ngrx/store';
    import 'rxjs/add/operator/map';
    import * as actions from '../actions';
    @Component({
    selector: 'my-component',
    template: `counter is: {{ counter }}`
    })
    export class MyComponent {
    constructor(store: Store){
    this.store = store;
    this.actions = actions;
    store.subscribe(state => this.counter = state.counter);
    }
    }

    View Slide

  36. Redux Kanban Board
    a more realistic example.
    github.com/lucalanca/redux-talk

    View Slide

  37. View Slide

  38. // state de-normalised as a dictionary (e.g. json api ==> firebase)
    const kanbanState = {
    columns: {
    "47749e5": { name: “Todo” , index: 0 },
    "dde4872": { name: “Doing", index: 1 },
    "dde4872": { name: “Done” , index: 2 }
    }
    }

    View Slide

  39. // state de-normalised as a dictionary (e.g. json api ==> firebase)
    const kanbanState = {
    columns: {
    "47749e5": { name: “Todo” , index: 0 },
    "dde4872": { name: “Doing", index: 1 },
    "dde4872": { name: “Done” , index: 2 }
    }
    }
    // selectors: normalise data so it’s easier to display it later.
    import { sortBy } from 'lodash';
    const arrayfyObject = (obj) => Object.keys(obj).map(id => ({ ...obj[id], id }))
    export function getSortedColumns(state) {
    const columns = arrayfyObject(state.columns || {});
    return sortBy(columns, 'index');
    }

    View Slide

  40. const kanbanState = {
    columns: {
    "47749e5": { name: “Todo” , index: 0 },
    "dde4872": { name: “Doing", index: 1 },
    "dde4872": { name: “Done” , index: 2 }
    }
    }
    getSortedColumns(kanbanState);
    // [
    // { id: "47749e5", name: "Todo" , index: 0 },
    // { id: "dde4872", name: "Doing", index: 1 },
    // { id: "dde4872", name: "Done" , index: 2 }
    // ]

    View Slide

  41. // actions.js
    export const RENAME_COLUMUN = 'RENAME_COLUMUN'
    export const renameColumn = (id, name) => ({
    type: RENAME_COLUMUN, payload: {id, name}
    })

    View Slide

  42. // actions.js
    export const RENAME_COLUMUN = 'RENAME_COLUMUN'
    export const renameColumn = (id, name) => ({
    type: RENAME_COLUMUN, payload: {id, name}
    })
    // reducers.js
    export default function columns (state = {}, { type, payload }) {
    if (type === RENAME_COLUMUN) {
    return Object.assign({}, state, {
    [payload.id]: Object.assign({}, state[payload.id], {
    name: payload.name
    })
    });
    }
    return state;
    }
    // selectors go usually here

    View Slide

  43. columns(kanbanState, renameColumn("ac32fcc", "Shipped"));
    // newKanbanState: {
    // columns: {
    // "47749e5": {
    // "name": "Todo",
    // "index": 0
    // },
    // "dde4872": {
    // "name": "Doing",
    // "index": 1
    // },
    // "ac32fcc": {
    // "name": "Shipped",
    // "index": 2
    // }
    // }
    // }

    View Slide

  44. Presentational
    Components
    Container
    Components
    Presentational and Container Components by Dan Abramov

    View Slide

  45. Presentational
    Components
    Container
    Components
    concern how things look how things work
    Presentational and Container Components by Dan Abramov

    View Slide

  46. Presentational
    Components
    Container
    Components
    concern how things look how things work
    dependencies — actions and store
    Presentational and Container Components by Dan Abramov

    View Slide

  47. Presentational
    Components
    Container
    Components
    concern how things look how things work
    dependencies — actions and store
    logic —
    data loading
    and mutation
    Presentational and Container Components by Dan Abramov

    View Slide

  48. Presentational
    Components
    Container
    Components
    concern how things look how things work
    dependencies — actions and store
    logic —
    data loading
    and mutation
    state stateless (pure) stateful
    Presentational and Container Components by Dan Abramov

    View Slide

  49. Presentational
    Components
    Container
    Components
    concern how things look how things work
    dependencies — actions and store
    logic —
    data loading
    and mutation
    state stateless (pure) stateful
    Presentational and Container Components by Dan Abramov


    visual
    interaction

    View Slide

  50. export default const Column = ({
    id, name,
    onAddTask
    }) =>

    {name}

    Add Task


    View Slide

  51. export default const Column = ({
    id, name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ column.name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() column;
    @Output() onAddTask = new EventEmitter();
    }

    View Slide

  52. export default const Column = ({
    id, name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ column.name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() column;
    @Output() onAddTask = new EventEmitter();
    }

    View Slide

  53. export default const Column = ({
    id, name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ column.name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() column;
    @Output() onAddTask = new EventEmitter();
    }

    View Slide

  54. export default const Column = ({
    id, name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ column.name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() column;
    @Output() onAddTask = new EventEmitter();
    }

    View Slide

  55. export default const Column = ({
    id, name,
    onAddTask
    }) =>

    {name}

    Add Task


    @Component({
    selector: 'column',
    template: `

    {{ column.name }}

    Add Task

    `
    })
    export class ColumnComponent {
    @Input() column;
    @Output() onAddTask = new EventEmitter();
    }

    View Slide

  56. import { connect } from 'react-redux'
    import * as actions from "../actions"
    import { getColumnsWithTasks }
    from "../reducers"
    @connect(
    (state) => ({
    columns: getColumnsWithTasks(state)
    }),
    actions
    )
    class Board extends Component {
    render() {
    const { addTask, columns } = this.props;
    return (

    { columns.map(column =>
    {...column}
    onAddTask={
    () => addTask(column.id)
    } />
    )}

    )
    }
    }


    View Slide

  57. import { connect } from 'react-redux'
    import * as actions from "../actions"
    import { getColumnsWithTasks }
    from "../reducers"
    @connect(
    (state) => ({
    columns: getColumnsWithTasks(state)
    }),
    actions
    )
    class Board extends Component {
    render() {
    const { addTask, columns } = this.props;
    return (

    { columns.map(column =>
    {...column}
    onAddTask={
    () => addTask(column.id)
    } />
    )}

    )
    }
    }

    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    import { getColumnsWithTasks }
    from '../reducers';
    @Component({
    selector: 'app-board',
    template: `

    [column]="column"
    (onAddTask)="this.store.dispatch(
    actions.addTask(column.id)
    )" />
    `
    })
    export class Board {
    constructor(store: Store){
    this.actions = actions;
    this.store = store;
    store.subscribe(state =>
    this.columns =
    getColumnsWithTasks(state)
    );
    }
    }

    View Slide

  58. import { connect } from 'react-redux'
    import * as actions from "../actions"
    import { getColumnsWithTasks }
    from "../reducers"
    @connect(
    (state) => ({
    columns: getColumnsWithTasks(state)
    }),
    actions
    )
    class Board extends Component {
    render() {
    const { addTask, columns } = this.props;
    return (

    { columns.map(column =>
    {...column}
    onAddTask={
    () => addTask(column.id)
    } />
    )}

    )
    }
    }

    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    import { getColumnsWithTasks }
    from '../reducers';
    @Component({
    selector: 'app-board',
    template: `

    [column]="column"
    (onAddTask)="this.store.dispatch(
    actions.addTask(column.id)
    )" />
    `
    })
    export class Board {
    constructor(store: Store){
    this.actions = actions;
    this.store = store;
    store.subscribe(state =>
    this.columns =
    getColumnsWithTasks(state)
    );
    }
    }

    View Slide

  59. import { connect } from 'react-redux'
    import * as actions from "../actions"
    import { getColumnsWithTasks }
    from "../reducers"
    @connect(
    (state) => ({
    columns: getColumnsWithTasks(state)
    }),
    actions
    )
    class Board extends Component {
    render() {
    const { addTask, columns } = this.props;
    return (

    { columns.map(column =>
    {...column}
    onAddTask={
    () => addTask(column.id)
    } />
    )}

    )
    }
    }

    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    import { getColumnsWithTasks }
    from '../reducers';
    @Component({
    selector: 'app-board',
    template: `

    [column]="column"
    (onAddTask)="this.store.dispatch(
    actions.addTask(column.id)
    )" />
    `
    })
    export class Board {
    constructor(store: Store){
    this.actions = actions;
    this.store = store;
    store.subscribe(state =>
    this.columns =
    getColumnsWithTasks(state)
    );
    }
    }

    View Slide

  60. import { connect } from 'react-redux'
    import * as actions from "../actions"
    import { getColumnsWithTasks }
    from "../reducers"
    @connect(
    (state) => ({
    columns: getColumnsWithTasks(state)
    }),
    actions
    )
    class Board extends Component {
    render() {
    const { addTask, columns } = this.props;
    return (

    { columns.map(column =>
    {...column}
    onAddTask={
    () => addTask(column.id)
    } />
    )}

    )
    }
    }

    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    import { getColumnsWithTasks }
    from '../reducers';
    @Component({
    selector: 'app-board',
    template: `

    [column]="column"
    (onAddTask)="this.store.dispatch(
    actions.addTask(column.id)
    )" />
    `
    })
    export class Board {
    constructor(store: Store){
    this.actions = actions;
    this.store = store;
    store.subscribe(state =>
    this.columns =
    getColumnsWithTasks(state)
    );
    }
    }

    View Slide

  61. import { connect } from 'react-redux'
    import * as actions from "../actions"
    import { getColumnsWithTasks }
    from "../reducers"
    @connect(
    (state) => ({
    columns: getColumnsWithTasks(state)
    }),
    actions
    )
    class Board extends Component {
    render() {
    const { addTask, columns } = this.props;
    return (

    { columns.map(column =>
    {...column}
    onAddTask={
    () => addTask(column.id)
    } />
    )}

    )
    }
    }

    import { Store } from '@ngrx/store';
    import * as actions from '../actions';
    import { getColumnsWithTasks }
    from '../reducers';
    @Component({
    selector: 'app-board',
    template: `

    [column]="column"
    (onAddTask)="this.store.dispatch(
    actions.addTask(column.id)
    )" />
    `
    })
    export class Board {
    constructor(store: Store){
    this.actions = actions;
    this.store = store;
    store.subscribe(state =>
    this.columns =
    getColumnsWithTasks(state)
    );
    }
    }

    View Slide

  62. views
    how does it look
    application logic
    what happens if I click here
    business logic
    how do we change data
    Framework-agnostic Webapps
    routes
    what pages do we have,
    when do we navigate between pages.
    talking to a server
    get, store and update data.
    data validation
    what is valid data?
    what is invalid data?

    View Slide

  63. views
    how does it look
    application logic
    what happens if I click here
    business logic
    how do we change data
    Framework-agnostic Webapps
    routes
    what pages do we have,
    when do we navigate between pages.
    talking to a server
    get, store and update data.
    data validation
    what is valid data?
    what is invalid data?

    View Slide

  64. views
    how does it look
    application logic
    what happens if I click here
    business logic
    how do we change data
    Framework-agnostic Webapps
    routes
    what pages do we have,
    when do we navigate between pages.
    talking to a server
    get, store and update data.
    data validation
    what is valid data?
    what is invalid data?


    View Slide

  65. views
    how does it look
    application logic
    what happens if I click here
    business logic
    how do we change data
    Framework-agnostic Webapps
    routes
    what pages do we have,
    when do we navigate between pages.
    talking to a server
    get, store and update data.
    data validation
    what is valid data?
    what is invalid data?



    View Slide

  66. views
    how does it look
    application logic
    what happens if I click here
    business logic
    how do we change data
    Framework-agnostic Webapps
    routes
    what pages do we have,
    when do we navigate between pages.
    talking to a server
    get, store and update data.
    data validation
    what is valid data?
    what is invalid data?






    View Slide

  67. views
    how does it look
    application logic
    what happens if I click here
    business logic
    how do we change data
    Framework-agnostic Webapps
    routes
    what pages do we have,
    when do we navigate between pages.
    talking to a server
    get, store and update data.
    data validation
    what is valid data?
    what is invalid data?









    View Slide

  68. views
    how does it look
    application logic
    what happens if I click here
    business logic
    how do we change data
    Framework-agnostic Webapps
    routes
    what pages do we have,
    when do we navigate between pages.
    talking to a server
    get, store and update data.
    data validation
    what is valid data?
    what is invalid data?









    View Slide

  69. views
    how does it look
    application logic
    what happens if I click here
    business logic
    how do we change data
    Framework-agnostic Webapps
    routes
    what pages do we have,
    when do we navigate between pages.
    talking to a server
    get, store and update data.
    data validation
    what is valid data?
    what is invalid data?









    View Slide

  70. The exploration goes on…
    Implement a Vue.js kanban (through Vuex)
    Add asynchronous actions (e.g.: store on firebase)
    Add routes (create a timeline view)
    Implement an Aurelia kanban (through aurelia-redux)

    View Slide

  71. If you want to dig more:
    Getting Started with Redux Course
    by Dan Abramov
    Building React Applications with Idiomatic Redux
    by Dan Abramov
    Build Redux Applications with Angular2, RxJS, and ngrx/store
    by John Lindquist
    Vuex — Vue.js State Management Pattern
    (inspired by redux)

    View Slide

  72. @lucalanca

    View Slide