Angular $rootscope.$broadcast correct usage in a service - angularjs

From what I've read, it seems using $rootScope.$broadcast is not advisable unless absolutely necessary. I'm using it in a service to notify a controller that a variable has changed. Is this incorrect? Is there a better way to do it? Should I be using watch instead (even though the variable only changes on user interaction) ?
the service:
function Buildservice($rootScope) {
var vm = this;
vm.box= [];
var service = {
addItem: addItem,
};
return service;
// Add item to the box
// Called from a directive controller
function addItem(item) {
vm.box.push(item);
broadcastUpdate();
}
function broadcastUpdate() {
$rootScope.$broadcast('updateMe');
}
// In the controller to be notified:
// Listener for box updates
$scope.$on('updateMe', function() {
// update variable binded to this controller
});
// and from a separate directive controller:
function directiveController($scope, buildservice) {
function addToBox(item){
buildservice.addItem(item);
}
So this works just fine for me, but I can't figure out if this is the way I should be doing it. Appreciate the help!

If you are in same module, why don't you use $scope instead of $rootScope?

You can use a callback function to notify the controller something has changed. You supply the service a function from the controller, and invoke that particular function whenever your variable has been changed. You could also notify multiple controllers if needed.
I have created a small example:
HMTL:
<div ng-controller="CtrlA as A">
{{A.label}}
<input type="text" ng-model="A.input" />
<button ng-click="A.set()">set</button>
</div>
<div ng-controller="CtrlB as B">
{{B.label}}
<input type="text" ng-model="B.input" />
<button ng-click="B.set()">set</button>
</div>
JS
var app = angular.module('plunker', []);
app.controller('CtrlA', function(AService) {
var vm = this;
vm.label = AService.get();
vm.notify = function() {
vm.label = AService.get();
}
vm.set = function() {
AService.set(vm.input)
}
AService.register(vm.notify);
});
app.controller('CtrlB', function(AService) {
var vm = this;
vm.label = AService.get();
vm.notify = function() {
vm.label = AService.get();
}
vm.set = function() {
AService.set(vm.input)
}
AService.register(vm.notify);
});
app.factory("AService", function() {
var myVar = "Observer";
var observers = [];
return {
get: function() {
return myVar;
},
set: function(name) {
console.log(name);
myVar = name;
this.notify();
},
register: function(fn) {
observers.push(fn);
},
notify: function() {
for( i = 0; i < observers.length; i++) {
observers[i]();
}
}
}
})
You will see upon executing this that the controllers get notified when the internal variable has been changed. (Notice: I haven't filtered the original sender from the list) (Plnkr)

Related

Sharing scope data in controller

My spring mvc controller returns an object.
My scenario is:
On click of a button from one page say sample1.html load a new page say sample2.html in the form of a table.
In sample1.html with button1 and controller1--> after clicking button1-->I have the object(lets say I got it from backend) obtained in controller1.
But the same object should be used to display a table in sample2.html
How can we use this object which is in controller1 in sample2.html?
You can use a service to store the data, and inject it in your controllers. Then, when the value is updated, you can use a broadcast event to share it.
Here is a few example:
HTML view
<div ng-controller="ControllerOne">
CtrlOne <input ng-model="message">
<button ng-click="handleClick(message);">LOG</button>
</div>
<div ng-controller="ControllerTwo">
CtrlTwo <input ng-model="message">
</div>
Controllers
function ControllerOne($scope, sharedService) {
$scope.handleClick = function(msg) {
sharedService.prepForBroadcast(msg);
};
}
function ControllerTwo($scope, sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = sharedService.message;
});
}
Service
myModule.factory('mySharedService', function($rootScope) {
var sharedService = {};
sharedService.message = '';
sharedService.prepForBroadcast = function(msg) {
this.message = msg;
this.broadcastItem();
};
sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
return sharedService;
});
JSFiddle demo
you can use factory to share data between controllers
<div ng-controller="CtrlOne">
<button ng-click="submit()">submit</button>
</div>
<div ng-controller="CtrlTwo">
{{obj}}
</div>
.controller('CtrlOne', function($scope, sampleFactory) {
$scope.sampleObj = {
'name': 'riz'
}; //object u get from the backend
$scope.submit = function() {
sampleFactory.setObj($scope.sampleObj);
}
})
.controller('CtrlTwo', function($scope, sampleFactory) {
$scope.obj = sampleFactory.getObj();
})
.factory('sampleFactory', function() {
var obj = {};
return {
setObj: function(_obj) {
obj = _obj;
},
getObj: function() {
return obj;
}
}
})

Communication Between Two Controllers; References vs Primatives

I have seen an unexpected behaviour in Angularjs with its factories.
I used a factory to communication between two controllers.
In the first scenario it is working fine but not in second one. The only difference between them is that in first example I am accessing the name from the view but in second one I am accessing in scope variable.
Scenario 1
<div ng-controller="HelloCtrl">
<a ng-click="setValue('jhon')">click</a>
</div>
<br />
<div ng-controller="GoodbyeCtrl">
<p>{{fromFactory.name}}</p>
</div>
//angular.js example for factory vs service
var app = angular.module('myApp', []);
app.factory('testFactory', function() {
var obj = {'name':'rio'};
return {
get : function() {
return obj;
},
set : function(text) {
obj.name = text;
}
}
});
function HelloCtrl($scope, testFactory) {
$scope.setValue = function(value) {
testFactory.set(value);
}
}
function GoodbyeCtrl($scope, testFactory) {
$scope.fromFactory = testFactory.get();
}
Scenario 2
<div ng-controller="HelloCtrl">
<a ng-click="setValue('jhon')">click</a>
</div>
<br />
<div ng-controller="GoodbyeCtrl">
<p>{{fromFactory}}</p>
</div>
//angular.js example for factory vs service
var app = angular.module('myApp', []);
app.factory('testFactory', function() {
var obj = {'name':'rio'};
return {
get : function() {
return obj;
},
set : function(text) {
obj.name = text;
}
}
});
function HelloCtrl($scope, testFactory) {
$scope.setValue = function(value) {
testFactory.set(value);
}
}
function GoodbyeCtrl($scope, testFactory) {
$scope.fromFactory = testFactory.get().name;
}
The difference is:
Scenario I
$scope.fromFactory = testFactory.get();
<div ng-controller="GoodbyeCtrl">
<p> {{fromFactory.name}}</p>
</div>
The $scope variable is set to testFactory.get() which is an object reference. On each digest cycle the watcher fetches the value of the property name using the object reference. The DOM gets updated with changes to that property.
Scenario II
$scope.fromFactory = testFactory.get().name;
<div ng-controller="GoodbyeCtrl">
<p>{{fromFactory}}</p>
</div>
The $scope variable is set to testFactory.get().name which is a primative. On each digest cycle, the primative value doesn't change.
The important difference is that when a reference value is passed to a function, and a function modifies its contents, that change is seen by the caller and any other functions that have references to the object.

Directive input value changing when calling directive multiple times

I have the following directive,
(function(){
angular.module("pulldownmodule")
.controller("pulldownCtrl",['pullDownServices','$scope',"multiselectDefaults","templates",function(pullDownServices,$scope,multiselectDefaults,templates){
//Local variables
_this = this;
var dropdownData = {};
var currentTemplate = {};
var firstTemplate;
//Validation function
function validateInput(){
console.log(_this.dropdownid);
if (_this.dropdownid) {
getPullDownData(_this.dropdownid,_this.filter);
}
//check if the dropdown ID is present
}
$scope.$watch('pulldownCtrl.dropdownid',function(newVal){
console.log(_this.dropdownid);
if (newVal) {
validateInput();
};
});
}])
.directive('pulldown', [function(){
return {
scope: {},
bindToController:{
dropdownid:"=",
filter:"=",
templatetype:"#"
},
controller:'pulldownCtrl',
controllerAs:'pulldownCtrl',
templateUrl: 'pulldown/html/dropDownDirective.html'
};
}]);
})()
I am calling the directive 2 times as follows
<div pulldown dropdownid="currentID" templatetype="template2" filter="customFilter"></div>
<div pulldown dropdownid="currentID2" templatetype="template2" filter="customFilter2"></div>
Passing the value of dropdownid in the controller as
$scope.currentID = 1;
$scope.currentID2 = 5;
The issue here is if i call the directive only 1 time everything works fine, but if i call it multiple times then i get the _this.dropdownid in $watch as the second directives value. Not sure what I'm doing wrong.
Probably i have to create a new instance using 'new'.
Directive HTML
Following is the major part of the directives HTML,
<select id="searchData" kendo-multi-select="pulldown" k-options="ddoptions" k-rebind="ddoptions" k-on-change="getChangevalue('searchData')"></select>
i'm using the kendo multiselect
As #hgoebl point out _this = this; it is kind of global (not application level) variable though you use in a function scope.
Use var _this = this;
//after assign "_this" is not accessible here
(function(){
//after assign "_this" is accessible here
angular.module("pulldownmodule")
.controller(...function(){
_this = this; //use var _this = this;
//...others code
});
}();
angular.module("pulldownmodule",[])
.controller("pulldownCtrl",['$scope',function($scope){
//Local variables
_this = this;
// Initialize your models here
$scope.currentID = '1';
$scope.currentID2 = '3';
var dropdownData = {};
var currentTemplate = {};
var firstTemplate;
//Validation function
function validateInput(){
console.log(_this.dropdownid);
if (_this.dropdownid) {
getPullDownData(_this.dropdownid,_this.filter);
}
//check if the dropdown ID is present
}
$scope.$watch('currentID', function (newVal) {
console.log($scope.currentID);
if (newVal) {
validateInput();
};
});
$scope.$watch('currentID2', function (newVal) {
console.log($scope.currentID2);
if (newVal) {
validateInput();
};
});
}])
.directive('pulldown', [function(){
return {
scope: {
dropdownid:"=",
filter:"=",
templatetype:"#"
},
template: '<select ng-model="dropdownid"> <option ng-repeat="a in [1,2,3,4,5]" value="{{a}}"> {{a}} </option> </select>',
link: function (scope,element, attr) {
scope.$watch("dropdownid", function (newVal) {
scope.dropdownid;
});
}
};
}]);

Should I return collection from factory to controller via function?

I'm struggling with choosing the correct way of accessing collection located inside service from controller. I see two options, both have ups and downs:
Returning function from service that returns collection:
Service:
app.factory('healthService', function () {
var healths = [{},{},{},{}];
function updateHealths() {
healths = [...];
}
return {
getHealths : function() {
return healths;
},
update : function () {
updateHealths();
}};
});
Controller:
$scope.healths = healthService.getHealths;
$scope.update = healthService.update;
View:
ng-repeat = "health in healths()"
ng-click = "update()" '
I'm not sure about efficiency here- how often will healths() be evaluated?
Giving the possibility to access collection directly from controller:
Service:
app.factory('healthService', function () {
return {
healths : [{},{},{},{}],
update :function() {
this.healths = [...];
}
});
Controller:
$scope.healthService = healthService;
View:
ng-repeat = "health in healthService.healths" '
ng-click = "healthService.update()"
Which one is better, faster? Any other tips?
Why not try wrapping your collection in an object (which acts as a service state) to allow binding to occur on the object, rather than by exposing functions. For example:
app.factory('healthService', function() {
var state = {
healths: [...]
};
return {
getState: function() { return state; },
updateHealths: function() { state.healths = [...]; }
};
});
Then inside your controller:
$scope.healthState = healthService.getState();
Then to reference your healths from your html, use:
<div ng-repeat="health in healthState.healths"></div>

Refresh a controller from another controller in Angular

In order to have two controllers speak to each other in Angular, it is recommended to create a common service which is made accessible to both controllers. I've attempted to illustrate this in a very simple fiddle. Depending on which button you press, the app is supposed to tailor the message below the buttons.
So why isn't this working? Am I missing something obvious or more fundamental?
HTML
<div ng-controller="ControllerOne">
<button ng-click="setNumber(1)">One</button>
<button ng-click="setNumber(2)">Two</button>
</div>
<div ng-controller="ControllerTwo">{{number}} was chosen!</div>
JavaScript
var app = angular.module('app', []);
app.factory("Service", function () {
var number = 1;
function getNumber() {
return number;
}
function setNumber(newNumber) {
number = newNumber;
}
return {
getNumber: getNumber,
setNumber: setNumber,
}
});
function ControllerOne($scope, Service) {
$scope.setNumber = Service.setNumber;
}
function ControllerTwo($scope, Service) {
$scope.number = Service.getNumber();
}
Try creating a watch in your controller:
$scope.$watch(function () { return Service.getNumber(); },
function (value) {
$scope.number = value;
}
);
Here is a working fiddle http://jsfiddle.net/YFbC2/
Seems like problem with property that holds a primitive value. So you can make these changes:
app.factory("Service", function () {
var number = {val: 1};
function getNumber() {
return number;
}
function setNumber(newNumber) {
number.val = newNumber;
}
return {
getNumber: getNumber,
setNumber: setNumber,
}
});
See fiddle
Just call in HTML Service.getNumber() and in controller ControllerTwo call Service like:
$scope.Service = Service;
Example:
HTML
<div ng-controller="ControllerOne">
<button ng-click="setNumber(1)">One</button>
<button ng-click="setNumber(2)">Two</button>
</div>
<div ng-controller="ControllerTwo">{{Service.getNumber()}} was chosen!</div>
JS
function ControllerOne($scope, Service) {
$scope.setNumber = Service.setNumber;
}
function ControllerTwo($scope, Service) {
$scope.Service = Service;
}
Demo Fidlle

Resources