Reacting to changes in AngularJS
by Michael Hladky
3 Parts
Watchers
Events
Channels
Code sections
Service
View
Controller
.service('valueService', function() {
var val = 'value';
var getValue = function() {
return val;
};
var setValue = function(newValue) {
return val = newValue;
};
return {
getValue: getValue,
setValue: setValue
};
});
Service
<div ng-controller="parentCtrl">
<div ng-controller="child1Ctrl">
{{data.val}}
<input type="text" ng-model="data.val">
<button ng-click="setValue(data.val)">Set</button>
</div>
<div ng-controller="child2Ctrl">
{{data.val}}
<input type="text" ng-model="data.val">
<button ng-click="setValue(data.val)">Set</button>
</div>
//...
</div>
View
.controller('parentCtrl',function() { })
.controller('child1Ctrl',function($scope, valueService) {
$scope.val = valueService.getValue();
$scope.setValue = function(newVlaue) {
valueService.setValue(newVlaue);
};
})
.controller('child2Ctrl',function($scope, valueService) {
$scope.val = valueService.getValue();
$scope.setValue = function(newVlaue) {
valueService.setValue(newVlaue);
};
})
//...
Controller
Controller-Tree || Scope-Tree
Rendered
ui-router states => controller tree
Using $watch to react to changes
$watch
watchExpression (string, function)
is called on every digest, returns the watched value
listener (function)
Is called when new and old values are not equal
objectEquality (boolean)
Compare for object equality instead of reference equality
Returns a unregister function
var unregister = $scope.$watch(
'val',
function(newValue, oldValue) {
//some logic
if (newValue !== oldValue) {
//actions
$scope.myVal = newValue;
}
}
);
unregister = $scope.$watch(watchExpression, listener, [objectEquality]);
.controller('parentCtrl',function() { })
.controller('child1Ctrl',function($scope, valueService) {
$scope.val = valueService.getValue();
$scope.setValue = function(newVlaue) {
valueService.setValue(newVlaue);
};
$scope.$watch(
valueService.getValue,
function(newValue, oldValue) {
if (newValue !== oldValue) {
$scope.val = valueService.getValue();
}
});
})
.controller('child2Ctrl', function($scope, valueService) {
//same here
});
Controller
.service('valueService', function() {
var val = 'Value';
var getValue = function() {
return val;
};
var setValue = function(newValue) {
val = newValue;
};
return {
getValue: getValue,
setValue: setValue
};
});
Service
Should we use $watch for this purpose?
Very inefficient
Hard to test
Bad Performance
Using events to react to changes
Methods
$emit (publish)
$broadcast (publish)
$on (subscribe)
$emit
$broadcast
$rootScope.$broadcast
.controller('child1Ctrl',function($scope, valueService) {
$scope.val = 'test';
$scope.setValue = function(newValue) {
valueService.setValue(newVlaue);
}
var onValueChangedHandler = function (value) {
if(value !== $scope.data.val) {
$scope.data.val = valueService.getValue();
}
}
$scope.$on('valueChanged', function(event, args) {
onValueChangedHandler(args.value);
});
})
.controller('child2Ctrl',function(valueService) {
//same here
})
Controller
.service('valueService', function($rootScope) {
var val = 'Value';
var getValue = function() {
return val;
};
var setValue = function(newValue) {
val = newValue;
var args = {value: val};
$rootScope.$broadcast('valueChanged', args);
};
return {
getValue: getValue,
setValue: setValue
};
})
Service
Is it really better now?
Debug on many places in the application
Easy to get unstructured
Event-Callback-Hell
Encapsulating the pub/sub-logic into a channel
.controller('child1Ctrl',
function($scope, valueService) {
$scope.val = 'test';
$scope.setValue = function(newValue) {
valueService.setValue(newVlaue);
}
var onValueChangedHandler = function (value) {
if(value !== $scope.val) {
$scope.data.val = valueService.getValue();
}
}
$scope.$on('valueChanged',
function(event, args) {
onValueChangedHandler(args.value);
}
);
})
Controller
.service('valueService',
function($rootScope) {
var val = 'Value';
var getValue = function() {
return val;
};
var setValue = function(newValue) {
val = newValue;
var args = {value: val};
$rootScope.$broadcast(
'valueChanged',
args
);
};
return {
getValue: getValue,
setValue: setValue
};
})
Service
How to improve this?
Channel
//???
Controller
Service
Constants for the event names
Channel
.controller('child1Ctrl',
function($scope, valueService, myEvents) {
$scope.val = 'test';
$scope.setValue = function(newValue) {
valueService.setValue(newVlaue);
}
var onValueChangedHandler = function (value) {
if(value !== $scope.val) {
$scope.data.val = valueService.getValue();
}
}
$scope.$on(myEvents.valueChanged,
function(event, args) {
onValueChangedHandler(args.value);
}
);
})
.service('valueService',
function($rootScope, myEvents) {
var val = 'Value';
var getValue = function() {
return val;
};
var setValue = function(newValue) {
val = newValue;
var args = {value: val};
$rootScope.$broadcast(
myEvents.valueChanged,
args
);
};
return {
getValue: getValue,
setValue: setValue
};
})
.config('myEvents', function() {
valueChanged : 'valueChanged'
});
Controller
Service
Channel for pub/sub the events
Channel
.controller('child1Ctrl',
function($scope, valueService, myEvents) {
$scope.val = 'test';
$scope.setValue = function(newValue) {
valueService.setValue(newVlaue);
}
var onValueChangedHandler = function (value) {
if(value !== $scope.val) {
$scope.data.val = valueService.getValue();
}
}
$scope.$on(myEvents.valueChanged,
function(event, args) {
onValueChangedHandler(args.value);
}
);
})
.service('valueService',
function($rootScope, myEvents) {
var val = 'Value';
var getValue = function() {
return val;
};
var setValue = function(newValue) {
val = newValue;
var args = {value: val};
$rootScope.$broadcast(
myEvents.valueChanged,
args
);
};
return {
getValue: getValue,
setValue: setValue
};
})
.config('myEvents', function() {
valueChanged : 'valueChanged'
});
.config('myEvents', function() {
valueChanged : 'valueChanged'
});
.service('myChannel',
function($rootScope, myEvents) {
// Publish to value changed event
var pubValueChanged = function (newVlaue) {};
// Subscribe to value changed event
var subValueChanged = function($scope, handler) {};
return {
publishValueChanged : publishValueChanged,
onValueChanged : onValueChanged
};
});
Controller
Service
Moving pub logic
Channel
.controller('child1Ctrl',
function($scope, valueService, myEvents) {
$scope.val = 'test';
$scope.setValue = function(newValue) {
valueService.setValue(newVlaue);
}
var onValueChangedHandler = function (value) {
if(value !== $scope.val) {
$scope.data.val = valueService.getValue();
}
}
$scope.$on(myEvents.valueChanged,
function(event, args) {
onValueChangedHandler(args.value);
}
);
})
.service('valueService',
function(myChannel) {
var val = 'Value';
var getValue = function() {
return val;
};
var setValue = function(newValue) {
val = newValue;
myChannel.pubValueChanged(newValue);
};
return {
getValue: getValue,
setValue: setValue
};
})
.config('myEvents', function() {
valueChanged : 'valueChanged'
});
.config('myEvents', function() {
valueChanged : 'valueChanged'
});
.service('myChannel',
function($rootScope, myEvents) {
// Publish to value changed event
var pubValueChanged = function (newVlaue) {
var args = {value: val};
$rootScope.$broadcast(
myEvents.valueChanged,
args
);
};
// Subscribe to value changed event
var subValueChanged = function($scope, handler) {};
return {
publishValueChanged : publishValueChanged,
onValueChanged : onValueChanged
};
});
Controller
Service
Moving sub logic
Channel
.controller('child1Ctrl',
function($scope, valueService, myChannel) {
$scope.val = 'test';
$scope.setValue = function(newValue) {
valueService.setValue(newVlaue);
}
var onValueChangedHandler = function (value) {
if(value !== $scope.val) {
$scope.data.val = valueService.getValue();
}
}
myChannel
.subValueChanged($scope, onValueChangedHandler);
})
.service('valueService',
function(myChannel) {
var val = 'Value';
var getValue = function() {
return val;
};
var setValue = function(newValue) {
val = newValue;
myChannel.pubValueChanged(newValue);
};
return {
getValue: getValue,
setValue: setValue
};
})
.config('myEvents', function() {
valueChanged : 'valueChanged'
});
.config('myEvents', function() {
valueChanged : 'valueChanged'
});
.service('myChannel',
function($rootScope, myEvents) {
// Publish to value changed event
var pubValueChanged = function (newVlaue) {
var args = {value: val};
$rootScope.$broadcast(
myEvents.valueChanged,
args
);
};
// Subscribe to value changed event
var subValueChanged = function($scope, handler) {
$scope.$on(myEvents.valueChanged,
function(event, args) {
handler(args.value);
}
);
};
return {
publishValueChanged : publishValueChanged,
onValueChanged : onValueChanged
};
});
What's better now?
Good separation of concerns
Moved code away from the controller
Extentd/change the logic in a single place
Single place for debugging
Further options
Log fired events (debugging)
Split/merge something (events)
Debunk something (clicks, keydowns)
...
We can also communicate
between directives => can have a $scope
between services => $fakeScope = $rootScope.newScope()
Performance???
$rootScope.$emit
pub and sub on the $rootScope => sub returns unsub function
just pass in the callback from the subscribing $scope => ;-)
unsure after $scope destruction => memory leaks

$emit brings 100% more performance
Channel
.config('myEvents', function() {
valueChanged : 'valueChanged'
});
.service('myChannel', function($rootScope, myEvents) {
var publishValueChanged = function (newVlaue) {
var args = {value: newVlaue};
//$rootScope.$broadcast(myEvents.valueChanged, args);
$rootScope.$emit(myEvents.valueChanged, args);
};
var onValueChanged = function($scope, handler) {
//$scope.$on(myEvents.valueChanged, function(event, args) {
var unsubToValueChanged = $rootScope.$on(myEvents.valueChanged, function(event, args) {
//;-)
handler(args.value);
});
//
$scope.$on('$destroy', function() {
//memory leaks
unsubToValueChanged();
});
};
return {
publishValueChanged : publishValueChanged,
onValueChanged : onValueChanged
};
});
3 lines, 1 file, +100% => :-)
michael@hladky.at
Reacting to changes in AngularJs
By Michael Hladky
Reacting to changes in AngularJs
How to encapsulate your event pub/sub into a separate service
- 1,104