• Stars
    star
    313
  • Rank 133,714 (Top 3 %)
  • Language
    JavaScript
  • Created about 10 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Use the FLUX architecture with Angular JS

flux-angular

flux-angular makes it easy to implement a performant, scalable, and clean flux application architecture in an angular application. It does this by providing access to a new angular.store method for holding immutable application state using Baobab. The flux service is exposed for dispatching actions using the Yahoo Dispatchr. $scope.$listenTo is exposed as a way to respond to changes in a store and sync them with the view-model.

Installation

Use npm to install and then require('flux-angular') in your application.

npm install --save flux-angular

Usage

By default the state in a store is immutable which means it cannot be changed once created, except through a defined API. If you're unfamiliar with the benefits of immutable data this article and this video explain the theory and benefits.

Some of the pros:

  • Faster reads because there is no deep cloning
  • Less renders and $scope.$watch triggers because the reference to the object doesn't change unless the object changes
  • Computed data (by using this.monkey in a store) can be observed in the same way as raw data. This allows for more logic to live in the store (e.g. a sorted version of a list) and for angular to only re-render when the raw data underlying the computed data changes. See the full docs.
  • Changes are batched together so that multiple dispatches only trigger one re-render is needed. This can be disabled by setting the asynchronous option to false.

Some of the cons:

  • Need to use a slightly more verbose API for changing state.
  • Slightly slower writes
  • ng-repeat with immutable objects need to use the track by option. Otherwise angular will fail, complaining it can't add the $$hashKey variable to the collection items.
  • If your directive/controller does need to modify the immutable object (e.g. for use with ng-model) you must use something like the angular.copy function when pulling it out of the store. However, note that this has a performance impact. Also note that primitives are always copied so they don't need to be cloned.

Conclusion: It is faster, but a bit more verbose!

Configuration

Options that can be specified for the Baobab immutable store are described here. For example, you may want to turn off immutability in production for a slight speed increase, which you can do by setting the defaults:

angular.module('app', ['flux']).config(function(fluxProvider) {
  fluxProvider.setImmutableDefaults({ immutable: false })
})

By default, your $listenTo callbacks will be wrapped in $evalAsync to ensure they are executed as part of a digest cycle. You can turn this off like this:

angular.module('app', ['flux']).config(function(fluxProvider) {
  fluxProvider.useEvalAsync(false)
})

Create a store

angular.module('app', ['flux']).store('MyStore', function() {
  return {
    initialize: function() {
      this.state = this.immutable({
        comments: [],
      })
    },
    handlers: {
      ADD_COMMENT: 'addComment',
    },
    addComment: function(comment) {
      this.state.push('comments', comment)
    },
    exports: {
      getLatestComment: function() {
        var comments = this.state.get('comments')
        return comments[comments.length - 1]
      },
      get comments() {
        return this.state.get('comments')
      },
    },
  }
})

See the Baobab docs for documentation on how to retrieve and update the immutable state.

Two way databinding

angular
  .module('app', ['flux'])
  .store('MyStore', function() {
    return {
      initialize: function() {
        this.state = this.immutable({
          person: {
            name: 'Jane',
            age: 30,
            likes: 'awesome stuff',
          },
        })
      },
      handlers: {
        SAVE_PERSON: 'savePerson',
      },
      savePerson: function(payload) {
        this.state.merge('person', payload.person)
      },
      saveName: function(payload) {
        this.state.set(['person', 'name'], payload.name)
      },
      exports: {
        get person() {
          return this.state.get('person')
        },
      },
    }
  })
  .component('myComponent', {
    templateUrl: 'myComponent.html',
    controller: function(MyStore, myStoreActions) {
      var vm = this
      vm.savePerson = myStoreActions.savePerson
      vm.$listenTo(MyStore, setStoreVars)
      vm.$listenTo(MyStore, ['person', 'name'], setName)

      function setStoreVars() {
        $scope.person = MyStore.person
      }

      function setName() {
        $scope.name = MyStore.person.name
      }
    },
  })
  .service('myStoreActions', function(flux) {
    var service = {
      savePerson: savePerson,
    }

    return service

    function savePerson(person) {
      flux.dispatch('SAVE_PERSON', { person: person })
    }
  })

By using the .$listenTo() method we set up a callback that will be fired whenever any state in the store changes. Also demonstrated via the setName example is that you can trigger an update only when a specific node of the tree is changed. This gives you more control over how controllers and directives react to changes in the store. Thus, when we dispatch the updated values and merge them into the immutable object the callback is triggered and our scope properties can be synced with the store.

When using .$listenTo(), the listener will be cleaned up when the scope of the controller is destroyed. Alternatively, flux.listenTo() does not unsubscribe when a scope is destroyed, instead it returns a callback that will unsubscribe from the event listener. Unlike .$listenTo(), flux.listenTo() will not call the callback as part of setting up the listener.

Dispatch actions

It can be helpful to create a service for dispatching actions related to a store since different components may want to trigger the same action. Additionally, the action methods are the place where the coordination of multiple dispatch calls occur, as shown in the addComment method below.

angular
  .module('app', ['flux'])
  .factory('commentActions', function($http, flux) {
    var service = {
      setTitle: setTitle,
      addComment: addComment,
    }
    return service

    // An exaple of a basic dispatch with the first argument being the action key and a payload.
    // One or more stores is expected to have a handler for COMMENT_SET_TITLE
    function setTitle(title) {
      flux.dispatch('COMMENT_SET_TITLE', { title: title })
    }

    // It is not recommended to run async operations in your store handlers. The
    // reason is that you would have a harder time testing and the **waitFor**
    // method also requires the handlers to be synchronous. You solve this by having
    // async services, also called **action creators** or **API adapters**.
    function addComment(comment) {
      flux.dispatch('COMMENT_ADD', { comment: comment })
      $http
        .post('/comments', comment)
        .then(function() {
          flux.dispatch('COMMENT_ADD_SUCCESS', { comment: comment })
        })
        .catch(function(error) {
          flux.dispatch('COMMENT_ADD_ERROR', { comment: comment, error: error })
        })
    }
  })

Wait for other stores to complete their handlers

The waitFor method allows you to let other stores handle the action before the current store acts upon it. You can also pass an array of stores. It was decided to run this method straight off the store, as it gives more sense and now the callback is bound to the store itself.

angular
  .module('app', ['flux'])
  .store('CommentsStore', function() {
    return {
      initialize: function() {
        this.state = this.immutable({ comments: [] })
      },
      handlers: {
        ADD_COMMENT: 'addComment',
      },
      addComment: function(comment) {
        this.waitFor('NotificationStore', function() {
          this.state.push('comments', comment)
        })
      },
      getComments: function() {
        return this.state.get('comments')
      },
    }
  })
  .store('NotificationStore', function() {
    return {
      initialize: function() {
        this.state = this.immutable({ notifications: [] })
      },
      handlers: {
        ADD_COMMENT: 'addNotification',
      },
      addNotification: function(comment) {
        this.state.push('notifications', 'Something happened')
      },
      exports: {
        getNotifications: function() {
          return this.state.get('notifications')
        },
      },
    }
  })

Testing stores

When Angular Mock is loaded flux-angular will reset stores automatically.

describe('adding items', function() {
  beforeEach(module('app'))

  it('it should add strings dispatched to addItem', inject(function(
    MyStore,
    flux
  ) {
    flux.dispatch('ADD_ITEM', 'foo')
    expect(MyStore.getItems()).toEqual(['foo'])
  }))

  it('it should add number dispatched to addItem', inject(function(
    MyStore,
    flux
  ) {
    flux.dispatch('ADD_ITEM', 1)
    expect(MyStore.getItems()).toEqual([1])
  }))
})

If you are doing integration tests using protractor you will want to disable asynchronous event dispatching in Baobab since it relies on setTimeout, which protractor can't detect:

browser.addMockModule('protractorFixes', function() {
  angular.module('protractorFixes', []).config(function(fluxProvider) {
    fluxProvider.setImmutableDefaults({ asynchronous: false })
  })
})

Performance

Any $scopes listening to stores are removed when the $scope is destroyed. Immutability (which uses Object.freeze) can be disabled in production.

FAQ

Cannot call dispatch while another dispatch is executing

This is a problem/feature that is generic to the flux architecture. It can be solved by having an action dispatch multiple events.

Did you really monkeypatch Angular?

Yes. Angular has a beautiful API (except directives ;-) ) and I did not want flux-angular to feel like an alien syntax invasion, but rather it being a natural part of the Angular habitat. Angular 1.x is a stable codebase and I would be very surprised if this monkeypatch would be affected in later versions.

Contributing

Consider using Visual Studio Code if you don't already have a favorite editor. The project includes a debug launch configuration and will recommend appropriate extensions for this project.

  1. Fork the official repository
  2. Clone your fork: git clone https://github.com/<your-username>/gatsby.git
  3. Setup the repo and install dependencies: npm run bootstrap
  4. Make sure that tests are passing for you: npm test
  5. Add tests and code for your changes
  6. Make sure tests still pass: npm test
  7. Commit, push, and pull request your changes

License

flux-angular is licensed under the MIT license.

The MIT License (MIT)

Copyright (c) 2014 Christian Alfoni

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

More Repositories

1

formsy-react

A form input builder and validator for React JS
JavaScript
2,595
star
2

webpack-express-boilerplate

A boilerplate for running a Webpack workflow in Node express
JavaScript
1,398
star
3

webpack-bin

A webpack code sandbox
JavaScript
710
star
4

react-app-boilerplate

React application boilerplate
JavaScript
305
star
5

EmptyBox

A complete isomorphic hackable blog service based on React JS
JavaScript
211
star
6

react-webpack-cookbook

Temp repo for transfer
HTML
187
star
7

flux-react-boilerplate

A boilerplate for a full FLUX architecture
JavaScript
166
star
8

react-states

Explicit states for predictable user experiences
TypeScript
139
star
9

flux-react

A library combining tools to develop with a FLUX architecture
JavaScript
139
star
10

markdown-to-react-components

Convert markdown into react components
JavaScript
132
star
11

rxjs-react-component

A component allowing you to change state using observables
JavaScript
119
star
12

create-ssl-certificate

Command line tool to create self signed SSL certificate
JavaScript
97
star
13

immutable-store

An easy to use immutable store for React JS and FLUX architecture
JavaScript
90
star
14

impact

Reactive state management for React
TypeScript
86
star
15

webpack-example

An example of webpack workflow
JavaScript
78
star
16

proxy-state-tree

An implementation of the Mobx/Vue state tracking approach, for library authors
JavaScript
66
star
17

timsy

Agnostic functional state machine with epic type support
TypeScript
58
star
18

redux-nodes

Nodes instead of reducers
TypeScript
47
star
19

react-simple-flex

An intuitive abstraction over flexbox
JavaScript
47
star
20

reactive-router

A reactive wrapper around Page JS
JavaScript
46
star
21

react-addressbar

Take control of the addressbar in a reactive way
JavaScript
42
star
22

form-data-to-object

Converts application/x-www-form-urlencoded keys to plain JS object
JavaScript
36
star
23

react-packaging

Examples of how to create a workflow with React JS using different packaging tools
JavaScript
31
star
24

jflux

An easy to use unidirectional component based framework
JavaScript
29
star
25

objectory

Object factory honoring JavaScripts prototype delegation
JavaScript
27
star
26

npm-extractor

Extracts npm packages into memory
JavaScript
18
star
27

emmis

Create a chaining API for your application
TypeScript
17
star
28

isomorphic-react-baobab-example

An example related to article about using baobab to create an isomorphic react app
JavaScript
17
star
29

process-control

A tool for managing continuous async process by starting, stopping, restarting and disposing
TypeScript
17
star
30

flutter_observable_state

Observable state for flutter applications
Dart
16
star
31

angular-flux

A plugin for Angular JS that lets you write code with the FLUX pattern
JavaScript
16
star
32

exploring-elm-boilerplate

A boilerplate used in an article series exploring Elm
JavaScript
14
star
33

baobab-hot-loader

Using Webpack hot replacement update your application state without reload
JavaScript
13
star
34

predictable-user-experiences-in-react-book

A book about predictable user experiences in React
TypeScript
13
star
35

cerebral-react-baobab

A Cerebral package with React and Baobab
JavaScript
13
star
36

op-op-spec

The op-op specification. A simple straight forward approach to functional programming
13
star
37

flux-react-router

A router in the flux-react family
JavaScript
13
star
38

signalit

Manage state locally and globally in React with signals
TypeScript
13
star
39

observable-state

Observables for creating reactive functional state in applications
JavaScript
11
star
40

cerebral-react-immutable-store

Cerebral package with react and immutable store
JavaScript
10
star
41

R

An application framework using functional reactive concepts and virtual-dom
JavaScript
9
star
42

flux-react-dispatcher

React dispatcher for the FLUX architecture
JavaScript
9
star
43

backbone-draganddrop-delegation

A consistent drag and drop across browsers using native like events
JavaScript
9
star
44

react-environment-interface

Define and consume a custom environment for your application
TypeScript
8
star
45

baobab-angular

A state handling library for Angular, based on Baobab
JavaScript
8
star
46

react-nodesy

React node hierarchy with render props
TypeScript
7
star
47

chrome-recorder

Lets you easily record audio and video in Chrome
JavaScript
7
star
48

cerebral-angular-immutable-store

A cerebral package for angular and immutable-store
JavaScript
7
star
49

reactive-app

Build large scale applications supported by visual tools
TypeScript
5
star
50

virtual-dom-loader

A virtual-hyperscript, used in virtual-dom, to JSX converter for Webpack
JavaScript
5
star
51

HotDiggeDy

Test project for using observables to build complex apps
JavaScript
5
star
52

typed-client-router

TypeScript
4
star
53

batchcalls

Batches calls to a function and passes arguments as arrays
JavaScript
4
star
54

cerebral-boilerplate

A webpack boilerplate for cerebral applications
JavaScript
4
star
55

class-states

Manage async complexity with states
TypeScript
4
star
56

cerebral-router-demo

The demo code for the cerebral router introduction
JavaScript
4
star
57

actorial

Actor pattern with state machines
TypeScript
4
star
58

flux-react-store

A simple construct for your flux stores
JavaScript
4
star
59

create-gql-api

Simplify GQL typing and consumption
JavaScript
4
star
60

christianalfoni.com

My personal website
TypeScript
4
star
61

redux-proxy-thunk

TypeScript
3
star
62

TeachKidsCode

An open source service for learning code
JavaScript
3
star
63

formsy-angular

A form input builder and validator for Angular JS
JavaScript
3
star
64

the-angular-experiment

An experiment bringing components and immutable state to Angular
JavaScript
3
star
65

grunt-tdd

Run browser and Node JS tests on Buster, Mocha or Jasmine and get the reports in the browser
JavaScript
3
star
66

boilproject-service

The service that allows you to create boilerplates
TypeScript
2
star
67

new-git-flow

Created with CodeSandbox
HTML
2
star
68

cerebral-immutable-store

Immutable Store Model layer for Cerebral
JavaScript
2
star
69

family-scrum-v2

TypeScript
2
star
70

webpack-express-isomorphic-react-boilerplate

An isomorphic boilerplate with react, babel, routing and express on the server
2
star
71

middleend-boilerplate

A boilerplate for building applications with a middleend
2
star
72

markdown-excalidraw

TypeScript
2
star
73

codesandbox-weekplanner

Created with CodeSandbox
TypeScript
2
star
74

lib-boilerplate

A boilerplate for creating any JavaScript frontend, or frontend+backend, library
JavaScript
2
star
75

models-test-2

Created with CodeSandbox
JavaScript
1
star
76

grunt-buster-tdd

A browser and Node TDD tool using Buster JS
JavaScript
1
star
77

devchat

TypeScript
1
star
78

C

An inheritance experiment which allows for private variables and "super" up the constructor chain
JavaScript
1
star
79

boilproject-cli

An NPX tool to easily start a new project
JavaScript
1
star
80

Protos

Inheritance the native JavaScript way
JavaScript
1
star
81

our-project-design-system

Created with CodeSandbox
JavaScript
1
star
82

hm

1
star
83

create-css-theme

Converts your theme into css variables for easy consumption with CSS approach
1
star
84

contribute-vscode-plugin

Contribute to github for beginners
TypeScript
1
star
85

module-loader-tdd

An easy to use and easy to test module loader
JavaScript
1
star
86

validate

A general JavaScript validation tool with pre-configuration, Backbone support and can be used cross client/server
1
star