Unidirectional AngularJS
Alex Taylor
Overview
- Angular App Architecture
- Flux Overview
- Flux + Angular
- Recap
Managing application state is hard
- Data flow
- 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
-
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
- Slide deck: slides.com/alextaylor/unidirectional-angularjs
- Alt.js Example: github.com/alexmckenley/superheroic-flux
- Redux Example: github.com/alexmckenley/superheroic-redux