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

Scalable Angular 2 Application Architecture

Scalable Angular 2 Application Architecture

Modern web applications have constantly growing requirements and their complexity grows exponentially. Some of the biggest challenges in front of us are state management, testability, flexibility and on top of that we need to develop the application at reasonable level of simplicity in order to ease the recruitment process.

In this presentation I’ll introduce a scalable Angular 2 application architecture, which answers the following requirements:
- Testability.
- Predictable state management.
- Scalable communication layer.
- Modular and robust design.

Minko Gechev

April 16, 2016
Tweet

More Decks by Minko Gechev

Other Decks in Programming

Transcript

  1. View Slide

  2. Scalable
    Application rchitecture
    github.com/mgechev
    twitter.com/mgechev
    blog.mgechev.com

    View Slide

  3. View Slide

  4. View Slide

  5. STANG2
    50% off

    View Slide

  6. Agenda

    View Slide

  7. – Martin Fowler
    “…decisions that are hard to
    change…”
    Architecture

    View Slide

  8. Story Time

    View Slide

  9. Dynamic
    Requirements

    View Slide

  10. View Slide

  11. Scalable
    communication layer

    View Slide

  12. Communication layer
    • RESTful API
    • WebSocket application service
    • WebRTC data-channel

    View Slide

  13. Various
    package formats

    View Slide

  14. Package formats
    • RESTful API
    • JSON commands
    • WebSocket application service
    • JSON-RPC
    • WebRTC data-channel
    • BERT-RPC

    View Slide

  15. Multiple state mutation
    sources

    View Slide

  16. Scalable
    team

    View Slide

  17. View Slide

  18. Lazy-loading

    View Slide

  19. Dynamic Requirements
    Scalable Communication Layer
    Various package formats
    Multiple state mutation sources
    Scalable team
    Lazy-loading

    View Slide

  20. Dynamic Requirements
    Scalable Communication Layer
    Various package formats
    Multiple state mutation sources
    Scalable team
    Lazy-loading

    View Slide

  21. abstraction |əbˈstrakʃ(ə)n|

    noun [ mass noun ]

    4 the process of considering something
    independently of its associations or attributes: the
    question cannot be considered in abstraction from
    the historical context in which it was raised.

    View Slide

  22. WebRTC
    Gateway
    WebSocket
    Gateway

    View Slide

  23. Gateway
    WebRTC
    Gateway
    WebSocket
    Gateway

    View Slide

  24. Dynamic Requirements
    Scalable Communication Layer
    Various package formats
    Multiple state mutation sources
    Scalable team
    Lazy-loading

    View Slide

  25. redux

    View Slide

  26. Dynamic Requirements
    Scalable Communication Layer
    Various package formats
    Multiple state mutation sources
    Scalable team
    Lazy-loading

    View Slide

  27. Modular

    View Slide

  28. .
    ʦʒʒ src
    ʮʒʒ multi-player
    ʔ ʮʒʒ commands
    ʔ ʮʒʒ components
    ʔ ʦʒʒ gateways
    ʮʒʒ single-player
    ʔ ʦʒʒ components
    ʮʒʒ home
    ʔ ʦʒʒ components
    ʦʒʒ shared

    View Slide

  29. .
    ʦʒʒ src
    ʮʒʒ multi-player
    ʔ ʮʒʒ commands
    ʔ ʮʒʒ components
    ʔ ʦʒʒ gateways
    ʮʒʒ single-player
    ʔ ʦʒʒ components
    ʮʒʒ home
    ʔ ʦʒʒ components
    ʦʒʒ shared
    /home

    View Slide

  30. .
    ʦʒʒ src
    ʮʒʒ multi-player
    ʔ ʮʒʒ commands
    ʔ ʮʒʒ components
    ʔ ʦʒʒ gateways
    ʮʒʒ single-player
    ʔ ʦʒʒ components
    ʮʒʒ home
    ʔ ʦʒʒ components
    ʦʒʒ shared
    /single-player

    View Slide

  31. .
    ʦʒʒ src
    ʮʒʒ multi-player
    ʔ ʮʒʒ commands
    ʔ ʮʒʒ components
    ʔ ʦʒʒ gateways
    ʮʒʒ single-player
    ʔ ʦʒʒ components
    ʮʒʒ home
    ʔ ʦʒʒ components
    ʦʒʒ shared
    /multi-player

    View Slide

  32. Sample Tech Stack
    • Angular 2
    • RxJS
    • ngrx
    • TypeScript
    • ImmutableJS

    View Slide

  33. RxJS
    in 2 slides

    View Slide

  34. [1, 2, 3]
    .map(n => n * 2)
    .filter(n => n > 2);
    higher-order-functions.ts

    View Slide

  35. let obs = Rx.Observable.create(observer => {
    let counter = 0;
    setInterval(() => observer.next(counter++), 1000);
    });
    obs
    .map(n => n * 2)
    .filter(n => n > 2)
    .subscribe(n => console.log(n));
    rx.ts

    View Slide

  36. let obs = Rx.Observable.create(observer => {
    let counter = 0;
    setInterval(() => observer.next(counter++), 1000);
    });
    obs
    .map(n => n * 2)
    .filter(n => n > 2)
    .subscribe(n => console.log(n));
    rx.ts

    View Slide

  37. Sample application

    View Slide

  38. View Slide

  39. View Slide

  40. High-level
    Architecture

    View Slide

  41. UI components
    Façade
    (provides simplified interface to the components)
    State management Async services
    Gateways
    (HTTP, WS, WebRTC)
    Commands
    (RESTful, RPC)
    Payloads
    (BERT, JSON)
    Store Reducers

    View Slide

  42. UI components
    Façade
    (provides simplified interface to the components)
    State management Async services
    Gateways
    (HTTP, WS, WebRTC)
    Commands
    (RESTful, RPC)
    Payloads
    (BERT, JSON)
    Store Reducers

    View Slide

  43. UI components
    Façade
    (provides simplified interface to the components)
    State management Async services
    Gateways
    (HTTP, WS, WebRTC)
    Commands
    (RESTful, RPC)
    Payloads
    (BERT, JSON)
    Store Reducers

    View Slide

  44. UI components
    Façade
    (provides simplified interface to the components)
    State management Async services
    Gateways
    (HTTP, WS, WebRTC)
    Commands
    (RESTful, RPC)
    Payloads
    (BERT, JSON)
    Store Reducers

    View Slide

  45. UI components
    Façade
    (provides simplified interface to the components)
    State management Async services
    Gateways
    (HTTP, WS, WebRTC)
    Commands
    (RESTful, RPC)
    Payloads
    (BERT, JSON)
    Store Reducers

    View Slide

  46. UI components

    View Slide

  47. UI components
    Façade
    (provides simplified interface to the components)
    State management Async services
    Gateways
    (HTTP, WS, WebRTC)
    Commands
    (RESTful, RPC)
    Payloads
    (BERT, JSON)
    Store Reducers

    View Slide

  48. export class GameComponent implements AfterViewInit {
    @Input() text: string;
    @Output() change: EventEmitter …
    constructor(private _model: GameModel …) {}
    changeHandler(data: string) {
    this._model.onProgress(data);
    }
    get invalid() {
    return this._model.game$
    .scan((accum: boolean, current: any) => {
    return (current && current.get(‘invalid’)
    || accum;
    }, false);
    }
    }
    game.component.ts

    View Slide

  49. export class GameComponent implements AfterViewInit {
    @Input() text: string;
    @Output() change: EventEmitter …
    constructor(private _model: GameModel …) {}
    changeHandler(data: string) {
    this._model.onProgress(data);
    }
    get invalid() {
    return this._model.game$
    .scan((accum: boolean, current: any) => {
    return (current && current.get(‘invalid’)
    || accum;
    }, false);
    }
    }
    game.component.ts

    View Slide

  50. UI components
    Façade
    (provides simplified interface to the components)
    State management Async services
    Gateways
    (HTTP, WS, WebRTC)
    Commands
    (RESTful, RPC)
    Payloads
    (BERT, JSON)
    Store Reducers

    View Slide

  51. game.model.ts
    @Injectable()
    export class GameModel extends Model {
    game$: Observable;
    constructor(protected _store: Store,
    @Inject(AsyncService) _services) {
    super(_services || []);
    this.game$ = this._store.select('game');
    }
    ...
    completeGame(time: number, text: string) {
    const action = GameActions.completeGame(time, text);
    this._store.dispatch(action);
    this.performAsyncAction(action)
    .subscribe(() => console.log('Done!'));
    }
    }

    View Slide

  52. game.model.ts
    @Injectable()
    export class GameModel extends Model {
    game$: Observable;
    constructor(protected _store: Store,
    @Inject(AsyncService) _services) {
    super(_services || []);
    this.game$ = this._store.select('game');
    }
    ...
    completeGame(time: number, text: string) {
    const action = GameActions.completeGame(time, text);
    this._store.dispatch(action);
    this.performAsyncAction(action)
    .subscribe(() => console.log('Done!'));
    }
    }

    View Slide

  53. @Injectable()
    export class GameModel extends Model {
    game$: Observable;
    constructor(protected _store: Store,
    @Inject(AsyncService) _services) {
    super(_services || []);
    this.game$ = this._store.select('game');
    }
    ...
    completeGame(time: number, text: string) {
    const action = GameActions.completeGame(time, text);
    this._store.dispatch(action);
    this.performAsyncAction(action)
    .subscribe(() => console.log('Done!'));
    }
    }
    game.model.ts

    View Slide

  54. @Injectable()
    export class GameModel extends Model {
    game$: Observable;
    constructor(protected _store: Store,
    @Inject(AsyncService) _services) {
    super(_services || []);
    this.game$ = this._store.select('game');
    }
    ...
    completeGame(time: number, text: string) {
    const action = GameActions.completeGame(time, text);
    this._store.dispatch(action);
    this.performAsyncAction(action)
    .subscribe(() => console.log('Done!'));
    }
    }
    game.model.ts

    View Slide

  55. @Injectable()
    export class GameModel extends Model {
    game$: Observable;
    constructor(protected _store: Store,
    @Inject(AsyncService) _services) {
    super(_services || []);
    this.game$ = this._store.select('game');
    }
    ...
    completeGame(time: number, text: string) {
    const action = GameActions.completeGame(time, text);
    this._store.dispatch(action);
    this.performAsyncAction(action)
    .subscribe(() => console.log('Done!'));
    }
    }
    game.model.ts

    View Slide

  56. @Injectable()
    export class GameModel extends Model {
    game$: Observable;
    constructor(protected _store: Store,
    @Inject(AsyncService) _services) {
    super(_services || []);
    this.game$ = this._store.select('game');
    }
    ...
    completeGame(time: number, text: string) {
    const action = GameActions.completeGame(time, text);
    this._store.dispatch(action);
    this.performAsyncAction(action)
    .subscribe(() => console.log('Done!'));
    }
    }
    game.model.ts

    View Slide

  57. UI components
    Façade
    (provides simplified interface to the components)
    State management Async services
    Gateways
    (HTTP, WS, WebRTC)
    Commands
    (RESTful, RPC)
    Payloads
    (BERT, JSON)
    Store Reducers

    View Slide

  58. Component
    Model
    Store
    Dispatcher
    startGame()
    dispatch(action)
    applyReducers(action, store)
    next(state)

    View Slide

  59. Component
    Model
    Store
    Dispatcher
    startGame()
    dispatch(action)
    applyReducers(action, store)
    next(state)

    View Slide

  60. game.reducer.ts
    export const gameReducer =
    (state: any = initialState.get(‘game'),
    action: Action) => {
    switch (action.type) {
    case START_GAME:
    state = fromJS({});
    break;
    case INVALID_GAME:
    state = state.set('invalid', true);
    break;
    case GAME_PROGRESS:
    state = state.set(‘currentText',
    action.payload.text);
    break;
    }
    return state;
    };

    View Slide

  61. game.reducer.ts
    export const gameReducer =
    (state: any = initialState.get(‘game'),
    action: Action) => {
    switch (action.type) {
    case START_GAME:
    state = fromJS({});
    break;
    case INVALID_GAME:
    state = state.set('invalid', true);
    break;
    case GAME_PROGRESS:
    state = state.set(‘currentText',
    action.payload.text);
    break;
    }
    return state;
    };

    View Slide

  62. game.reducer.ts
    export const gameReducer =
    (state: any = initialState.get(‘game'),
    action: Action) => {
    switch (action.type) {
    case START_GAME:
    state = fromJS({});
    break;
    case INVALID_GAME:
    state = state.set('invalid', true);
    break;
    case GAME_PROGRESS:
    state = state.set(‘currentText',
    action.payload.text);
    break;
    }
    return state;
    };

    View Slide

  63. game.reducer.ts
    export const gameReducer =
    (state: any = initialState.get(‘game'),
    action: Action) => {
    switch (action.type) {
    case START_GAME:
    state = fromJS({});
    break;
    case INVALID_GAME:
    state = state.set('invalid', true);
    break;
    case GAME_PROGRESS:
    state = state.set(‘currentText',
    action.payload.text);
    break;
    }
    return state;
    };

    View Slide

  64. Component
    Model
    Store
    Dispatcher
    startGame()
    dispatch(action)
    applyReducers(action, store)
    next(state)

    View Slide

  65. game.component.ts

    get invalid() {
    return this._model.game$
    .scan((accum: boolean, current: any) => {
    return current.get('invalid') || accum;
    }, false);
    }

    View Slide

  66. game.component.ts

    get invalid() {
    return this._model.game$
    .scan((accum: boolean, current: any) => {
    return current.get('invalid') || accum;
    }, false);
    }

    View Slide

  67. game.component.ts

    get invalid() {
    return this._model.game$
    .scan((accum: boolean, current: any) => {
    return current.get('invalid') || accum;
    }, false);
    }

    View Slide

  68. game.component.html


    The game is invalid...


    View Slide

  69. game.component.html


    The game is invalid...


    View Slide

  70. Async Services

    View Slide

  71. UI components
    Façade
    (provides simplified interface to the components)
    State management Async services
    Gateways
    (HTTP, WS, WebRTC)
    Commands
    (RESTful, RPC)
    Payloads
    (BERT, JSON)
    Store Reducers

    View Slide

  72. Remote
    Service
    App

    View Slide

  73. Remote
    Service
    App

    View Slide

  74. export abstract class AsyncService {
    abstract process(data: Action): Observable;
    }
    base.async-service.ts

    View Slide

  75. export class GameP2PService extends AsyncService {
    constructor(private _rtcGateway: WebRTCGateway, private _store: Store) {
    _rtcGateway.dataStream
    .map((data: any) => JSON.parse(data.toString()))
    .subscribe((command: any) => {
    switch (command.method) {
    case PROGRESS:
    _store.dispatch(P2PActions.progress(command.payload.text));
    break;
    }
    });
    }
    process(action: Action) {
    const commandBuilder = buildP2PCommand(action);
    if (!commandBuilder) {
    console.warn('This command is not supported');
    return Observable.create((obs: Observer) => obs.complete());
    } else
    return commandBuilder(baseCommand).invoke();
    }
    }
    game-p2p.async-service.ts

    View Slide

  76. export class GameP2PService extends AsyncService {
    constructor(private _rtcGateway: WebRTCGateway, private _store: Store) {
    _rtcGateway.dataStream
    .map((data: any) => JSON.parse(data.toString()))
    .subscribe((command: any) => {
    switch (command.method) {
    case PROGRESS:
    _store.dispatch(P2PActions.progress(command.payload.text));
    break;
    }
    });
    }
    process(action: Action) {
    const commandBuilder = buildP2PCommand(action);
    if (!commandBuilder) {
    console.warn('This command is not supported');
    return Observable.create((obs: Observer) => obs.complete());
    } else
    return commandBuilder(baseCommand).invoke();
    }
    }
    game-p2p.async-service.ts

    View Slide

  77. export class GameP2PService extends AsyncService {
    constructor(private _rtcGateway: WebRTCGateway, private _store: Store) {
    _rtcGateway.dataStream
    .map((data: any) => JSON.parse(data.toString()))
    .subscribe((command: any) => {
    switch (command.method) {
    case PROGRESS:
    _store.dispatch(P2PActions.progress(command.payload.text));
    break;
    }
    });
    }
    process(action: Action) {
    const commandBuilder = buildP2PCommand(action);
    if (!commandBuilder) {
    console.warn('This command is not supported');
    return Observable.create((obs: Observer) => obs.complete());
    } else
    return commandBuilder(baseCommand).invoke();
    }
    }
    game-p2p.async-service.ts

    View Slide

  78. But what if…

    View Slide

  79. Model
    S1 S2

    View Slide

  80. Model
    S1 S2
    A

    View Slide

  81. Model
    S1 S2
    A

    View Slide

  82. Model
    S1 S2
    A

    View Slide

  83. Model
    S1 S2
    A

    View Slide

  84. Model
    S1 S2
    A

    View Slide

  85. Model
    S1 S2
    A

    View Slide

  86. Immutability

    View Slide

  87. let user = new Map();
    user = user.set('name', 'Joe');
    // { name: 'Joe' }
    console.log(user.toJS());
    immutable.js

    View Slide

  88. No
    static typing

    View Slide

  89. Immutability or Static Typing

    View Slide

  90. Why not
    Both?

    View Slide

  91. export interface IUser {
    id?: number;
    gender?: number;
    email?: string;
    }
    const userRecord = Immutable.Record({
    id: 0,
    gender: 0,
    email: null
    });
    export class User extends userRecord implements IUser {
    id: number;
    gender: number;
    email: string;
    constructor(config: IUser) {
    super(config);
    }
    }
    immutable-records.ts

    View Slide

  92. export interface IUser {
    id?: number;
    gender?: number;
    email?: string;
    }
    const userRecord = Immutable.Record({
    id: 0,
    gender: 0,
    email: null
    });
    export class User extends userRecord implements IUser {
    id: number;
    gender: number;
    email: string;
    constructor(config: IUser) {
    super(config);
    }
    }
    immutable-records.ts

    View Slide

  93. export interface IUser {
    id?: number;
    gender?: number;
    email?: string;
    }
    const userRecord = Immutable.Record({
    id: 0,
    gender: 0,
    email: null
    });
    export class User extends userRecord implements IUser {
    id: number;
    gender: number;
    email: string;
    constructor(config: IUser) {
    super(config);
    }
    }
    immutable-records.ts

    View Slide

  94. export interface IUser {
    id?: number;
    gender?: number;
    email?: string;
    }
    const userRecord = Immutable.Record({
    id: 0,
    gender: 0,
    email: null
    });
    export class User extends userRecord implements IUser {
    id: number;
    gender: number;
    email: string;
    constructor(config: IUser) {
    super(config);
    }
    }
    immutable-records.ts

    View Slide

  95. View Slide

  96. Recap

    View Slide

  97. Async services
    Business facade
    Business logic
    Communication logic
    Immutable app state
    Component tree
    root cmp
    sign-up form
    user
    UserModel
    User Action
    Creator
    signup(data)
    signup(data)
    RESTful Async
    Service
    process(action)
    userReducer
    register()
    RESTful
    CommandBuilder
    build(action)
    Restful
    Command
    Restful Gateway
    invoke()
    send()
    creates()
    uses
    as user$
    uses user$
    Stream
    Dependency
    Action (manipulation/method call)
    User registration
    user.email = email
    user.name = name

    View Slide

  98. Async services
    Business facade
    Business logic
    Communication logic
    Immutable app state
    Component tree
    root cmp
    sign-up form
    user
    UserModel
    User Action
    Creator
    signup(data)
    RESTful Async
    Service
    process(action)
    userReducer
    register()
    RESTful
    CommandBuilder
    build(action)
    Restful
    Command
    Restful Gateway
    invoke()
    send()
    creates()
    uses
    as user$
    uses user$
    Stream
    Dependency
    Action (manipulation/method call)
    User registration
    user.email = email
    user.name = name
    signup(data)

    View Slide

  99. Async services
    Business facade
    Business logic
    Communication logic
    Immutable app state
    Component tree
    root cmp
    sign-up form
    user
    UserModel
    User Action
    Creator
    RESTful Async
    Service
    process(action)
    userReducer
    register()
    RESTful
    CommandBuilder
    build(action)
    Restful
    Command
    Restful Gateway
    invoke()
    send()
    creates()
    uses
    as user$
    uses user$
    Stream
    Dependency
    Action (manipulation/method call)
    User registration
    user.email = email
    user.name = name
    signup(data)
    signup(data)

    View Slide

  100. Async services
    Business facade
    Business logic
    Communication logic
    Immutable app state
    Component tree
    root cmp
    sign-up form
    user
    UserModel
    User Action
    Creator
    RESTful Async
    Service
    process(action)
    userReducer
    register()
    RESTful
    CommandBuilder
    build(action)
    Restful
    Command
    Restful Gateway
    invoke()
    send()
    creates()
    uses
    as user$
    uses user$
    Stream
    Dependency
    Action (manipulation/method call)
    User registration
    user.email = email
    user.name = name
    signup(data)
    signup(data)

    View Slide

  101. Async services
    Business facade
    Business logic
    Communication logic
    Immutable app state
    Component tree
    root cmp
    sign-up form
    user
    UserModel
    User Action
    Creator
    RESTful Async
    Service
    process(action)
    userReducer
    register()
    RESTful
    CommandBuilder
    build(action)
    Restful
    Command
    Restful Gateway
    invoke()
    send()
    creates()
    uses
    as user$
    uses user$
    Stream
    Dependency
    Action (manipulation/method call)
    User registration
    user.email = email
    user.name = name
    signup(data)
    signup(data)

    View Slide

  102. Async services
    Business facade
    Business logic
    Communication logic
    Immutable app state
    Component tree
    root cmp
    sign-up form
    user
    UserModel
    User Action
    Creator
    RESTful Async
    Service
    process(action)
    userReducer
    register()
    RESTful
    CommandBuilder
    build(action)
    Restful
    Command
    Restful Gateway
    invoke()
    send()
    creates()
    uses
    as user$
    uses user$
    Stream
    Dependency
    Action (manipulation/method call)
    User registration
    user.email = email
    user.name = name
    signup(data)
    signup(data)

    View Slide

  103. Async services
    Business facade
    Business logic
    Communication logic
    Immutable app state
    Component tree
    root cmp
    sign-up form
    user
    UserModel
    User Action
    Creator
    RESTful Async
    Service
    process(action)
    userReducer
    register()
    RESTful
    CommandBuilder
    build(action)
    Restful
    Command
    Restful Gateway
    invoke()
    send()
    creates()
    uses
    as user$
    uses user$
    Stream
    Dependency
    Action (manipulation/method call)
    User registration
    user.email = email
    user.name = name
    signup(data)
    signup(data)

    View Slide

  104. Async services
    Business facade
    Business logic
    Communication logic
    Immutable app state
    Component tree
    root cmp
    sign-up form
    user
    UserModel
    User Action
    Creator
    RESTful Async
    Service
    process(action)
    userReducer
    register()
    RESTful
    CommandBuilder
    build(action)
    Restful
    Command
    Restful Gateway
    invoke()
    send()
    creates()
    uses
    as user$
    uses user$
    Stream
    Dependency
    Action (manipulation/method call)
    User registration
    user.email = email
    user.name = name
    signup(data)
    signup(data)

    View Slide

  105. Async services
    Business facade
    Business logic
    Communication logic
    Immutable app state
    Component tree
    root cmp
    sign-up form
    user
    UserModel
    User Action
    Creator
    RESTful Async
    Service
    process(action)
    userReducer
    register()
    RESTful
    CommandBuilder
    build(action)
    Restful
    Command
    Restful Gateway
    invoke()
    send()
    creates()
    uses
    as user$
    uses user$
    Stream
    Dependency
    Action (manipulation/method call)
    User registration
    user.email = email
    user.name = name
    signup(data)
    signup(data)

    View Slide

  106. Async services
    Business facade
    Business logic
    Communication logic
    Immutable app state
    Component tree
    root cmp
    sign-up form
    user
    UserModel
    User Action
    Creator
    signup(data)
    RESTful Async
    Service
    process(action)
    userReducer
    register()
    RESTful
    CommandBuilder
    build(action)
    Restful
    Command
    Restful Gateway
    invoke()
    send()
    creates()
    uses
    as user$
    uses user$
    Stream
    Dependency
    Action (manipulation/method call)
    User registration
    user.email = email
    user.name = name
    signup(data)

    View Slide

  107. Properties…
    • Predictable state management
    • Testable (easy to mock services thanks to DI)
    • Not coupled to any remote service
    • Not coupled to any message format
    • Model can use different services based on context
    • Easy management of async events

    View Slide

  108. Properties…
    • Predictable state management
    • Testable (easy to mock services thanks to DI)
    • Not coupled to any remote service
    • Not coupled to any message format
    • Model can use different services based on context
    • Easy management of async events

    View Slide

  109. Properties…
    • Predictable state management
    • Testable (easy to mock services thanks to DI)
    • Not coupled to any remote service
    • Not coupled to any message format
    • Model can use different services based on context
    • Easy management of async events

    View Slide

  110. Properties…
    • Predictable state management
    • Testable (easy to mock services thanks to DI)
    • Not coupled to any remote service
    • Not coupled to any message format
    • Model can use different services based on context
    • Easy management of async events

    View Slide

  111. Properties…
    • Predictable state management
    • Testable (easy to mock services thanks to DI)
    • Not coupled to any remote service
    • Not coupled to any message format
    • Model can use different services based on context
    • Easy management of async events

    View Slide

  112. Properties…
    • Predictable state management
    • Testable (easy to mock services thanks to DI)
    • Not coupled to any remote service
    • Not coupled to any message format
    • Model can use different services based on context
    • Easy management of async events

    View Slide

  113. Properties…
    • Predictable state management
    • Testable (easy to mock services thanks to DI)
    • Not coupled to any remote service
    • Not coupled to any message format
    • Model can use different services based on context
    • Easy management of async events

    View Slide

  114. Resources
    • redux
    • ngrx
    • Scalable Single-Page Application Architecture
    • Demo

    View Slide

  115. Thank you!
    github.com/mgechev
    twitter.com/mgechev
    blog.mgechev.com

    View Slide