Git Product home page Git Product logo

flux-angular's Introduction

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.

flux-angular's People

Contributors

afiedler avatar christianalfoni avatar codyray avatar gitter-badger avatar jrust avatar mickeahlinder avatar nherzing avatar sheerun avatar urik avatar wescravens avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flux-angular's Issues

Angular 1.2.x support

Would you be interested in supporting also Angular 1.2.x? bower.json says only 1.3.0

I have project that uses it and I'd like to use flux-angular in it.

Release files namig

I use bower for manage my deps.
And when i want to update some library, i just user bower update flux-angular.
But when you use versions in release files, i need to change files manually.
It make me sad :'(

Other popular bower libs dont use versions in their release files (jquery, angular, angula-mock, restangular, lodash, etc.).

Its not big deal, but expecting behavior for releases.

Thanks!

Async dispatch race condition

Hey there!

(Hopefully) quick question relating to the nature of action dispatches and asynchronous event handling. In my effort to support user initiated browser refreshes I've settled upon a convention where controllers are responsible for populating their injected stores by calling an async service to fire off dispatches with successful responses.

.controller('example', function ($scope, myService, myStore) {

    myService.getThing();
    myService.getAnotherThing();

    $scope.$listenTo(myStore, function () {
        ...
    });

});

where myService is defined like so:

.factory('myService', function ($http, flux) {
    return {
        getThing: function () {
            $http.get('/api/call').then(
                // success
                function (response) {
                    flux.dispatch('someActionA', response.data);
                },
                // error
                function (response) {
                    // custom error handling
                }
            );
        },

        // defined similarly to getThing() above
        getAnotherThing: function () { ... }
    };
});

With this convention and some work with localStorage, I'm able to support full page/view/data reloads within the single-page app environment.

But, I think I've hit a wall with a race condition. It seems like the $http promise from the second async call in the controller is resolving while the dispatch from the first is executing (or vice versa), which results in: "cannot execute 'someActionB' while 'someActionA' is executing"

So with that, am I handling the async data responses in a reasonable way? I figured one option was to wrap dispatches into a queue, but wanted to get your thoughts before going down that route. Thanks!

$listenTo never updates the scope

Hi there

Iยดve been testing out flux-angular for a few days on a project.

I actually thought everything worked perfectly, until i tested it on my pc at home and nothing worked. I was wondering what cause this.

Iยดve been using angular batarang ( stable ) browser extension in chrome. And if i turn this OFF, nothing updates within my $listenTo blocks in my directive.

my store holds an array like this:

days: [
    calendars: [
          0: { },
          1: { },
          ...
     ],
     ...
]

I then have a directive that goes through all days in an ng-repeat, then all calendars, also in an ng-repeat.

My problem is, that when i bind the scope variable to the store, it gets the data fine the first time.

But this code below never does anything:

            $scope.$listenTo(calendarStore, function() {
                $timeout(function() {
                    $scope.days = calendarStore.days;
                }, 0);
            });

If i put in a console.log after setting the scope variable, it does show an updated scope with the new values. BUT, nothing in the UI ever changes.

The only way it works, is when batarang is active, which i find incredibly odd.
I have no idea why, as iยดm pretty new with all this.

ps. this happens with both immutable stores and without.

Question: cloning of stores - performance

I'm just exploring flux and flux-angular for the first time and haven't looked to deep into the source code, but when reading this: "When emitChange is run all values on state will be cloned to bound scopes."
I wondered if I should worry about performance when the state has large objects/arrays and a lot of bound scopes?

flux-angular IE8 problems

Now, I understand that IE8 will be getting dropped relatively soon (January 2016), but since we're still not there, yet, is there any chance that we could change those Object.defineProperty calls in the code? (they're not suported in IE8) Otherwise would mean that this plugin doesn't fully support Angular 1.2.X's capabilities (as in being IE8 compliant)

Objects in store copied to arrays in controllers

Okay, after our discussion on your blog, I'm already back with a question :p. It seemed more appropriate to put it here.

Some useful array functions (eg. indexOf) can't be used because of the deep cloning, because they use strict equality comparison. For example, if I want to update an item in a store, I need a way to find the original item. I could give each item an identifier, but in that case it makes more sense to use an object instead of an array in the store, so I don't need to iterate over the array each time. So I end up with something like:

todos : {
  identifier1 : {
    id: identifier1,
    title: 'test todo',
    completed: false
  }
}

However, in my controllers and views I often want to use filters, get the length of the scope variable etc, so there I'd rather work with arrays. Since the data is cloned anyway, it would maybe be nice if there'd be an option to copy from object to array or vice versa.

Object.prototype got lost.

When I try to save an $resource object into store e.g.

$resource('/user/:id').get({id :123}).get().then(function(user){
    flux.dispatch('saveUser', {user : user});
});

then inside my store we have the correct $resource object.
When calling getUser() from store, the copy of the AppStore.user is no $resource anymore it becomes an object.

How far can we go with deepcopy in such a situation?
Is it possible to return the object without losing it's prototype?

Does immutable reduce rendering?

I'm trying to figure out the benefits of using immutability store vs deep cloning. My initial thought was that it would reduce the number of times Angular had to render the DOM in an ng-repeat (if used in conjunction with track by). However, so far my testing isn't bearing this out -- the ng-repeat seems to be kicked off every time a change causes the listenTo callback to be fired and re-sync the data (and along similar lines, the $scope.$watch on the same property is also invoked the same number of times). I'm wondering if this is because of the fact that even with immutability I still have to call toJS() on the data before handing it over to Angular. Will toJS() end up giving a new object reference every time?

Aside from the ng-repeat, are there other pros and cons of immutability mode? I'm happy to try and take anything learned here and add it to the README

Proposal / Question: Make store state attributes (literally) immutable

First off, thanks for the work on flux-angular. I was able to port our project over to it quite quickly and working with the flux model has been awesome.

Since immutability is desired for data feeding into controllers, have you looked into an option like immutable.js? On top of the potential performance gains for large datasets (i.e. not having to clone on get()), one immediate benefit I can see is a reduction in store getter methods. For instance,

flux.createStore('myStore', {
    a: ...,
    b: ...,
    c: ...,

    (... action handlers, etc)

    exports: {
        getA: function () {...},
        getB: ...
        getC: ...
    }
});

could be reduced to:

flux.createStore('myStore', {
    state: {
        a: ...,
        b: ...,
        c: ...,
    },

   (... action handlers, etc)
});

Then, instead of myStore.getA(), we just do myStore.a and get the immutable ref back. I suppose exports would still be needed for anything more complex than a simple getter method.

Anyway, just a thought.

Any thoughts on integration with Angular 1.4 "new" Router?

Seems like the new router observes the "component" structure as well, so the $scope doesn't seem to be intended to be accessible. In short, I can't find a way to make "$listenTo" accessible in a controller.

New Router:
https://github.com/angular/router

My Code:

VoteController.$inject = ['widgetStore','flux'];
function VoteController (widgetStore, flux)
{

    //
    // Internal
    // 
    var _self = this;

    // Defaults
    this.votes = widgetStore.getVotes();

    // Expose Functions
    this.submitVote = submitVote;


    //
    // Events
    // 

    this.$listenTo(widgetStore, 'widget.vote', onVote ); <--- errors out here 


    //
    // Functions
    // 

    function submitVote ()
    {
        console.log( 'submitVote' );

        flux.dispatch('submitVote', {});
    }


    function onVote ()
    {
        console.log( 'onVote' );
    }
}

documentation: "mixin" instead of "mixins"

Hey, i noticed that you're using "mixin" in your example instead of "mixins"

return flux.store({
        mixin: [MyMixin],
        todos: [],

It took me a minute to figure out why the mixins weren't working. You need to change that to "mixins".

RequireJS shim support

Got problem when trying to shim module to requirejs, it always returns empty plain object instead of module name

Dispatch from an emit handler causing another dispatch in-progress error

I have been facing dispatch in-progress issue when action handler executes a dispatch synchronously when being invoked from a listener triggered by another dispatch. I understand why this is happening but is it not possible to run the emit handlers asynchronously within a dispatch? I could handle it by making the dispatch asynchronous using $q/$timeout etc., but it does not sound right. Am i missing something obvious in the design or is this the expected behavior and workaround? Appreciate any help on this.

I have put together a simple example in a plunker. The example is a simple one where 2 directives one listing some users and another displaying their details. Both of them uses same action creator and store. In order for quick look up below is the piece of code to replicate the error.

    this.getUser = function (id) {
        ///Need to run this asynchronously, since this is run as a part of existing dispatch's emitted handlers
      $q.when().then(function () {
          _getUser(id);
      } );

      //comment out the above and uncomment below and see the error.
      //_getUser(id);
   }

Don't do `deepClone()` on listed types of objects

Hi,
I'm working on project with Three.js & flux-angular. I'm storing instances of Three.js classes in the flux stores. When I'm getting() instance back from stores, I'm getting cloned objects. That crashes my Three.js code. I need to get original instances from stores.

I looked on source of safeDeepClone function. There is an exception for example for Date objects. I'm thinking in implementing angular provider for flux-angular where I can list types of objects that would be returned directly.

Are you open to this idea, or have any feedback on that?

Emit custom event from each store

We could emit specific events on each store, instead of the standard this.emitChange(). This might be useful if we have multiple things updating at once, but are interested only in a specific change. What do you think?

Unable to load the node module

Hi,

I am trying to consume your module in a project and I have following statement in my app.js

var flux = require('flux-angular');

When I run my watchify command: watchify starting_file.js -o dist/bundle.js -v -d .

I get following error:

Error: Cannot find module 'flux-angular'

When I run the browserify command: browserify starting_file.js -o dist/bundle.js -d

I get below error:

Error: Cannot find module 'flux-angular' from '/Users/user/Documents/CF/CF.Common/Client.Components/app'
at /Users/user/Documents/CF/CF.Common/Client.Components/node_modules/browserify/node_modules/browser-resolve/node_modules/resolve/lib/async.js:50:17
at process (/Users/user/Documents/CF/CF.Common/Client.Components/node_modules/browserify/node_modules/browser-resolve/node_modules/resolve/lib/async.js:119:43)
at /Users/user/Documents/CF/CF.Common/Client.Components/node_modules/browserify/node_modules/browser-resolve/node_modules/resolve/lib/async.js:128:21
at load (/Users/user/Documents/CF/CF.Common/Client.Components/node_modules/browserify/node_modules/browser-resolve/node_modules/resolve/lib/async.js:60:43)
at /Users/user/Documents/CF/CF.Common/Client.Components/node_modules/browserify/node_modules/browser-resolve/node_modules/resolve/lib/async.js:66:22
at /Users/user/Documents/CF/CF.Common/Client.Components/node_modules/browserify/node_modules/browser-resolve/node_modules/resolve/lib/async.js:21:47
at Object.oncomplete (fs.js:108:15)

npm ERR! Darwin 13.4.0
npm ERR! argv "node" "/Users/user/local/bin/npm" "start"
npm ERR! node v0.10.35
npm ERR! npm v2.4.1
npm ERR! code ELIFECYCLE
npm ERR! [email protected] start: browserify start_file.js -o dist/bundle.js -d
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] start script 'browserify start_file.js -o dist/bundle.js -d'.
npm ERR! This is most likely a problem with the CF.Common package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR! browserify start_file.js -o dist/bundle.js -d
npm ERR! You can get their info via:
npm ERR! npm owner ls CF.Common
npm ERR! There is likely additional logging output above.

npm ERR! Please include the following file with any support request:

I am trying to consume the Version 2 of your library.

can some one please help.

karma tests fail with PhantomJS backend

I started to integrate flux-angular into our project today and have hit a wall with karma. For some reason, PhantomJS isn't a fan of flux-angular. Any thoughts as to why? We've been using Travis CI to build and test our PR commits so it'd be nice to keep PhantomJS around.

Test runner output: (from flux-angular root, not our project)

$ karma start --single-run --browsers PhantomJS
INFO [karma]: Karma v0.12.31 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.8 (Mac OS X)]: Connected on socket 7F9CnaKZXhVbKx0NhLVA with id 77953079
PhantomJS 1.9.8 (Mac OS X) FLUX-ANGULAR Using the store method should expose the exports object of the store FAILED
    TypeError: 'undefined' is not a function (evaluating 'this.getStore.bind(this)')
... (continues for each flux-angular test)

$action and handlers

Into stores handlers, it would be great to be able to directly access the actions key constant instead of retype the value.

For example :

var store = {
handlers: function()
var handlers = {};
handlers[actions.LOAD_CURRENT_USER] = 'onLoadCurrentUser';
return handlers;
},
};

To make it work I changed your code line 42 :

Store.handlers = ((true === _.isFunction(spec.handlers)) ? spec.handlers() : spec.handlers);

Maybe you have a better way ?

Better testability for flux-angular

Following scenario:

We have a big app with a modular sourcecode organisation.
Each submodule is responsible for a definitive concern.
These modules keep track of there state within there own stores.

E.g.

  • myApp
    • login -> LoginStore
    • registration -> RegistrationStore

When it comes to unit testing, I want to test my units isolated from other units.
Now the problem when trying to test my LoginStore isolated with following code:

describe('LoginStore', function () {
    var loginStore;
    beforeEach(module('myApp.login'));
    beforeEach(inject(function (LoginStore) {
        loginStore = LoginStore;
    }));

    it('should be defined', function () {
        expect(loginStore).toBeDefined();
      }
    );
  });

...Is that the RegistrationStore (and all other stores) must also be available, because all js files are loaded into test suit and the angular.store method is invoked (I guess) on bootstrap. This method saves the stores in some contexts and later when injecting the LoginStore, the RegistrationModule is not available and therefore the RegistrationStore is not found, what leads to an UnknownProvider error:
Error: [$injector:unpr] Unknown provider: RegistrationStoreProvider <- RegistrationStore.

I could fix my unit test by loading the module for registration before each test, but this leads to a chain of more dependencies, that I actually don't want to test and configure in this scenario.

Can you maybe change this behavior and only register the stores when they are actually created?
I think it's something about the preinjection and the storeNames array.

moduleInstance.store = function (storeName, storeDefinition) { // Line 82 flux.angular-2.0.1
  ...
  this.factory(storeName, ['$injector', 'flux', function ($injector, flux) {
    ... invoked later ...
  }; 

  // Add store names for pre-injection 
  storeNames.push(storeName);
  return this;
};

Thank you for diving deeper! I will also try to solve it.

Support max listener configuration

Hi,

When a particular store change is being listened by more than 10 different places(EventEmitter default limit -defaultMaxListeners- is 10), i get the following error from the EventEmitter.

(node) warning: possible EventEmitter memory leak detected. 3 listeners added. Use emitter.setMaxListeners() to increase limit.

Which traces down to this comment in the source:

// By default EventEmitters will print a warning if more than
// 10 listeners are added to it. This is a useful default which
// helps finding memory leaks.
//
// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.

and the configuration is a pretty useful use case i believe.Is there a better way to configure this (other than this.setMaxListeners(n) within the store initialize method)? Probably exposing a provider (FluxProvider) function to specify the listener limit for required stores as a dictionary or specifying the settings in the store itself, example:

.store('userStore', function(){
  var _user = "";
  return{
     maxListeners:20, //Max listeners here or within a generic config key in order to be able to specify any other configuration
     userChanged:function(user){
         _user = user;
         this.emitChange();
      },
    exports:{
      getData:function(){
        return _user;
      }
    },
    handlers:{
      'userChanged':'userChanged'
    }
  }
});

and just to fix it in the code pen, i made this change just to showcase in the demo.

// Call the constructor of EventEmitter2
 EventEmitter2.call(this, {
    wildcard: true,
   maxListeners: spec.maxListeners
});

Appreciate any help on this.

$listenTo multiple stores

Does it will make sense to have a way to listen to an array of Stores, and only provide one unique callback. I sometime see things like:

$scope.$listenTo(StoreA, function () {
    set();
});
$scope.$listenTo(StoreB, function () {
    set();
});
$scope.$listenTo(StoreC, function () {
    set();
});

could be replaced by :

$scope.$listenTo([StoreA, StoreB, StoreC], function () {
    set();
});

I can implement this and do a pull request. Just wanted to make sure it's something you guys want as a design =). Let me know!

safedeepcopy issue when object has length property

Hi

If you return the following variable from an export function in a store, you will see that you get lots of undefined in vars property.

var obj = {
    a: {
      x: 1,
      vars: [
        {b:2, length:30}, 
        {c:3, length:20}]
    }
  };

Problem is because of the length property in the vars array objects.

Here is the related line for this problem in safeDeepCopy

// Handle Array - or array-like items
if (obj instanceof Array || obj.length) {

I changed it to this to fix my current problem

if (angular.isArray(obj)) { //obj instanceof Array || obj.length) {

A fix would be greatly appreciated!

Nihat

Bump immutable-store

Could you release a version with the latest immutable-store? The ability to set primitives will allow me to give it a much more more thorough run. Thanks.

Listened event on scope is not being called

Hi!
I am using version 2.2.0 of flux-angular.
I am triggering an event like like this from my store named tc3Store

this.emitChange('offline')

And when I try to listen to changes like this, it won't trigger

$scope.$listenTo(tc3Store, 'offline', function(){
    console.log('gone offline'); // won't show anything 
});

However, if I listen to it like this (a generic listen), it triggers

 $scope.$listenTo(tc3Store, function(){
     console.log('tc3store change');
 });

What am I doing wrong?

EventHandler runs 2 times?

Hi Christian,

maybe you should mention in your readme.me that scope listeners are always called one time initially.

I don't know if this behavior is good.
My feeling for listeners is, that they should only be called when there events are emitted...

When not using the clean flux way this lead to unforeseen behavior.
If a listener changed values that are not reflected inside the store. The change will happen even if the event was not triggered.

In my case it took a while to find the reason, why my user was always logged out... :).
My advise would be to invoke the listeners only on corresponding events.

Have fun!

Wolfram

Minifing with ngAnnotate

When using ngAnnotate before minifing your code, stores are not detected by ngAnnotate.
Therefore, when defining a store, we can use the ngInject annotation:

angular.module('xy').store(createStore);

/*@ngInject*/
function createStore(dep1,dep2,...){...}

flux is not dispatching if called from resolve on ui-router on the FIRST page load

Hi,

I am using ui-router with flux angular for a single page app. I have a little problem with flux not dispatching on page load.

Imagine I have the following $state config for my ui-router

.state('account.home', {
    url: '/home',
    controller: 'HomeCtrl',
    templateUrl: '...',
    resolve: {
       preloaded: function(flux, constants){
           // being super simple here just to focus on the problem 
           flux.dispatch(constants.actions.GROUP_POPULATE);
           return 1;
        )
    }
...

That dispatch won't fire when the page is reloaded/refreshed to .com/account/home (I have a console.log in my .store service to test). However, after page is loaded, if i navigate to some other url on the single page app like .com/account/profile and then go back to .com/account/home with a click (state change), it will fire. It just won't fire on initial load.

The order of my scripts are like this:

angular.js
flux.angular.js
ui-router.js
<all of my controllers/directives/services etc>
myMainModule.run.js

Any suggestions?

How to test stores?

Thanks first for your great work. I apprechiate that we now can use angular.store and flux.dispatcher.

But do you have an idea how to unittest your stores?
I ran into problems, when I try execute my karma:jasmine test using flux-angular_2.0.0. stores.

My test:

describe('MyStore', function () {
    beforeEach(module('myApp.myStore'));

    it('should be defined', inject(
        function (MyStore) {
          expect(MyStore).toBeDefined();
        }
      )
    );
  });

resulting error:
MyStore should be defined FAILED
Error: [$injector:unpr] Unknown provider: AnotherStoreProvider <- AnotherStore
...

I tried to test MyStore. But I got an error about AnotherStore. Looking at your code it seems like all stores in the system must be available.

Export "flux" module name in commonJS module.

Most angular modules that support CommonJS will export the angular module name so that it can be directly required into the angular dependency array.

Example:

angular.module('myModule', [
  require('flux-angular')  // exports "flux"
]);

vs how it currently works

require('flux-angular'); // exports Object {}
angular.module('myModule', [
  'flux'
]);

Request: actions helper

First off, I'm really enjoying working with your library. You've put this together really well.

I wanted to throw an idea at you that i saw in another flux library called "reflux" (https://github.com/spoike/refluxjs). He's got a couple really nice helpers when creating the stores. I like how he has a "listenToMany" or just a "listenables: actions" in there that automatically binds actions to functions. It makes the library a little easier to use, and i'm finding that without it it feels like i'm really having to wire up a lot of functions with the same name.

Just a thought from someone who's used a few of these flux frameworks... i understand reflux may have a different opinion on this stuff than your library.

global is not defined

When i do a clean bower install of flux-angular, I get a console error that "global is not defined". I believe it references this line:

var angular = global.angular || require('angular');

I don't understand what i would be doing wrong, or how to resolve this.

Implement component into v2

Hi,

First, thanks for the great job, this library is stunning.
I still have things to figure out but to have the flux pattern available in angular, is awesome.

1- In your first version of the library, you gave two examples, one using a normal angular controller and a second one using component (a preconfigured directive).
I'm wondering if you can implement back component into version 2 (Users can choose to use controller or component).
So I don't need to implement it back in every new version by myself.

2- Do you have a code example using this library more advanced than a todo list, with multiples models and relations.

3- Did you try to implement React components with angular-flux ?

$listenTo not triggering $digest when using "controller as" and "this"

I was curious if i'm doing something wrong here. I'm listenting to my store from my controller and using that controller on my directive. I'm using the "bindToController" which wants you to add your data to "this" in your controller instead of $scope. My problem is, when my store emits a change, it is not getting updated from $scope.$listenTo unless i run a $timeout. Here is my setup:

.directive('bigList', function(){
return {
controller:"ListCtrl",
controllerAs:"ctrl",
bindToController: true,
...etc...

.controller('ListCtrl', function($scope, ListStore, $timeout){
this.state = [];
$scope.$listenTo(ListStore, function () {
this.state = ListStore.getState();
$timeout(function(){},0);
}.bind(this));

if I don't add the $timeout, it won't get picked up by the apply cycle in some cases. if i add $scope.$digest(), it will tell me that the digest cycle is already running.

I'm wondering if this is because i'm using "this" instead of $scope for my controller data. Or if the $listenTo helper isn't triggering a digest cycle in some cases.

This does update properly in some instances. For example, in my init function i'm making an $http call and emiting the change in the success callback. i know $http triggers the digest. But if i use an action that changes my data model and emits a change, the $listenTo won't see it.

any thoughts would be super helpful!

Breaks the app when store is registered but respective module is not in the dependency list anywhere

Hi @christianalfoni ,

It seems like there is bug due to injector invocation on preinjectionList. When we register a store they get registered and added to the preInjection list. But if we do not list the respective module that contains this store as depdendency anywhere at all, it throws unknown provider error due to the fact that $injector.invoke on the preinjection list contains this store too but is not available in the module cache. I believe it should not cause an error, i would be interested to know why is there an explicit injector invocation (even if i remove it, still does work).

Steps to replicate:

Create a store:

function UserStore(){
  var _user = "";
  return{
     userChanged:function(user){
         _user = user;
         this.emitChange();
      },
    exports:{
      getData:function(){
        return _user;
      }
    },
    handlers:{
      'userChanged':'userChanged'
    }
  }
};

Register it under a module:

angular.module('myStores',[]).store('userStore',UserStore);

Create your app

 angular.module('app', ['flux'])
   .controller('controller', function(flux){
   this.name="Hey";
});

now this will throw error,

Uncaught Error: [$injector:unpr] Unknown provider: userStoreProvider <- userStore

to fix it you have to list myStores as dependency in app even though you don't use it (yet) i.e

 angular.module('app', ['flux','myStore'])...

Demo

This situation can happen more often during development on a large code base and different developers working on it. The error is misleading too because first thing every one will check is "where have i injected userStore without injecting the module". Can this be handled? Thank you!!

Cannot call dispatch while another dispatch is executing

Hi,

I'm facing a lots of dispatch overlap issues.
My basic scenario is :
When the the project is initialized,
1- it loads the current user (if the client has a token) (it dispatches LOAD_USER, LOAD_USER_SUCCESS, LOAD_USER_ERROR)
2- The user store listens to LOAD_USER_SUCCESS, sets the user and emits a users.load_user.success
3- My App component listens to users.load_user.success and calls the action to initialize the router.
4- The router dispatches CHANGE_ROUTE, CHANGE_ROUTE_SUCCESS and CHANGE_ROUTE_ERROR

The problem is the dispatcher did not finish to dispatch LOAD_USER_SUCCESS and triggers CHANGE_ROUTE.

You never really talked about implementing a queue of dispatch, is there a reason for that ?

ps. I saw someone proposed to implement the possibility to $listenTo multiple events, what is the status ?

$listenTo a store in a nested directive that have an isolate scope

Giving an example where MyStore is immutable:

    app.directive('myDirective', function() {
        return {
            replace: true,
            transclude: true,
            scope: {
                config: '=',
            },
            controller: function($scope, MyStore) {

                $scope.$listenTo(MyStore, function() {
                    console.log($scope.config.value);
                });
            },
            template: '{{config.value}}'
        }
    });

When the * event occurs, the $scope.config which is a descendant object of MyStore, part of ng-repeat loop, still references to an object of previous state in $listenTo callback.

Yet the template does have access to the updated config.value

That forced me to provide the index to the isolate scope and access MyStore in the $listenTo callback, thus the myDirective now have to know the full.path.to.the.config object :(

Oh my, just got an EventEmitter memory leak detected. error. Replaced listenTo with $broadcast / $on

Is there a better solution to work around that issue?

$scope.$watch can't be used for obvious reasons

Calling an action in emitChange() handler.

Hello this is a quick question.

How does one go about calling an action in a emitChange() handler without getting the "Error: Cannot call dispatch while another dispatch is executing. Attempted to execute 'some-action' but 'some-other-action' is already executing." error.

Example:

        $scope.$listenTo(pageStore, function() {
            var previousPage = currentPage;
            currentPage = pageStore.getPage();

            switch (currentPage.status) {
                case 'trigger':
                    console.log('Rootscope -> Triggering page change!');
                    pageActions.change.attempt(currentPage.info.name');
                    break;
                case 'transitioning':
                    console.log('Rootscope -> Page change initiated!');
                    break;
                case 'accepted':
                    console.log('Rootscope -> Page change accepted!');
                    $rootScope.pageTitle = currentPage.info.title;
                    $rootScope.pageDescription = currentPage.info.description;
                    break;
                case 'rejected':
                    console.log('Rootscope -> Page change rejected!');
                    $state.go('login');
                    break;
                default:
                    console.log('Rootscope -> Unhandled pageStore event!', page);
            }
        });

Is this case, I would expect the action to be queued after the current dispatch instead of throwing. But my guess is that I don't quite understand how FLUX dispatch works.

Of course, one could always user a $timeout, but this is a pretty bad hack and I'm sure there must be a way to handle this.

Your help would be very much appreciated! And GREAT work on the library, we are very much appreciative of your efforts!

Handle store relations with async calls.

Hi

I would like to have your point of view about how to use the flux pattern and handle store relations with async calls.

Example :

this.$listenTo(AuthStore, 'auth.loadCurrentUserSuccess', function () {
UserCompaniesAction.loadCompanies();
});

In this component code we are waiting the app to load the current user (Async) to be able to load the user companies.
They are both triggering a dispatch change.

So I get the error :
Error: Cannot call dispatch while another dispatch is executing. Attempted to execute 'load_companies_user' but 'load_current_user_success' is already executing.

So the previous dispatch did not finish when the app dispatch load_companies_user.

I don't want to use a setTimeout because it's ugly
So how would you refactor this code to respect the flux pattern ?

Suppress webpack `Critical dependencies` warning message

Requiring flux-angular with webpack introduces a warning message in console

Critical dependencies:
1:435-442 This seems to be a pre-built javascript file. Though this is possible, it's not recommended. Try to require the original source to get better results.

Best way to use mixins

Christian, thanks for creating this project and your accompanying blog entries, very cool stuff. In the README file you have the following tip:

You do not want to divide your stores within one section of your application as they very quickly become dependant on each other. That can result in circular dependency problems. Use mixins instead and create big stores. mixins, actions and handlers will be merged with the main store.

Can you expand on this idea? Do you suggest having just one big store (ie AppStore) and a lot of mixins?

this.exports error: cannot read property of undefined.

Hi

Imagine a store named dataStore defined like this:

return {
data:[],
exports: {
   func1: function(){
      return this.data;
   },
   func2: function(){
      return this.exports.func1();
   },

   func3: function(){
      return this.exports.func2();
   }
}
}

If I do dataStore.func3(), I would get an error in func2 that says
cannot read property func1 of undefined.

I assume this bindings got lost somewhere. Any fixes?

UPDATE:
Right now, I am bypassing the problem by doing this.exports.func2.call(this) in func3 and this.exports.func1.call(this) in func2.

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.