Auto-updating scope variables in angularjs - angularjs

I'm currently playing with AngularJS. I'd like to return, from a service, a variable that will let the scope know when it has changed.
To illustrate this, have a look at the example from www.angularjs.org, "Wire up a backend". Roughly, we can see the following:
var projects = $firebase(new Firebase("http://projects.firebase.io"));
$scope.projects = projects;
After this, all updates made to the projects object (through updates, be it locally or remotely) will be automatically reflected on the view that the scope is bound to.
How can I achieve the same in my project? In my case, I want to return a "self-updating" variable from a service.
var inbox = inboxService.inboxForUser("fred");
$scope.inbox = inbox;
What mechanisms let the $scope know that it should update?
EDIT:
In response to the suggestions, I tried a basic example. My controller:
$scope.auto = {
value: 0
};
setInterval(function () {
$scope.auto.value += 1;
console.log($scope.auto.value);
}, 1000);
And, somewhere in my view:
<span>{{auto.value}}</span>
Still, it only displays 0. What am I doing wrong ?

UPDATE:
I made a demo plunker: http://plnkr.co/edit/dmu5ucEztpfFwsletrYW?p=preview
I use $timeout to fake updates.
The trick is to use plain javascript references:
You need to pass an object to the scope.
You mustn't override that object, just update or extend it.
If you do override it, you lose the "binding".
If you use $http it will trigger a digest for you.
So, whenever a change occurs, the scope variable reference to same object that gets updated in the service, and all the watchers will be notified with a digest.
AFAIK, That's how $firebase & Restangular work.
If you do multiple updates you need to have a way of resetting properties.
Since you hold a reference to an object across the application, you need to be aware of memory leaks.
For example:
Service:
app.factory('inboxService', function($http){
return {
inboxForUser: function(user){
var inbox = {};
$http.get('/api/user/' + user).then(function(response){
angular.extend(inbox, response.data);
})
return inbox;
}
};
});
Controller:
app.controller('ctrl', function(inboxService){
$scope.inbox = inboxService.inboxForUser("fred");
});

It depends on how the object is updating. If it gets updated "within" angular, a digest cycle will be triggered (See http://docs.angularjs.org/guide/scope), and the view will update automatically. That is the beauty of Angular.
If the object gets updated "outside" of angular (e.g. a jQuery plugin), then you can manually trigger a digest cycle by wrapping the code that's doing the updating in an $apply function. Something like this:
$scope.$apply(function() {
//my non angular code
});
See http://docs.angularjs.org/api/ng.$rootScope.Scope for more info.

Related

Why do we need $scope.apply() when do a change from console but not otherwise?

Say there is an AngularJS controller like this:
var module = angular.module('myApp', []);
module.controller('TimeCtrl', function($scope, $interval) {
var tick = function() {
$scope.clock = Date.now();
window.scope = $scope;
}
tick();
$interval(tick, 1000);
});
Changing $scope.clock would automatically reflect in the DOM.
However, when I do it in the console, I need to do explicitly do
$scope.apply. Why is it so? Is $interval doing some magic?
In general, don't I need to $scope.watch these variables? I thought the purpose of $scope.watch was this.
1.
$interval(f, time)
is more or less
setInterval(function() {
f();
$rootScope.$apply();
}, time)
2.
<div>{{test}}</div>
is more or less
$scope.$watch('test', function(value) {
divElement.innerHTML = value;
})
Why is it so? Is $interval doing some magic?
Yes, it is. It is triggering automatically the AngularJS digest cycle, something you do "manually" with the $scope.$apply() (which also triggers it). This causes the changes to be reflected in the DOM. If the digest cycle is not triggered, AngularJS "does not know changes have been made in the model, so it does not update the DOM".
(...) don't I need to watch these variables?
No, unless you need to be notified when any of these variables have changed their values. As long as you do all changes inside the AngularJS scope the DOM will always be "notified" (updated).
How to know when I am doing the thing inside the AngularJS scope?
Usually when you use functions provided by services such as $interval and $timeout, you're doing things inside the scope, because these are wrappers of the original (setInterval and setTimeout) and trigger automatically the mentioned digest cycle, keeping things synced between model and DOM.
So, finally,
Why do we need $scope.apply() when doing a change from console but not
otherwise?
Because from console you are doing some changes outside the AngularJS scope and need to trigger the mentioned digest cycle by yourself.

Angular: Setting variables inside $on events, need to call $apply?

I am using an angular module then sends messages and I am trying to set a variable to true or false but I notice that they do not get updated in the UI unless I wrap them inside a $apply. Is this the best way of doing this?
This is my working code, remove the $apply and although the variable is updated - the view never updates.
$scope.$on('duScrollspy:becameActive', ($event, $element, $target) => {
$scope.$apply(() => {
this.ready = true;
});
});
Anyone any ideas on the best way to fix this? Calling $apply seems like a code smell, I know listening for events on the scope isn't considered good practice but this is a third party Angular module.
Modifying the scope inside an event requires a call to $apply().
(this is bound to the scope btw)
There's no way around it. (if you need the view updated.)
$apply is actually called automatically in many places like $http promise resolve code (https://github.com/angular/angular.js/blob/master/src/ng/http.js line 1331-1351) but it's not called on events.
Note, line numbers may change. Search for function done()
So this is not code smell, this is standard.
Your problem is likely the this keyword. It's important to remember that this changes it's meaning depending on the context in which it is used. You probably think that this is referring to your controller, or service, or whatever object you have defined your listener inside of. That's why you see this common pattern of naming the controller as the very first statement:
function MyController($scope, $rootScope) {
var vm = this;
$scope.$on('duScrollspy:becameActive', ($event, $element, $target) => {
vm.ready = true;
// or
$scope.ready = true
// is better than
this.ready = true;
});
}

Digest is not triggered when scope variable is updated in AngularJS

I am using AngularJS and angularFire to show a list of my tasks:
<ul ng-repeat="tool in tools">
<li>{{tool.name}} {{ tool.description}}</li>
</ul>
var toolRef = new Firebase(dbRef + "/tools/" + toolId);
toolRef.once('value', function(snapshot) {
console.log(angular.toJson(snapshot.val()));
$scope.tools.push(snapshot.val());
//$scope.$apply();
});
http://jsfiddle.net/oburakevych/5n9mj/11/
Code is very simple: I bind a 'site' object to my Firebase DB. The object contains a list of ID of relevant tools. Then I load every tool in the $scope.tools variable and use ng-repeat to show them in the UI.
However, every time I push a new entry into my $scope.tools - the DOM is not updated. I have to call $scope.$apply to trigger digest after every push - see commented out line 18 and then it works.
It's really strange since I sow this several times now and only with scope variables bound with angularFire.
Can anyone explain this? Am I doing anything wrong?
I am not sure about the Firebase once method as how it work, but it seems like it is making changes to model outside of Angular context in it's callback and hence you need $scope.$apply.
In anuglar services like $http ,$resource, and $timeout internally call $scope.$apply so that you don't need to call it on the callback. If the Firebase system provides a method replacement for once which internally does apply or returns a promise, you can very well skip call to apply method again and again.
Because you change the scope outside of AngularJS, you must use $scope.$apply() to inform Angular about the scope changes. That is a common way to handle that. To use the error handling of AngularJS i would wrap the code in a callback function like:
$scope.$apply(function(){
$scope.tools.push(snapshot.val());
});
As tschiela says, you need to wrap the function. I use $timeout. So...
<ul ng-repeat="tool in tools">
<li>{{tool.name}} {{ tool.description}}</li>
</ul>
var toolRef = new Firebase(dbRef + "/tools/" + toolId);
toolRef.once('value', function(snapshot) {
$timeout(function() {
$scope.tools.push(snapshot.val());
})
});
To update scopes when you change them, you need to use the factory angularFireCollection of angularFire. Otherwise you are just calling a javascript class wich won't update scopes until $apply().
var getTool = function(dbRef,toolId) {
console.log("Gettingtool ID: " + toolId);
angularFireCollection(dbRef + "/tools/" + toolId, function(snapshot) {
console.log(angular.toJson(snapshot.val()));
$scope.tools.push(snapshot.val());
});
};
DEMO

Force "soft" update of fields with Angularjs

I have an application, call it a "form-filler" that works with many, many sites using Jquery to automatically update fields.
Pseudo Code:
Inject Jquery into the webpage
Discover the required form.
Update the values, e.g.,
$(document).ready(function) {
$('#id').val("some value");
}
I have a new customer who is using Angularjs and this model breaks down as the $scope is obviously being updated "out-of-band". I don't have access to the third party source to make changes, so I was wondering if it is possibly to get a jQuery update to trigger an Angularjs update?
You can use angular.element() to get a hold of the scope and the ngModelController:
var value = 'theNewValue';
var el = angular.element($('#name'));
el.scope().$apply(function(){
el.val(value);
el.controller('ngModel').$setViewValue(el.val());
});
Here is a simple example: http://plnkr.co/edit/OJQQmanwQoFQSgECuqal?p=preview
Agreeing on the other responses, I'd suggest to use $timeout instead of $apply to avoid problems with the digest phase.
Like in #liviu-t response, get hold of the $timeout service by means of the $element's injector. Then use it as it was a nextTick() function. It is in fact (with second argument 0 or missing) almost equivalent to nextTick(), with the difference that it always runs its argument in the digest phase, unlike $apply, which must be called outside of the digest.
It's a tad complicated depending on the actual case. My solution assumes that the elements are available on dom ready and not loaded by angular using partials. DEMO
JS
function setAngularValue($elem, value) {
var scope = $elem.scope();
var ngModelName = $elem.attr('ng-model');
if(ngModelName) {
var $injector = $elem.injector();
//get the parse service to use on the ng-model
var $parse = $injector.get('$parse');
var ngModel = $parse(ngModelName);
scope.$apply(function() {
//this will allow any ng-model value ex: my.object.value
ngModel.assign(scope, value);
});
}
else {
//fallback if there is no model, weird case imho
$elem.val(value);
}
}
$(document).ready(function() {
var $elem = angular.element('#myJqueryId');
var value = 'some value';
setAngularValue($elem, value);
});
HTML
<p>Hello {{my.angular.model}}!</p>
<input id="myJqueryId" ng-model="my.angular.model"></input>
LINKS
$injector
$parse

Angular.js - share the same model between multiple views by $rootScope [duplicate]

I have a problem where i'm initialising a variable on the scope in a controller. Then it gets changed in another controller when a user logs in. This variable is used to control things such as the navigation bar and restricts access to parts of the site depending on the type of user, so its important that it holds its value. The problem with it is that the controller that initialises it, gets called again by angular some how and then resets the variable back to its initial value.
I assume this is not the correct way of declaring and initialising global variables, well its not really global, so my question is what is the correct way and is there any good examples around that work with the current version of angular?
You've got basically 2 options for "global" variables:
use a $rootScope http://docs.angularjs.org/api/ng.$rootScope
use a service http://docs.angularjs.org/guide/services
$rootScope is a parent of all scopes so values exposed there will be visible in all templates and controllers. Using the $rootScope is very easy as you can simply inject it into any controller and change values in this scope. It might be convenient but has all the problems of global variables.
Services are singletons that you can inject to any controller and expose their values in a controller's scope. Services, being singletons are still 'global' but you've got far better control over where those are used and exposed.
Using services is a bit more complex, but not that much, here is an example:
var myApp = angular.module('myApp',[]);
myApp.factory('UserService', function() {
return {
name : 'anonymous'
};
});
and then in a controller:
function MyCtrl($scope, UserService) {
$scope.name = UserService.name;
}
Here is the working jsFiddle: http://jsfiddle.net/pkozlowski_opensource/BRWPM/2/
If you just want to store a value, according to the Angular documentation on Providers, you should use the Value recipe:
var myApp = angular.module('myApp', []);
myApp.value('clientId', 'a12345654321x');
Then use it in a controller like this:
myApp.controller('DemoController', ['clientId', function DemoController(clientId) {
this.clientId = clientId;
}]);
The same thing can be achieved using a Provider, Factory, or Service since they are "just syntactic sugar on top of a provider recipe" but using Value will achieve what you want with minimal syntax.
The other option is to use $rootScope, but it's not really an option because you shouldn't use it for the same reasons you shouldn't use global variables in other languages. It's advised to be used sparingly.
Since all scopes inherit from $rootScope, if you have a variable $rootScope.data and someone forgets that data is already defined and creates $scope.data in a local scope you will run into problems.
If you want to modify this value and have it persist across all your controllers, use an object and modify the properties keeping in mind Javascript is pass by "copy of a reference":
myApp.value('clientId', { value: 'a12345654321x' });
myApp.controller('DemoController', ['clientId', function DemoController(clientId) {
this.clientId = clientId;
this.change = function(value) {
clientId.value = 'something else';
}
}];
JSFiddle example
Example of AngularJS "global variables" using $rootScope:
Controller 1 sets the global variable:
function MyCtrl1($scope, $rootScope) {
$rootScope.name = 'anonymous';
}
Controller 2 reads the global variable:
function MyCtrl2($scope, $rootScope) {
$scope.name2 = $rootScope.name;
}
Here is a working jsFiddle: http://jsfiddle.net/natefriedman/3XT3F/1/
In the interest of adding another idea to the wiki pool, but what about AngularJS' value and constant modules? I'm only just starting to use them myself, but it sounds to me like these are probably the best options here.
Note: as of the time of writing, Angular 1.3.7 is the latest stable, I believe these were added in 1.2.0, haven't confirmed this with the changelog though.
Depending on how many you need to define, you might want to create a separate file for them. But I generally define these just before my app's .config() block for easy access. Because these are still effectively modules, you'll need to rely on dependency injection to use them, but they are considered "global" to your app module.
For example:
angular.module('myApp', [])
.value('debug', true)
.constant('ENVIRONMENT', 'development')
.config({...})
Then inside any controller:
angular.module('myApp')
.controller('MainCtrl', function(debug, ENVIRONMENT), {
// here you can access `debug` and `ENVIRONMENT` as straight variables
})
From the initial question is actually sounds like static properties are required here anyway, either as mutable (value) or final (constant). It's more my personal opinion than anything else, but I find placing runtime configuration items on the $rootScope gets too messy, too quickly.
// app.js or break it up into seperate files
// whatever structure is your flavor
angular.module('myApp', [])
.constant('CONFIG', {
'APP_NAME' : 'My Awesome App',
'APP_VERSION' : '0.0.0',
'GOOGLE_ANALYTICS_ID' : '',
'BASE_URL' : '',
'SYSTEM_LANGUAGE' : ''
})
.controller('GlobalVarController', ['$scope', 'CONFIG', function($scope, CONFIG) {
// If you wish to show the CONFIG vars in the console:
console.log(CONFIG);
// And your CONFIG vars in .constant will be passed to the HTML doc with this:
$scope.config = CONFIG;
}]);
In your HTML:
<span ng-controller="GlobalVarController">{{config.APP_NAME}} | v{{config.APP_VERSION}}</span>
localStorage.username = 'blah'
If you're guaranteed to be on a modern browser. Though know your values will all be turned into strings.
Also has the handy benefit of being cached between reloads.
Please correct me if I'm wrong, but when Angular 2.0 is released I do not believe$rootScope will be around. My conjecture is based on the fact that $scope is being removed as well. Obviously controllers, will still exist, just not in the ng-controller fashion.Think of injecting controllers into directives instead. As the release comes imminent, it will be best to use services as global variables if you want an easier time to switch from verison 1.X to 2.0.
You can also use the environment variable $window so that a global variable declare outside a controller can be checked inside a $watch
var initWatch = function($scope,$window){
$scope.$watch(function(scope) { return $window.globalVar },
function(newValue) {
$scope.updateDisplayedVar(newValue);
});
}
Becareful, the digest cycle is longer with these global values, so it is not always real-timed updated. I need to investigate on that digest time with this configuration.
I just found another method by mistake :
What I did was to declare a var db = null above app declaration and then modified it in the app.js then when I accessed it in the controller.js
I was able to access it without any problem.There might be some issues with this method which I'm not aware of but It's a good solution I guess.
Try this, you will not force to inject $rootScope in controller.
app.run(function($rootScope) {
$rootScope.Currency = 'USD';
});
You can only use it in run block because config block will not provide you to use service of $rootScope.
It's actually pretty easy. (If you're using Angular 2+ anyway.)
Simply add
declare var myGlobalVarName;
Somewhere in the top of your component file (such as after the "import" statements), and you'll be able to access "myGlobalVarName" anywhere inside your component.
You can also do something like this ..
function MyCtrl1($scope) {
$rootScope.$root.name = 'anonymous';
}
function MyCtrl2($scope) {
var name = $rootScope.$root.name;
}

Resources