Skip to content

Commit

Permalink
Invoke initial callback without $evalAsync
Browse files Browse the repository at this point in the history
Fixes bug where state could be undefined while angular was running its
initial digest cycle initializing controllers.
  • Loading branch information
jrust committed Feb 27, 2017
1 parent 12ea75d commit c18de65
Show file tree
Hide file tree
Showing 6 changed files with 23 additions and 30 deletions.
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "flux-angular",
"version": "3.1.1",
"version": "3.1.2",
"main": "release/flux-angular.js",
"ignore": [
"**/.*",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "flux-angular",
"version": "3.1.1",
"version": "3.1.2",
"description": "A FLUX architecture for Angular JS",
"main": "./src/flux-angular.js",
"scripts": {
Expand Down
23 changes: 12 additions & 11 deletions release/flux-angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -4809,7 +4809,8 @@ angular.module('flux', []).provider('flux', function FluxProvider() {
$rootScope.constructor.prototype.$listenTo = function (storeExport, mapping, callback) {
var _this = this;

var cursor = void 0;
var cursor = void 0,
originalCallback = void 0;
var store = flux.getStore(storeExport);

if (!store.__tree) {
Expand All @@ -4823,21 +4824,21 @@ angular.module('flux', []).provider('flux', function FluxProvider() {
cursor = store.__tree.select(mapping);
}

originalCallback = callback;
if (useEvalAsync) {
(function () {
var originalCallback = callback;
callback = function callback(e) {
_this.$evalAsync(function () {
return originalCallback(e);
});
};
})();
callback = function callback(e) {
_this.$evalAsync(function () {
return originalCallback(e);
});
};
}

cursor.on('update', callback);

// Call the callback so that state gets the initial sync with the view-model variables
callback({});
// Call the callback so that state gets the initial sync with the view-model variables. evalAsync is specifically
// not used here because state should be available to angular as it is initializing. Otherwise state can be
// undefined while the first digest cycle is running.
originalCallback({});

// Remove the listeners on the store when scope is destroyed (GC)
this.$on('$destroy', function () {
Expand Down
2 changes: 1 addition & 1 deletion release/flux-angular.min.js

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions src/flux-angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ angular.module('flux', [])

// Extend scopes with $listenTo
$rootScope.constructor.prototype.$listenTo = function (storeExport, mapping, callback) {
let cursor;
let cursor, originalCallback;
const store = flux.getStore(storeExport);

if (!store.__tree) {
Expand All @@ -199,17 +199,19 @@ angular.module('flux', [])
cursor = store.__tree.select(mapping);
}

originalCallback = callback;
if (useEvalAsync) {
const originalCallback = callback;
callback = (e) => {
this.$evalAsync(() => originalCallback(e));
};
}

cursor.on('update', callback);

// Call the callback so that state gets the initial sync with the view-model variables
callback({});
// Call the callback so that state gets the initial sync with the view-model variables. evalAsync is specifically
// not used here because state should be available to angular as it is initializing. Otherwise state can be
// undefined while the first digest cycle is running.
originalCallback({});

// Remove the listeners on the store when scope is destroyed (GC)
this.$on('$destroy', () => cursor.off('update', callback));
Expand Down
14 changes: 2 additions & 12 deletions tests/flux-angular-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,21 +153,16 @@ describe('FLUX-ANGULAR', function() {
expect($scope.$listenTo).toBeDefined();
});

it('should call $evalAsync and then the callback when $listenTo is first attached so that the view-model is initialized', function() {
it('should invoke the callback immediately upon setting up the store so that state is available to angular as it is initializing', function() {
$scope.$listenTo(MyStore, cb);
expect($scope.$evalAsync.calls.count()).toEqual(1);
expect(cb.calls.count()).toEqual(0);
$browser.defer.flush();

expect($scope.$evalAsync.calls.count()).toEqual(0);
expect(cb.calls.count()).toEqual(1);
expect(cb.calls.argsFor(0)[0]).toEqual({});
});

it('should call $evalAsync and the callback when state is changed on any part of the tree', function() {
$scope.$listenTo(MyStore, cb);
$browser.defer.flush();
cb.calls.reset();
$scope.$evalAsync.calls.reset();

flux.dispatch('addItem', { item: 'foo' });
expect($scope.$evalAsync.calls.count()).toEqual(1);
Expand All @@ -184,9 +179,7 @@ describe('FLUX-ANGULAR', function() {

it('should call the callback if a specific cursor is listened to and changed', function() {
$scope.$listenTo(MyStore, ['items'], cb);
$browser.defer.flush();
cb.calls.reset();
$scope.$evalAsync.calls.reset();

flux.dispatch('addItem', { item: 'foo' });
expect($scope.$evalAsync.calls.count()).toEqual(1);
Expand All @@ -201,9 +194,7 @@ describe('FLUX-ANGULAR', function() {

it('should remove the listener when the scope is destroyed', function() {
$scope.$listenTo(MyStore, ['items'], cb);
$browser.defer.flush();
cb.calls.reset();
$scope.$evalAsync.calls.reset();

// need to keep a ref to evalAsync since it is removed when the scope is destroyed
const evalAsync = $scope.$evalAsync;
Expand All @@ -223,7 +214,6 @@ describe('FLUX-ANGULAR', function() {
$scope.$listenTo(MyStore, function() {
cb('MyStore');
});
$browser.defer.flush();
cb.calls.reset();

flux.dispatch('addItem', { item: 'test' });
Expand Down

0 comments on commit c18de65

Please sign in to comment.