Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Framework-Agnostic Web Applications with Redux at Frontend Conf 2017

Framework-Agnostic Web Applications with Redux at Frontend Conf 2017

The web is changing fast. If you're building web products today, it can be overwhelming. Which framework should your team adopt? What if something better suited comes out in a few months?

In this talk, we’ll explore Redux – an architecture that helps building applications that behave consistently and run across environments. Redux is gaining popularity within today’s most popular framework’s communities, by allowing developers to express their application logic in pure, easily testable, and reusable functions, enabling teams to keep shipping their products without rewriting their frontend every 6 months.

We'll use Redux to build a small web application and learn how to make use of the same codebase together with today's popular web frameworks – React, Angular, Vue – without changes.

João Figueiredo

September 01, 2017
Tweet

More Decks by João Figueiredo

Other Decks in Programming

Transcript

  1. Agnostic a: unwilling to commit to an opinion about something.

    b: compatible with many types of platforms or operating systems. (adjective)
  2. Framework-Agnostic Web Apps Applications that are designed in such a

    way that make them not tied to a particular framework.
  3. 05 06 07 08 09 10 11 12 13 14

    15 ® React Native 10 Years in Web
  4. 05 06 07 08 09 10 11 12 13 14

    15 ® React Native DOM
  5. 05 06 07 08 09 10 11 12 13 14

    15 ® React Native CSS
  6. 05 06 07 08 09 10 11 12 13 14

    15 ® React Native Applications
  7. 05 06 07 08 09 10 11 12 13 14

    15 ® React Native Tests
  8. 05 06 07 08 09 10 11 12 13 14

    15 ® React Native Dependencies
  9. 05 06 07 08 09 10 11 12 13 14

    15 ® React Native Tooling
  10. 05 06 07 08 09 10 11 12 13 14

    15 ® React Native Transpilation
  11. 05 06 07 08 09 10 11 12 13 14

    15 ® React Native
  12. 11 12 13 14 15 16 17 npm modules over

    time [Breaking News] This is not going to slow down.
  13. This is not necessarily bad. We just have to get

    used to it. Code that embraces change.
  14. This is not necessarily bad. We just have to get

    used to it. Code that embraces change. Agnostic Code.
  15. Framework-Agnostic Web Apps Allow us to easily switch to a

    better fitting framework without the need for a rewrite.
  16. Views how it looks App Logic what happens if I

    click here Routing what pages do I have
  17. Views how it looks App Logic what happens if I

    click here Routing what pages do I have Persistence how do I create/update data, optimistic updates, data consistency
  18. Views how it looks App Logic what happens if I

    click here Business Logic what data do I have, how do I interact with it it, who can do what, etc. Routing what pages do I have Persistence how do I create/update data, optimistic updates, data consistency
  19. the web moves faster than you Why is this important?

    (and that’s ok) users judge you on your product. (and that’s difficult on its own)
  20. A library to manage state of your application. — Redux

    docs Your framework-agnostic best friend. — Me
  21. REDUX APPS run in different environments (Good because the web

    moves fast) (Good because making good products is hard) behave consistently and are easy to test.
  22. store.getState(); Holds the whole state tree of your application. Redux

    Store { todos: [ { name: 'Attend Frontend Conf' }, { name: 'Introduce Redux Store' }, ], }
  23. Everything is state. Dissecting Twitter's Redux Store by Ryan Johnson

    Tweets Users Lists Cards Timeline Settings Notifications …
  24. Redux Actions Payloads of information that send data from your

    application to the store. const action = { type: "ADD_TODO", payload: { name: "A new todo" } }; store.dispatch(action);
  25. Redux Action Creators function renameTodo(index, name) { } return {

    type: "RENAME_TODO", payload: { index, name } };
  26. Redux Action Creators function renameTodo(index, name) { } return {

    type: "RENAME_TODO", payload: { index, name } }; ACTION CREATOR
  27. Redux Action Creators function renameTodo(index, name) { } return {

    type: "RENAME_TODO", payload: { index, name } }; ACTION
  28. Redux Reducers Actions describe that something happened. Reducers specify how

    the state changes in response. (previous, action) => newState
  29. Redux Reducers function reducer(state = INITIAL_STATE, { type, payload })

    { if (type === 'INCREMENT') { return state + 1; } if (type === 'INCREMENT_BY') { return state + payload; } return state; } ACTION
  30. Write a simple redux application and use in React, Vue

    and Angular 2 without code changes. CHALLENGE #1
  31. ADD Buy Groceries Go to Gym x [ { name:

    'Buy Groceries', isCompleted: false }, { name: 'Go To Gym', isCompleted: true } ] Step 1: design state
  32. // actions/index.js export const ADD_TODO = 'ADD_TODO'; export const TOGGLE_TODO

    = 'TOGGLE_TODO'; export const addTodo = (name) => ({ type: ADD_TODO, payload: name }) export const toggleTodo = (index) => ({ type: TOGGLE_TODO, payload: index }); Step 2: actions and action creators
  33. // reducers/index.js export function todos(state = [], { type, payload

    }) { if (type === ADD_TODO) { return state.concat([ { name: payload, isCompleted: false } ]); } if (type === TOGGLE_TODO) { return [ ...state.slice(0, payload - 1), { ...state[payload], isCompleted: !state[payload].isCompleted }, ...state.slice(payload + 1) ]; } return state; } Step 3: reducer
  34. ADD

  35. import reducer from './reducers'; const store = createStore(reducer); store.subscribe(() =>

    { const todos = store.getState() // [] render(todos); }); ADD Step 4: create store
  36. import reducer from './reducers'; const store = createStore(reducer); store.subscribe(() =>

    { const todos = store.getState() // [] render(todos); }); ADD Step 4: create store render(todos);
  37. ADD Buy Groceries store.subscribe(() => { const todos = store.getState()

    // [ // { name: 'Buy groceries', isCompleted: false } // ] render(todos); });
  38. ADD Buy Groceries Go to Gym store.subscribe(() => { const

    todos = store.getState() // [ // { name: 'Buy groceries', isCompleted: false } // { name: 'Go to Gym', isCompleted: false } // ] render(todos); });
  39. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') )
  40. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { }
  41. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), });
  42. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); Bootstrapping Framework
  43. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); Bootstrapping Framework import React from 'react' import { render } from 'react-dom' render( document.getElementById('root') )
  44. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); Bootstrapping Framework import React from 'react' import { render } from 'react-dom' render( document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; @NgModule({ imports: [ BrowserModule, ], bootstrap: [ ] }) export class AppModule { }
  45. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); Bootstrapping Framework import React from 'react' import { render } from 'react-dom' render( document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; @NgModule({ imports: [ BrowserModule, ], bootstrap: [ ] }) export class AppModule { } import Vue from 'vue'; new Vue({ el: '#root', render: h => h( ), });
  46. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); import React from 'react' import { render } from 'react-dom' render( document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; @NgModule({ imports: [ BrowserModule, ], bootstrap: [ ] }) export class AppModule { } import Vue from 'vue'; new Vue({ el: '#root', render: h => h( ), }); ⛔ Bootstrapping Framework
  47. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); Bootstrap Redux Part
  48. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); import React from 'react' import { createStore } from 'redux' import reducer from './reducers'; const store = createStore(reducer); Bootstrap Redux Part
  49. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); import React from 'react' import { createStore } from 'redux' import reducer from './reducers'; const store = createStore(reducer); import reducer from ‘./reducers'; Bootstrap Redux Part
  50. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); import React from 'react' import { createStore } from 'redux' import reducer from './reducers'; const store = createStore(reducer); import reducer from ‘./reducers'; import { createStore } from 'redux'; import reducer from './reducers'; const store = createStore(reducer); Bootstrap Redux Part
  51. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); import React from 'react' import { createStore } from 'redux' import reducer from './reducers'; const store = createStore(reducer); import reducer from ‘./reducers'; import { createStore } from 'redux'; import reducer from './reducers'; const store = createStore(reducer); Bootstrap Redux Part ✅
  52. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); Binding Redux to Framework
  53. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); import { Provider } from 'react-redux' <Provider store={store}> </Provider>, Binding Redux to Framework
  54. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); import { Provider } from 'react-redux' <Provider store={store}> </Provider>, import { StoreModule } from '@ngrx/store'; StoreModule.provideStore(reducer) Binding Redux to Framework
  55. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); import { Provider } from 'react-redux' <Provider store={store}> </Provider>, import { StoreModule } from '@ngrx/store'; StoreModule.provideStore(reducer) Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); Binding Redux to Framework
  56. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); import { Provider } from 'react-redux' <Provider store={store}> </Provider>, import { StoreModule } from '@ngrx/store'; StoreModule.provideStore(reducer) Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); Binding Redux to Framework ⛔
  57. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); Top Level Component
  58. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); import TodoApp from ‘./components/TodoApp’; <TodoApp /> Top Level Component
  59. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); import TodoApp from ‘./components/TodoApp’; <TodoApp /> import TodoApp from ‘./components/TodoApp'; TodoApp Top Level Component
  60. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); import TodoApp from ‘./components/TodoApp’; <TodoApp /> import TodoApp from ‘./components/TodoApp'; TodoApp import TodoApp from './components/TodoApp'; TodoApp Top Level Component
  61. import React from 'react' import { render } from 'react-dom'

    import { Provider } from 'react-redux' import { createStore } from 'redux' import reducer from './reducers'; import TodoApp from ‘./components/TodoApp'; const store = createStore(reducer); render( <Provider store={store}> <TodoApp /> </Provider>, document.getElementById('root') ) import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import reducer from ‘./reducers'; import TodoApp from ‘./components/TodoApp'; @NgModule({ imports: [ BrowserModule, StoreModule.provideStore(reducer) ], bootstrap: [TodoApp] }) export class AppModule { } import Vue from 'vue'; import { createStore } from 'redux'; import reducer from './reducers'; import TodoApp from './components/TodoApp'; const store = createStore(reducer); Vue.use({ install: () => Object.defineProperty( Vue.proprotype, '$store', { get() { return store; }, }), }); new Vue({ el: '#root', render: h => h(TodoApp), }); import TodoApp from ‘./components/TodoApp’; <TodoApp /> import TodoApp from ‘./components/TodoApp'; TodoApp import TodoApp from './components/TodoApp'; TodoApp Top Level Component
  62. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import React, { Component } from 'react'; import { connect } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp );
  63. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } import React, { Component } from 'react'; import { connect } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } }
  64. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> import React, { Component } from 'react'; import { connect } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script>
  65. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> Component Definition
  66. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> import React, { Component } from 'react'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> </div> export default ( TodoApp ); Component Definition
  67. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> import React, { Component } from 'react'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> </div> export default ( TodoApp ); import { Component } from '@angular/core'; @Component({ selector: 'app-board', template: ` <div> </div>` }) export class BoardComponent { constructor( ){ } } Component Definition
  68. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> import React, { Component } from 'react'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> </div> export default ( TodoApp ); import { Component } from '@angular/core'; @Component({ selector: 'app-board', template: ` <div> </div>` }) export class BoardComponent { constructor( ){ } } <template> <div> </div> </template> <script> export default { }; </script> Component Definition
  69. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> import React, { Component } from 'react'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> </div> export default ( TodoApp ); import { Component } from '@angular/core'; @Component({ selector: 'app-board', template: ` <div> </div>` }) export class BoardComponent { constructor( ){ } } <template> <div> </div> </template> <script> export default { }; </script> Component Definition ⛔
  70. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> Binding Redux to Component
  71. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> import { connect } from 'react-redux'; import * as actions from '../actions'; export default connect(state => state, actions)( ); Binding Redux to Component
  72. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> import { connect } from 'react-redux'; import * as actions from '../actions'; export default connect(state => state, actions)( ); import { Store } from '@ngrx/store'; import * as actions from '../actions'; store: Store<any> this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } Binding Redux to Component
  73. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> import { connect } from 'react-redux'; import * as actions from '../actions'; export default connect(state => state, actions)( ); import { Store } from '@ngrx/store'; import * as actions from '../actions'; store: Store<any> this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } import * as actions from "../../common/actions"; import { connect } from "../store"; mixins: [connect(state => state, actions)] Binding Redux to Component
  74. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> import { connect } from 'react-redux'; import * as actions from '../actions'; export default connect(state => state, actions)( ); import { Store } from '@ngrx/store'; import * as actions from '../actions'; store: Store<any> this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } import * as actions from "../../common/actions"; import { connect } from "../store"; mixins: [connect(state => state, actions)] Binding Redux to Component ⛔
  75. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> Component “Template”
  76. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> Component “Template”
  77. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` Component “Template”
  78. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> Component “Template”
  79. import React, { Component } from 'react'; import { connect

    } from 'react-redux'; import * as actions from '../actions'; const TodoApp = ({ todos, addTodo, toggleTodo }) => <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> export default connect(state => state, actions)( TodoApp ); import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import * as actions from '../actions'; @Component({ selector: 'app-board', template: ` <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` }) export class BoardComponent { constructor(store: Store<any>){ this.store = store; this.actions = actions; store.subscribe(state => { this.todos = state.todos; }) } } <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> <script> import * as actions from "../../common/actions"; import { connect } from "../store"; export default { mixins: [connect(state => state, actions)] }; </script> <div> {todos.map(({ name, isCompleted }, index) => <label key={i}> <input type="checkbox" checked={isCompleted} onChange={() => toggleTodo(index)} /> { name } </label> )} <input type="text" onChange={evt => addTodo(evt.target.value)} /> </div> <div> <label *ngFor="let todo of todos;"> <input type="checkbox" [checked]="todo.isCompleted" (change)="store.dispatch( actions.toggleTodo(index) )" /> {{ todo.name }} </label> <input type="text" (change)="store.dispatch( actions.addTodo($event.target.value) )" /> </div>` <template> <div> <label v-for="(todo, index) in todos" :key="index"> <input type="checkbox" :checked="todo.isCompleted" @change="() => toggleTodo(index)" /> {{ todo.name }} </label> <input type="text" @change="(evt) => addTodo(evt.target.value)" /> </div> </template> Component “Template”
  80. Write a trello clone redux application and use in React,

    Vue and Angular 2 without code changes. CHALLENGE #2
  81. const columnsState = { "dd933cd3-842f-4546-b2b2-94851eb2e89c": { index: 0, name: "todo"

    }, "f9c9e0ff-6f54-4787-bbb4-b467318fffc7": { index: 1, name: "doing" }, "d8ee1c37-7c21-4754-9ebc-1c73b3eb3a8d": { index: 2, name: "done" } }; Step 1: design state
  82. { "47749e5": { name: "Todo" }, "dde4872": { name: "Doing"

    }, "ac32fcc": { name: "Done" } } Normalized State
  83. { "47749e5": { name: "Todo" }, "dde4872": { name: "Doing"

    }, "ac32fcc": { name: "Done" } } Normalized State const arrayfyObject = obj => Object .keys(obj) .map(id => ({ ...obj[id], id })); A simple selector
  84. { "47749e5": { name: "Todo" }, "dde4872": { name: "Doing"

    }, "ac32fcc": { name: "Done" } } [ { id: "47749e5", name: "Todo" }, { id: "dde4872", name: "Doing" }, { id: "ac32fcc", name: "Done" } ] Normalized State De-Normalized State const arrayfyObject = obj => Object .keys(obj) .map(id => ({ ...obj[id], id })); A simple selector
  85. const tasksState = { "542e1d5b-db3a-4910-a103-78b55488198c": { column: "dd933cd3-842f-4546-b2b2-94851eb2e89c", assignee: null,

    name: "task 1" }, "33255381-182d-4183-b5b7-b7cfd409f583": { column: "f9c9e0ff-6f54-4787-bbb4-b467318fffc7", assignee: null, name: "task 2" } }; Step 1: design state
  86. const tasksState = { "542e1d5b-db3a-4910-a103-78b55488198c": { column: "dd933cd3-842f-4546-b2b2-94851eb2e89c", assignee: null,

    name: "task 1" }, "33255381-182d-4183-b5b7-b7cfd409f583": { column: "f9c9e0ff-6f54-4787-bbb4-b467318fffc7", assignee: null, name: "task 2" } }; Step 1: design state column: "dd933cd3-842f-4546-b2b2-94851eb2e89c" column: “f9c9e0ff-6f54-4787-bbb4-b467318fffc7" };
  87. Step 2: actions // actions/columns.js export const RENAME_COLUMUN = "RENAME_COLUMUN";

    export const renameColumn = (id, name) => ({ type: RENAME_COLUMUN, payload: { id, name } });
  88. // actions/tasks.js export const ADD_TASK_TO_COLUMN = "ADD_TASK_TO_COLUMN"; export const addTaskToColumn

    = (columnId, userId) => ({ type: ADD_TASK_TO_COLUMN, payload: { columnId, userId } }); Step 2: actions
  89. Step 3: reducers // reducers/columns.js export default function columns(state =

    {}, { type, payload }) { if (type === RENAME_COLUMUN) { return { ...state, [payload.id]: { ...state[payload.id], name: payload.name } }; } return state; }
  90. { "47749e5": { name: "Todo" }, "dde4872": { name: "Doing"

    }, "ac32fcc": { name: "Done" } } columns( kanbanState, renameColumn("ac32fcc", "Shipped")); { "47749e5": { name: "Todo" }, "dde4872": { name: "Doing" }, "ac32fcc": { name: "Done" } }
  91. { "47749e5": { name: "Todo" }, "dde4872": { name: "Doing"

    }, "ac32fcc": { name: "Done" } } columns( kanbanState, renameColumn("ac32fcc", "Shipped")); "ac32fcc": { name: "Done" }
  92. { "47749e5": { name: "Todo" }, "dde4872": { name: "Doing"

    }, "ac32fcc": { name: “Shipped" } } columns( kanbanState, renameColumn("ac32fcc", "Shipped")); "ac32fcc": { name: “Shipped" } { "47749e5": { name: "Todo" }, "dde4872": { name: "Doing" }, "ac32fcc": { name: "Done" } } "ac32fcc": { name: "Done" }
  93. // reducers/tasks.js export default function tasks(state = {}, { type,

    payload }) { if (type === ADD_TASK_TO_COLUMN) { return { ...state, [v4()]: { column: payload.columnId } }; } return state; } Step 3: reducers
  94. // reducers/index.ks import columns from "./columns"; import tasks from "./tasks";

    import users from "./users"; const rootReducer = combineReducers({ columns, tasks, users }); Step 3: combining reducers
  95. Step 3: combining reducers { columns: { '1': { name:

    'Todo' }, '2': { name: 'Doing' }, '3', { name: 'Done' } }, tasks: { '1': { name: 'Task 1', column: '1' }, '2': { name: 'Task 2', column: '2' } } } { columns: { }, tasks: { }
  96. function getColumnsWithTasks(state) { const columns = arrayfyObject(state.columns || {}); const

    tasks = arrayfyObject(state.tasks || {}); return columns.map(column => ({ ...column, tasks: tasks.filter(t => t.column === column.id) })); } A more complex selector
  97. getColumnsWithTasks(state); { columns: { '1': { name: 'Todo' }, '2':

    { name: 'Doing' }, '3', { name: 'Done' } }, tasks: { '1': { name: 'Task 1', column: '1' }, '2': { name: 'Task 2', column: '2' } } } columns: tasks:
  98. getColumnsWithTasks(state); [ { name: "todo", id: '1', tasks: [ {

    name: "Task 1", id: '1' } ] }, { name: "doing", id: '2', tasks: [ { name: "Task 2", id: '2' } ] }, { name: "done", id: '3', tasks: [] } ]; { columns: { '1': { name: 'Todo' }, '2': { name: 'Doing' }, '3', { name: 'Done' } }, tasks: { '1': { name: 'Task 1', column: '1' }, '2': { name: 'Task 2', column: '2' } } } columns: tasks: { name: "todo", id: '1', tasks: [ { name: "Task 1", id: '1' } ] }
  99. Presentational Components Container Components concern how things look how things

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

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

    work dependencies — actions and store business logic — - Presentational and Container Components by Dan Abramov
  102. Presentational Components Container Components concern how things look how things

    work dependencies — actions and store business logic — (redux) Presentational and Container Components by Dan Abramov visual interaction
  103. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> Presentational Component export default const Column = ({ name, onAddTask }) => <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div>
  104. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } Presentational Component export default const Column = ({ name, onAddTask }) => <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); }
  105. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component export default const Column = ({ name, onAddTask }) => <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script>
  106. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Structure
  107. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Structure export default const Column = ({ }) => <div> </div>
  108. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Structure export default const Column = ({ }) => <div> </div> @Component({ selector: 'column', template: ` <div> </div>` }) export class ColumnComponent { }
  109. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Structure export default const Column = ({ }) => <div> </div> @Component({ selector: 'column', template: ` <div> </div>` }) export class ColumnComponent { } <template> <div> </div> </template> <script> export default { } </script>
  110. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Structure export default const Column = ({ }) => <div> </div> @Component({ selector: 'column', template: ` <div> </div>` }) export class ColumnComponent { } <template> <div> </div> </template> <script> export default { } </script>
  111. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Inputs/Outputs
  112. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Inputs/Outputs name, onAddTask
  113. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Inputs/Outputs name, onAddTask @Input() name; @Output() onAddTask = new EventEmitter();
  114. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Inputs/Outputs name, onAddTask @Input() name; @Output() onAddTask = new EventEmitter(); props: ['name', 'onAddTask'],
  115. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Inputs/Outputs name, onAddTask @Input() name; @Output() onAddTask = new EventEmitter(); props: ['name', 'onAddTask'], ✅
  116. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Binding inputs
  117. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Binding inputs <span>{name}</span>
  118. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Binding inputs <span>{name}</span> <span>{{ name }}</span>
  119. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Binding inputs <span>{name}</span> <span>{{ name }}</span> <span>{{ name }}</span>
  120. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Binding inputs <span>{name}</span> <span>{{ name }}</span> <span>{{ name }}</span> ✅
  121. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Emitting outputs + +
  122. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Emitting outputs onClick={onAddTask}>
  123. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Emitting outputs onClick={onAddTask}> (click)="onAddTask.emit()">
  124. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Emitting outputs onClick={onAddTask}> (click)="onAddTask.emit()"> @click=“onAddTask">
  125. export default const Column = ({ name, onAddTask }) =>

    <div> <span>{name}</span> <button onClick={onAddTask}> Add Task </button> </div> @Component({ selector: 'column', template: ` <div> <span>{{ name }}</span> <button (click)="onAddTask.emit()"> Add Task </button> </div>` }) export class ColumnComponent { @Input() name; @Output() onAddTask = new EventEmitter(); } <template> <div> <span>{{ name }}</span> <button @click=“onAddTask"> Add Task </button> </div> </template> <script> export default { props: ['name', 'onAddTask'], } </script> Presentational Component: Emitting outputs onClick={onAddTask}> (click)="onAddTask.emit()"> @click=“onAddTask">
  126. Container Component import { connect } from "react-redux"; import *

    as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); import { connect } from "react-redux"; import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello);
  127. Container Component import { connect } from "react-redux"; import *

    as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *ngFor="let column of columns" [name]="column.name" (onAddTask)="store.dispatch( actions.addTask(column.id) )" (onColumnNameChange)="store.dispatch( actions.renameColumn(column.id, $event.target.name) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } } import { connect } from "react-redux"; import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *ngFor="let column of columns" [name]="column.name" (onAddTask)="store.dispatch( actions.addTask(column.id) )" (onColumnNameChange)="store.dispatch( actions.renameColumn(column.id, $event.target.name) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } }
  128. Container Component import { connect } from "react-redux"; import *

    as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *ngFor="let column of columns" [name]="column.name" (onAddTask)="store.dispatch( actions.addTask(column.id) )" (onColumnNameChange)="store.dispatch( actions.renameColumn(column.id, $event.target.name) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } } import { connect } from "react-redux"; import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *ngFor="let column of columns" [name]="column.name" (onAddTask)="store.dispatch( actions.addTask(column.id) )" (onColumnNameChange)="store.dispatch( actions.renameColumn(column.id, $event.target.name) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } }
  129. Container Component: Redux bindings import { connect } from "react-redux";

    import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *ngFor="let column of columns" [name]="column.name" (onAddTask)="store.dispatch( actions.addTask(column.id) )" (onColumnNameChange)="store.dispatch( actions.renameColumn(column.id, $event.target.name) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } }
  130. Container Component: Redux bindings import { connect } from "react-redux";

    import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *ngFor="let column of columns" [name]="column.name" (onAddTask)="store.dispatch( actions.addTask(column.id) )" (onColumnNameChange)="store.dispatch( actions.renameColumn(column.id, $event.target.name) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } } import { connect } from "react-redux"; export default connect( )( );
  131. Container Component: Redux bindings import { connect } from "react-redux";

    import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *ngFor="let column of columns" [name]="column.name" (onAddTask)="store.dispatch( actions.addTask(column.id) )" (onColumnNameChange)="store.dispatch( actions.renameColumn(column.id, $event.target.name) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } } import { connect } from "react-redux"; export default connect( )( ); store: Store<any> this.store = store; this.actions = actions; store.subscribe(state => { });
  132. Container Component: Redux bindings import { connect } from "react-redux";

    import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *ngFor="let column of columns" [name]="column.name" (onAddTask)="store.dispatch( actions.addTask(column.id) )" (onColumnNameChange)="store.dispatch( actions.renameColumn(column.id, $event.target.name) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } } import { connect } from "react-redux"; export default connect( )( ); import { connect } from "../store"; export default { mixins: [connect( )] }; store: Store<any> this.store = store; this.actions = actions; store.subscribe(state => { });
  133. Container Component: Redux bindings import { connect } from "react-redux";

    import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *ngFor="let column of columns" [name]="column.name" (onAddTask)="store.dispatch( actions.addTask(column.id) )" (onColumnNameChange)="store.dispatch( actions.renameColumn(column.id, $event.target.name) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } } import { connect } from "react-redux"; export default connect( )( ); import { connect } from "../store"; export default { mixins: [connect( )] }; store: Store<any> this.store = store; this.actions = actions; store.subscribe(state => { }); ⛔
  134. Container Component: Behaviour import { connect } from "react-redux"; import

    * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *ngFor="let column of columns" [name]="column.name" (onAddTask)="store.dispatch( actions.addTask(column.id) )" (onColumnNameChange)="store.dispatch( actions.renameColumn(column.id, $event.target.name) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } }
  135. Container Component: Behaviour import { connect } from "react-redux"; import

    * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *ngFor="let column of columns" [name]="column.name" (onAddTask)="store.dispatch( actions.addTask(column.id) )" (onColumnNameChange)="store.dispatch( actions.renameColumn(column.id, $event.target.name) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } } import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} state => ({ columns: getColumnsWithTasks(state) }), actions
  136. Container Component: Behaviour import { connect } from "react-redux"; import

    * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *ngFor="let column of columns" [name]="column.name" (onAddTask)="store.dispatch( actions.addTask(column.id) )" (onColumnNameChange)="store.dispatch( actions.renameColumn(column.id, $event.target.name) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } } import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} state => ({ columns: getColumnsWithTasks(state) }), actions import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; (onAddTask)="store.dispatch( actions.addTask(column.id) )" (onColumnNameChange)="store.dispatch( actions.renameColumn(column.id, $event.target.name) )"> this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); });
  137. Container Component: Behaviour import { connect } from "react-redux"; import

    * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *ngFor="let column of columns" [name]="column.name" (onAddTask)="store.dispatch( actions.addTask(column.id) )" (onColumnNameChange)="store.dispatch( actions.renameColumn(column.id, $event.target.name) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } } import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} state => ({ columns: getColumnsWithTasks(state) }), actions @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; state => ({ columns: getColumnsWithTasks(state) }). actions import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; (onAddTask)="store.dispatch( actions.addTask(column.id) )" (onColumnNameChange)="store.dispatch( actions.renameColumn(column.id, $event.target.name) )"> this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); });
  138. Container Component: Behaviour import { connect } from "react-redux"; import

    * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; const Trello = ({ columns, renameColumn, addTaskToColumn }) => ( <Board> {columns.map(({ id, name, tasks }, index) => ( <Column key={id} name={name} onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} > {tasks.map(({ id: tId, ...rest }) => ( <Task {...rest} key={`task-${tId}`} /> ))} </Column> ))} </Board> ); export default connect( state => ({ columns: getColumnsWithTasks(state) }), actions )(Trello); <template> <board> <column v-for="(column, index) in state.columns" :key="column.id" :name="column.name" @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> <task v-for="task in column.tasks" :key="`task-${task.id}`" :name="task.name"/> </column> </board> </template> <script> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; import { connect } from "../store"; export default { mixins: [connect( state => ({ columns: getColumnsWithTasks(state) }). actions )] }; </script> import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; @Component({ selector: "app-board", template: ` <board> <column *ngFor="let column of columns" [name]="column.name" (onAddTask)="store.dispatch( actions.addTask(column.id) )" (onColumnNameChange)="store.dispatch( actions.renameColumn(column.id, $event.target.name) )"> <task *ngFor="let t of column.tasks" [name]="t.name"></task> </column> </board> ` }) export class BoardComponent { constructor(store: Store<any>) { this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); }); } } import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers"; onColumnNameChange={ (name) => renameColumn(id, name)} onAddTask={() => addTaskToColumn(id)} state => ({ columns: getColumnsWithTasks(state) }), actions @onColumnNameChange="(name) => renameColumn(id, name)" @onAddTask="() => addTaskToColumn(column.id)"> import * as actions from "../../common/actions"; import { getColumnsWithTasks } from "../../common/reducers"; state => ({ columns: getColumnsWithTasks(state) }). actions import * as actions from "../actions"; import { getColumnsWithTasks } from "../reducers/columns"; (onAddTask)="store.dispatch( actions.addTask(column.id) )" (onColumnNameChange)="store.dispatch( actions.renameColumn(column.id, $event.target.name) )"> this.store = store; this.actions = actions; store.subscribe(state => { this.columns = getColumnsWithTasks(state); });
  139. ∴ The web will keep changing. and so will our

    tools ∴ Framework wars are only a developers problem. nobody else cares
  140. ∴ The web will keep changing. and so will our

    tools ∴ Framework wars are only a developers problem. nobody else cares ∴ Framework-agnostic web applications helps us make peace both. redux is a good ally in this journey