Unidirectional AngularJS

Alex Taylor

Overview

  • Angular App Architecture
  • Flux Overview
  • Flux + Angular
  • Recap

Managing application state is hard

  1. Data flow
  2. Code organization

Why?

Data Flow

Code Organization

src/
  |- app/
  |  |- home/
  |  |- about/
  |  |- contact/
  |  |
  |  |- directives/
  |  |- services/
  |  |- filters/
  |  |- app.js

What Are Services?

  • When should I use a service?
  • Should they be stateless?
  • Different kinds of services?

What Are Services?

  • Services are singletons
  • Services are not architecture
  • Services enable architecture

Services are objects you can use to organize and share code across your app.

2 Problems

  • Data flow
    • MVC architecture in large apps can become difficult to reason about
       
  • Code Organization
    • Angular services need supporting architecture or guidelines around their use 

Flux

  • Application Architecture, not a framework or library
  • Unidirectional data flow
  • Popularized by React, but very flexible

Flux implementations

  • Redux
  • Alt
  • OmniscientJS
  • Flummox
  • Fluxxor
  • Flux This
  • MartyJS
  • McFly
  • Delorean
  • Lux
  • Reflux
  • Fluxible
  • Fluxy
  • Material Flux...

and so on...

Flux Overview

Folder structure

src/
  |- app/
  |  |- actions/
  |  |- components/
  |  |- dispatcher/
  |  |- stores/
  |  |- utils/
  |  |- app.js

services

Flux + Angular

Why use Angular 1.x?

  • Stable, well tested, proven reliable
  • Developer familiarity + style guides/patterns
  • Angular 2 still new and changing
  • Upgrade path to Angular 2 through
    ng-upgrade

Libraries in Angular

  • $scope.$digest, $scope.$apply, $scope.$watch
if (!$scope.$$phase) $scope.$apply()

Existing options

  • Easy to get started
  • Helpful documentation, good Angular specific insights
  • Not necessary
    • Syntactic sugar
    • Specific flux implementation
  • More difficult migration path

 Flux is simply a pattern that you can incorporate on your own - author of ng-flux

Example Chat App

Creating Dispatcher (Alt)

angular.module('shf.alt', [])

.factory('alt', function alt() {
    return new window.Alt();
});

Creating Actions (Alt)

angular.module('shf.actions.chat-thread', [])

.factory('chatThreadActions', function chatThreadActionsFactory(alt) {
    function ChatThreadActions() {
        this.generateActions('clickThread');
    }
    
    return alt.createActions(ChatThreadActions);
});

Creating a Store (Alt)

angular.module('shf.stores.message', [])

.factory('messageStore', function messageStoreFactory(alt, chatMessageActions) {
    function MessageStore() {
        this.bindActions(chatMessageActions);

        this.messages = {};
    }

    MessageStore.prototype = {
        constructor: MessageStore,
        onCreateMessage: function(text) {},
    };

    MessageStore.get = function(id) {};
    MessageStore.getAll = function() {};

    return alt.createStore(MessageStore, 'MessageStore');
});

Creating Utils (Alt)

angular.module('shf.utils.chat-message', [])

.factory('chatMessageUtils', function chatMessageUtils() {
    var util = {
        convertRawMessage: convertRawMessage
    };

    function convertRawMessage(rawMessage, currentThreadID) {
        return {
            id: rawMessage.id,
            authorName: rawMessage.authorName,
            date: new Date(rawMessage.timestamp),
            text: rawMessage.text,
        };
    }

    return util;
});

Note

  • Always use Angular services for async actions
    • $http
    • $q
    • $timeout
    • $interval

Connecting the views

  • Controller-views
    • In charge of listening to stores
    • Often found at the top of the hierarchy
    • Pass data to children
       
  • Simple components
    • Small, easy to test
    • Receive data through attributes, and emit actions
    • No internal state*

Controller-views

.controller('MessageSectionCtrl', function(messageStore, threadStore) {
    var ctrl = this;

    init();

    function init() {
        messageStore.listen(getStateFromStores);
        threadStore.listen(getStateFromStores);

        getStateFromStores();
    }

    function getStateFromStores() {
        ctrl.messages = messageStore.getAllForCurrentThread();
        ctrl.thread = threadStore.getCurrent();
    }
});

Simple Components

  • ​Keep as stateless as possible​, data should be passed through attributes
  • Use `&`
  • Use `@`
  • Don't use `=`
  • State vs Props in React

Simple Component

.directive('messageListItem', function() {
    return {
        bindToController: true,
        controller: 'MessageListItemCtrl as ctrl',
        scope: {
            getMessage: '&message'
        },
        templateUrl: 'message-list-item.tpl.html'
    };
})

.controller('MessageListItemCtrl', function(chatMessageActions) {
    this.text = '';

    this.markAsRead = function(text) {
        chatMessageActions.markAsRead(ctrl.getMessage().id);
    };
});

Internal State

  • Cannot always be avoided
  • When necesary, angular makes things really easy
<input type="text" ng-model="ctrl.message" />
  • React implemented valueLink to do just this
<input type="text" valueLink={ this.state.message } />

Recap

  • Flux makes app easier to reason about
  • Better organization of services/components
  • Good separation of concerns, easy to change view layer
  • No $watch, $apply, or $$phase :)
  • More fun

Demo