$scope.$on changes not reflected in the view - angularjs

I'm doing a broadcast and on listening to the broadcast i'm trying to updated a variable on the scope that I wanted to display on the view, but the changes are not being reflected in the view immediately, until I click on the UI. Anyone know what should be done at this point, I don't want to use $apply. Here, please find my code.
rApp.factory('pService', ['$http', '$rootScope', '$sanitize',
function ($http, $rootScope, $sanitize) {
var pService = {};
//Some other code
pService.Update=function(status)
{
if(status.LastItemId!=undefined)
{
pService.disItemId = status.LastItemId;
$rootScope.$broadcast('updated',pService.disItemId);
}
}
//Some other code
return pService;
});
rApp.controller('dController', ['$scope','$rootScope' 'pService' ,dController]);
function dController($scope,$rootScope, pService) {
$rootScope.$on('updated',function (event, data) {
$scope.lastItemId = data; // I want to display the lastItemId on UI
})
});

Ideally Services are used for sharing common methods in different controllers. It's good to return this. Also, if you need to return the value to controller, instead using events why don't you simply return the value from a service public method and access the value in controller. Also, it's the controllers' work to initiate or call services' method and update corresponding scopes. Firing event from a service to notify controller is similar to what firing events from one controller to other. Services are not made for that purpose. See my code for reference.
DEMO - http://plnkr.co/edit/Co7ka0sZKZgYk5Oz88Np?p=preview
JS:
var app = angular.module('myApp', []);
app.controller('myController', ['$scope', '$rootScope', 'pService', function ($scope, $rootScope, pService) {
$scope.name = 'softvar';
$scope.itemId = pService.Update({LastItemId: 4})
}]);
app.factory('pService', [ '$rootScope', function ( $rootScope) {
this.pService = {};
//Some other code
this.Update= function(status) {
if (status.LastItemId) {
this.pService.disItemId = status.LastItemId;
console.log(this.pService.disItemId)
return this.pService.disItemId;
}
}
//Some other code
return this;
}]);
HTML:
<body ng-app="myApp" ng-controller="myController">
<h1>ItemId is: {{itemId}}!</h1>
</body>
UPDATE:
DEMO - http://plnkr.co/edit/Co7ka0sZKZgYk5Oz88Np?p=preview
JS:
var app = angular.module('myApp', []);
app.controller('myController', ['$scope', '$rootScope', 'pService', function ($scope, $rootScope, pService) {
$scope.name = 'softvar';
$scope.$on('serviceUpdated', function (ev, data) {
$scope.itemId = data;
});
pService.Update({LastItemId: 4});
}]);
app.factory('pService', [ '$rootScope', function ( $rootScope) {
this.pService = {};
//Some other code
this.Update= function(status) {
if (status.LastItemId) {
this.pService.disItemId = status.LastItemId;
console.log(this.pService.disItemId)
$rootScope.$broadcast('serviceUpdated', this.pService.disItemId);
}
}
//Some other code
return this;
}]);

What triggers the event being sent, i.e. where does your service's update() method get called? You may need to use apply to trigger a digest cycle if it is called from outside angular. I see from the comment you are using SignalR, that will not create a digest cycle to update bindings in angular. Try wrapping your call in an apply like this:
$rootScope.$apply(function(scope) {
service.Update();
});
You also don't need to use $rootScope.on(), you can just use $scope.on(). Broadcasts on the root scope will go down to all child scopes. If the message isn't used elsewhere, you can use $rootScope.emit() which bubbles upward and won't go down through all your child scopes.

Related

Unknown provider: dataProvider [duplicate]

I have two Angular controllers:
function Ctrl1($scope) {
$scope.prop1 = "First";
}
function Ctrl2($scope) {
$scope.prop2 = "Second";
$scope.both = Ctrl1.prop1 + $scope.prop2; //This is what I would like to do ideally
}
I can't use Ctrl1 inside Ctrl2 because it is undefined. However if I try to pass it in like so…
function Ctrl2($scope, Ctrl1) {
$scope.prop2 = "Second";
$scope.both = Ctrl1.prop1 + $scope.prop2; //This is what I would like to do ideally
}
I get an error. Does anyone know how to do this?
Doing
Ctrl2.prototype = new Ctrl1();
Also fails.
NOTE: These controllers are not nested inside each other.
One way to share variables across multiple controllers is to create a service and inject it in any controller where you want to use it.
Simple service example:
angular.module('myApp', [])
.service('sharedProperties', function () {
var property = 'First';
return {
getProperty: function () {
return property;
},
setProperty: function(value) {
property = value;
}
};
});
Using the service in a controller:
function Ctrl2($scope, sharedProperties) {
$scope.prop2 = "Second";
$scope.both = sharedProperties.getProperty() + $scope.prop2;
}
This is described very nicely in this blog (Lesson 2 and on in particular).
I've found that if you want to bind to these properties across multiple controllers it works better if you bind to an object's property instead of a primitive type (boolean, string, number) to retain the bound reference.
Example: var property = { Property1: 'First' }; instead of var property = 'First';.
UPDATE: To (hopefully) make things more clear here is a fiddle that shows an example of:
Binding to static copies of the shared value (in myController1)
Binding to a primitive (string)
Binding to an object's property (saved to a scope variable)
Binding to shared values that update the UI as the values are updated (in myController2)
Binding to a function that returns a primitive (string)
Binding to the object's property
Two way binding to an object's property
I like to illustrate simple things by simple examples :)
Here is a very simple Service example:
angular.module('toDo',[])
.service('dataService', function() {
// private variable
var _dataObj = {};
// public API
this.dataObj = _dataObj;
})
.controller('One', function($scope, dataService) {
$scope.data = dataService.dataObj;
})
.controller('Two', function($scope, dataService) {
$scope.data = dataService.dataObj;
});
And here the jsbin
And here is a very simple Factory example:
angular.module('toDo',[])
.factory('dataService', function() {
// private variable
var _dataObj = {};
// public API
return {
dataObj: _dataObj
};
})
.controller('One', function($scope, dataService) {
$scope.data = dataService.dataObj;
})
.controller('Two', function($scope, dataService) {
$scope.data = dataService.dataObj;
});
And here the jsbin
If that is too simple, here is a more sophisticated example
Also see the answer here for related best practices comments
--- I know this answer is not for this question, but I want people who reads this question and want to handle Services such as Factories to avoid trouble doing this ----
For this you will need to use a Service or a Factory.
The services are the BEST PRACTICE to share data between not nested controllers.
A very very good annotation on this topic about data sharing is how to declare objects. I was unlucky because I fell in a AngularJS trap before I read about it, and I was very frustrated. So let me help you avoid this trouble.
I read from the "ng-book: The complete book on AngularJS" that AngularJS ng-models that are created in controllers as bare-data are WRONG!
A $scope element should be created like this:
angular.module('myApp', [])
.controller('SomeCtrl', function($scope) {
// best practice, always use a model
$scope.someModel = {
someValue: 'hello computer'
});
And not like this:
angular.module('myApp', [])
.controller('SomeCtrl', function($scope) {
// anti-pattern, bare value
$scope.someBareValue = 'hello computer';
};
});
This is because it is recomended(BEST PRACTICE) for the DOM(html document) to contain the calls as
<div ng-model="someModel.someValue"></div> //NOTICE THE DOT.
This is very helpful for nested controllers if you want your child controller to be able to change an object from the parent controller....
But in your case you don't want nested scopes, but there is a similar aspect to get objects from services to the controllers.
Lets say you have your service 'Factory' and in the return space there is an objectA that contains objectB that contains objectC.
If from your controller you want to GET the objectC into your scope, is a mistake to say:
$scope.neededObjectInController = Factory.objectA.objectB.objectC;
That wont work...
Instead use only one dot.
$scope.neededObjectInController = Factory.ObjectA;
Then, in the DOM you can call objectC from objectA. This is a best practice related to factories, and most important, it will help to avoid unexpected and non-catchable errors.
Solution without creating Service, using $rootScope:
To share properties across app Controllers you can use Angular $rootScope. This is another option to share data, putting it so that people know about it.
The preferred way to share some functionality across Controllers is Services, to read or change a global property you can use $rootscope.
var app = angular.module('mymodule',[]);
app.controller('Ctrl1', ['$scope','$rootScope',
function($scope, $rootScope) {
$rootScope.showBanner = true;
}]);
app.controller('Ctrl2', ['$scope','$rootScope',
function($scope, $rootScope) {
$rootScope.showBanner = false;
}]);
Using $rootScope in a template (Access properties with $root):
<div ng-controller="Ctrl1">
<div class="banner" ng-show="$root.showBanner"> </div>
</div>
The sample above worked like a charm. I just did a modification just in case I need to manage multiple values. I hope this helps!
app.service('sharedProperties', function () {
var hashtable = {};
return {
setValue: function (key, value) {
hashtable[key] = value;
},
getValue: function (key) {
return hashtable[key];
}
}
});
I tend to use values, happy for anyone to discuss why this is a bad idea..
var myApp = angular.module('myApp', []);
myApp.value('sharedProperties', {}); //set to empty object -
Then inject the value as per a service.
Set in ctrl1:
myApp.controller('ctrl1', function DemoController(sharedProperties) {
sharedProperties.carModel = "Galaxy";
sharedProperties.carMake = "Ford";
});
and access from ctrl2:
myApp.controller('ctrl2', function DemoController(sharedProperties) {
this.car = sharedProperties.carModel + sharedProperties.carMake;
});
The following example shows how to pass variables between siblings controllers and take an action when the value changes.
Use case example: you have a filter in a sidebar that changes the content of another view.
angular.module('myApp', [])
.factory('MyService', function() {
// private
var value = 0;
// public
return {
getValue: function() {
return value;
},
setValue: function(val) {
value = val;
}
};
})
.controller('Ctrl1', function($scope, $rootScope, MyService) {
$scope.update = function() {
MyService.setValue($scope.value);
$rootScope.$broadcast('increment-value-event');
};
})
.controller('Ctrl2', function($scope, MyService) {
$scope.value = MyService.getValue();
$scope.$on('increment-value-event', function() {
$scope.value = MyService.getValue();
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<h3>Controller 1 Scope</h3>
<div ng-controller="Ctrl1">
<input type="text" ng-model="value"/>
<button ng-click="update()">Update</button>
</div>
<hr>
<h3>Controller 2 Scope</h3>
<div ng-controller="Ctrl2">
Value: {{ value }}
</div>
</div>
I'd like to contribute to this question by pointing out that the recommended way to share data between controllers, and even directives, is by using services (factories) as it has been already pointed out, but also I'd like to provide a working practical example of how to that should be done.
Here is the working plunker: http://plnkr.co/edit/Q1VdKJP2tpvqqJL1LF6m?p=info
First, create your service, that will have your shared data:
app.factory('SharedService', function() {
return {
sharedObject: {
value: '',
value2: ''
}
};
});
Then, simply inject it on your controllers and grab the shared data on your scope:
app.controller('FirstCtrl', function($scope, SharedService) {
$scope.model = SharedService.sharedObject;
});
app.controller('SecondCtrl', function($scope, SharedService) {
$scope.model = SharedService.sharedObject;
});
app.controller('MainCtrl', function($scope, SharedService) {
$scope.model = SharedService.sharedObject;
});
You can also do that for your directives, it works the same way:
app.directive('myDirective',['SharedService', function(SharedService){
return{
restrict: 'E',
link: function(scope){
scope.model = SharedService.sharedObject;
},
template: '<div><input type="text" ng-model="model.value"/></div>'
}
}]);
Hope this practical and clean answer can be helpful to someone.
You could do that with services or factories. They are essentially the same apart for some core differences. I found this explanation on thinkster.io to be the easiest to follow. Simple, to the point and effective.
Couldn't you also make the property part of the scopes parent?
$scope.$parent.property = somevalue;
I'm not saying it's right but it works.
Ah, have a bit of this new stuff as another alternative. It's localstorage, and works where angular works. You're welcome. (But really, thank the guy)
https://github.com/gsklee/ngStorage
Define your defaults:
$scope.$storage = $localStorage.$default({
prop1: 'First',
prop2: 'Second'
});
Access the values:
$scope.prop1 = $localStorage.prop1;
$scope.prop2 = $localStorage.prop2;
Store the values
$localStorage.prop1 = $scope.prop1;
$localStorage.prop2 = $scope.prop2;
Remember to inject ngStorage in your app and $localStorage in your controller.
There are two ways to do this
1) Use get/set service
2)
$scope.$emit('key', {data: value}); //to set the value
$rootScope.$on('key', function (event, data) {}); // to get the value
Second Approach :
angular.module('myApp', [])
.controller('Ctrl1', ['$scope',
function($scope) {
$scope.prop1 = "First";
$scope.clickFunction = function() {
$scope.$broadcast('update_Ctrl2_controller', $scope.prop1);
};
}
])
.controller('Ctrl2', ['$scope',
function($scope) {
$scope.prop2 = "Second";
$scope.$on("update_Ctrl2_controller", function(event, prop) {
$scope.prop = prop;
$scope.both = prop + $scope.prop2;
});
}
])
Html :
<div ng-controller="Ctrl2">
<p>{{both}}</p>
</div>
<button ng-click="clickFunction()">Click</button>
For more details see plunker :
http://plnkr.co/edit/cKVsPcfs1A1Wwlud2jtO?p=preview
I looked thru the answers above, I recommend pejman's Dec 29 '16 at 13:31 suggestion but he/she has not left a full answer. Here it is, I will put this as --> (you need a service and a listener $watch on one of the scopes from controllers for changes in the service area)
var app =
angular.module('myApp', ['ngRoute', 'ngSanitize']);
app.service('bridgeService', function () {
var msg = "";
return msg;
});
app.controller('CTRL_1'
, function ($scope, $http, bridgeService)
{
$http.get(_restApi, config)
.success(
function (serverdata, status, config) {
$scope.scope1Box = bridgeService.msg = serverdata;
});
});
app.controller('CTRL_2'
, function ($scope, $http, bridgeService)
{
$scope.$watch( function () {
return (bridgeService.msg);
}, function (newVal, oldVal) {
$scope.scope2Box = newVal;
}, true
);
});
If you don't want to make service then you can do like this.
var scope = angular.element("#another ctrl scope element id.").scope();
scope.plean_assign = some_value;
Besides $rootScope and services, there is a clean and easy alternative solution to extend angular to add the shared data:
in the controllers:
angular.sharedProperties = angular.sharedProperties
|| angular.extend(the-properties-objects);
This properties belong to 'angular' object, separated from the scopes, and can be shared in scopes and services.
1 benefit of it that you don't have to inject the object: they are accessible anywhere immediately after your defination!

Angular calling shared service data update in controller

I am trying to write some very primitive angular code with 2 controllers and 1 service.
So when I call shared service from controller 1 and update data, I want to use same in my controller 2 $scope so that controller 2 $scope value can reflect on my DOM.
App.controller('oneCtrl', function($scope, $uibModal, $log, sharedProperties) {
// Call a new DOM element to so that ModalInstanceCtrl will be called
// Once controller 2 finishes, I want to update a $scope variable here
// $scope.projectList = getProjectList();
});
App.controller('ModalInstanceCtrl', function ($scope, $uibModalInstance, sharedProperties) {
// This is a new modal which uses sharedProperties
// Update setProjectList() in service
});
App.service('sharedProperties', function() {
var projectList = new Array();
return {
getProjectList: function() {
return projectList;
},
setProjectList: function(value) {
projectList.push(value);
},
}
});
Once controller 2 calls setProjectList(). I want to auto update $scope value in controller 1 using getProjectList()
Please let me know how I can do that? Also do let me know if any further details needed on same.
A service in angular is a singleton so if you change data on the service it will be reflected whenever you call that service.
var app = angular.module('plunker', []);
app.controller('FirstCtrl', function($scope, userData) {
$scope.favoriteBook = userData.favoriteBook;
$scope.getFavoriteBook = function(){
$scope.favoriteBook = userData.favoriteBook;
}
});
app.controller('SecondCtrl', function($scope, userData) {
$scope.changeBook = function(){
userData.favoriteBook = 'The Hobbyt';
}
});
app.factory('userData', function(){
var favoriteBook = 'Harry Potter';
return{
favoriteBook : favoriteBook
}
})
Here you got a service that exposes an object, you can change the value of that object in the second controller and see it reflected in the first controller. Call changeBook(), and then getFavoriteBook()
This is the plunker:
the plunker

Updating the scope variable across multiple controllers in angularjs

I have two controllers- searchBoxController and productList. What I am trying to do is to update the scope variable $scope.products from multiple controllers. I know that defining it as a root variable is a very bad design- but putting that in shared service is not solving the problem. The update doesn't reflect in the HTML templates!
function SearchTermService(){
this.productSearch = function(data, $http){
var url = "";
$http.get(url).then(function(resp){
return resp.data;
},
function(err){
console.log(err);
});
};
};
var app = angular.module('app', []);
app.service("myService", MyService);
app.service("searchTermService", SearchTermService);
app.run(function($rootScope) {
$rootScope.products = new Date();
});
app.controller('productList', function ($scope, $rootScope, $http, myService) {
$rootScope.products = prod_res;
});
app.controller('searchBoxController', function($scope, $http, searchTermService, $rootScope){
$scope.getSearchResults = function(){
$rootScope.products = searchTermService.productSearch($scope.term, $http)
};
});
PS: I am not sure if I need to have a promise returned while assigning the $rootScope.products in 'searchBoxController', as the console.log says its undefined. Currently I am not returning a promise from the service.
In order to update a scope variable across multiple controller, you can use angular service.
You should use this because all angular services are singletons, so you can easily share common logic, share data between controller.
I've made an example where I use service in order to update some data. Then, my factory return an object data, so we will get an object, not just a fixed value. Thanks to this, our data will be updated, we will keep the binding data.
Controller
(function(){
function Controller($scope, $timeout, Service) {
//Retrieve current data object of our service
$scope.data = Service.value;
//will be set to 4
$timeout(function(){
Service.set(4, 'product');
}, 1000);
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
(function(){
function Controller2($scope, $timeout, Service) {
//Retrieve current data object of our service
$scope.data2 = Service.value;
}
angular
.module('app')
.controller('ctrl2', Controller2);
})();
Service
(function(){
function Service() {
//Our data object
var data = {
product: null
};
function set(value, field){
data[field] = value;
}
return {
set: set,
value: data
};
}
angular
.module('app')
.factory('Service', Service);
})();
HTML
<body ng-app='app'>
<div ng-controller='ctrl'>
<h2>Service value : {{data.product}}</h2>
</div>
<div ng-controller='ctrl2'>
<h2>Service value from controller2 : {{data2.product}}</h2>
</div>
</body>
So, we will share our data across multiple controller. By using services, you can avoid to use the $rootScope.
You can see the Working plunker

How to get an AngularJS controller in a filter

Is it possible to use dependency injection to reference a controller inside of a filter? I tried the following:
app.filter('myFilter', function(MyCtrl) {...})
app.controller('MyCtrl', function(...) {})
But I get an error that MyCtrl dependency cannot be found.
If you are trying to access some sort of state information inside your filter, you should consider moving that functionality into a service and then accessing the service from both your filter and your controller.
I can't come up with a reason to want to instantiate a controller inside a filter, so perhaps if you gave a bit more background about the problem you are trying to solve we could give you more tailored information on how best to implement it.
In any case, here's a Plunk showing a filter and a controller sharing some data:
app.controller('MainCtrl', function($scope, MyService) {
$scope.text = "ABCDEFG";
$scope.data = 2;
$scope.$watch('data', function() {
MyService.setData($scope.data);
})
});
app.service('MyService', function() {
service = {}; // the service object we'll return
var dataValue = 'data';
service.setData = function(data) {
dataValue = data;
}
service.getData = function() {
return dataValue;
}
return service;
});
app.filter('truncate', function(MyService) {
return function(input) {
return input.substring(0, MyService.getData());
};
});
If you check out the plunk, note that you have to update the text to get the filter to re-run with an updated number of characters to show.
actually this is the way:
Just inject $filter to your controller
.controller('myController', function($scope, $filter) {...});
Then it does not matter where you want to use that filter, just use it like this:
$filter('filterName');
If you want to pass arguments to that filter, do it using separate parentheses:
.controller('myController', function($scope, $filter) {
$filter('filterName')(argument1,argument2);
});

Alternatives to angularjs $watch to trigger ui change from service

I'm wondering whether there is a different approach to using $watch in order to achieve the following.
Setup:
ControllerA depends on ServiceA.
ControllerB depends on ServiceB.
Current browser view is showing both controllers.
Scenario:
ControllerA is initiating a function on ServiceA, which in turn changes the value of propery X in ServiceB which should be reflected in the UI of ControllerB.
http://jsfiddle.net/zexscvax/2/
html:
<div>
<div ng-controller="ControllerA"></div>
<div ng-controller="ControllerB">Progress: {{progress}}</div>
</div>
js:
var myApp = angular.module('myApp', []);
myApp.factory('serviceA', ['$q', '$interval', 'serviceB', function ($q, $interval, serviceB) {
var service = {};
service.start = function () {
var progress = 0;
var deferred = $q.defer();
deferred.promise.then(null,null, notifyServiceB);
function notifyServiceB() {
serviceB.update(progress);
}
$interval(function() {
if (progress == 0.99) {
deferred.resolve();
} else {
progress += 0.01;
deferred.notify(progress);
}
}, 50, 100);
};
return service;
}]);
myApp.factory('serviceB', ['$rootScope', function ($rootScope) {
var service = {};
service.update = function (progress) {
console.log('update', progress);
service.progress = progress;
//$rootScope.$apply(); // <+ ERROR: $digest already in progress
};
return service;
}]);
myApp.controller('ControllerA', ['$scope', 'serviceA',
function ($scope, serviceA) {
serviceA.start();
}]);
myApp.controller('ControllerB', ['$scope', 'serviceB',
function ($scope, serviceB) {
$scope.progress = serviceB.progress;
/* this works but I'm not sure whether this is performing
$scope.$watch(function () { return serviceB.progress; },
function (value) {
$scope.progress = serviceB.progress;
}
);
*/
}]);
Without the $watch in ControllerB for the property X in ServiceB, the UI would not get updated. I've also tried injecting $rootScope in ServiceB in order to run an apply() but that wouldn't work.
I'm not entirely sure whether there's a better way to setup this scenario or whether $watch is fine. I'm a bit worried about performance issues as the value of property X changes almost every 50 ms (it's basically a visual timer counting down).
Thanks for your input.
If you don't use $watch, you can use $rootScope to broadcast, and on controller B, you can $on this event and handle the view update.

Resources