I wanted to change a scope variable after the page has been initialized.
I have a angular application with following code:
$scope.names = ['Jack'];
append_name = function(){$scope.names.push('Bob');}
setTimeout(append_name, 2000);
Tough I don't see the value change after the specified delay.
Here is the plunker http://plnkr.co/edit/FBa2fwb7js8pRNENNJof
Short answer: use the built-in $timeout service instead of setTimeout:
http://plnkr.co/edit/nh1jEhocRpXtD0rUTh4k?p=preview
Long answer is here: https://stackoverflow.com/a/9693933/1418796
If you create code outside of angular you need to tell that you change something with $apply
$scope.names = ['Jack'];
append_name = function() {
$scope.$apply(function() {
$scope.names.push('Bob');
});
};
setTimeout(append_name, 2000);
You can create handy higher order function to wrap your functions with $apply:
function ngWrap($scope, fn) {
return function() {
var args = [].slice.call(arguments);
if ($scope.$$phase) {
fn.apply(null, args);
} else {
return $scope.$apply(function() {
fn.apply(null, args);
});
}
};
}
this can be used like:
setTimeout(ngWrap($scope, function() {
$scope.names.push('Bob');
}), 2000);
Also angular have $timeout that will handle this.
Related
I'm very new to angularjs and I want to establish a connection to my server and dynamically show the result to user. so far I've tried:
angular.module('myApp.controllers', []).controller('socketsController', function($scope) {
$scope.socket = {
client: null,
stomp: null
};
$scope.reconnect = function() {
setTimeout($scope.initSockets, 10000);
};
$scope.notify = function(message) {
$scope.result = message.body;
};
$scope.initSockets = function() {
$scope.socket.client = new SockJS('/resources');
$scope.socket.stomp = Stomp.over($scope.socket.client);
$scope.socket.stomp.connect({}, function() {
$scope.socket.stomp.subscribe('/user/topic/messages', $scope.notify);
});
$scope.socket.client.onclose = $scope.reconnect;
};
$scope.initSockets();
});
But when I use {{result}} nothing appears.
UPDATE
The server response is totally right with console.log(message.body).
I guess, the callback is not taking the scope properly. Try call $scope.$apply(); after you attach the message.body to result :
$scope.notify = function(message) {
$scope.result = message.body;
$scope.$apply();
};
$scope.$apply() triggers an angular digest cycle whcih will update all the bindings..
Call it inside a timeout function but inject $timeout first it will call the digest cycle and update the value.
$timeout(function(){
$scope.result = message.body;});
How to call a function asynchronously inside an Angular controller? I'v read about promises, but this in not a http call so i can't use that.
Is there any solution to resolve these kind of problems?
I would like to display a spiner/loader until the called function finishing it's job.
//Controller JS
$scope.myFunctionCallOnClick = function()
{
if($scope.type=='TYPE1')
{
$scope.showSpiner();
//This function has called syncronously, and i can't display my spiner
Utils.doStuff(words,$scope);
$scope.hideSpiner();
}
else
{
Utils.doStuff2(words,$scope);
}
}
//Utils.js - this is my helper factory
angular.module('app').factory('Utils', function() {
var factory = {};
..
..
factory.doStuff = function()
{
var iteration = 10000000;
whilte(iteration > 0)
{
console.log(iteration);
}
}
..
..
return factory;
});
Use $timeout service to let the browser do some of its work (show your spinner in this case), and call doStuff from within the $timeout callback.
$scope.myFunctionCallOnClick = function () {
if ($scope.type == 'TYPE1') {
$scope.showSpiner();
$timeout(function () {
Utils.doStuff(words, $scope);
$scope.hideSpiner();
});
}
else {
Utils.doStuff2(words, $scope);
}
}
you can use $timeout to call a function asynchronously
$timeout(function(){
/// function logic
}, 0);
similarly you can even use.. $evalAsync
read more about them here
I have a controller with a watch that uses debounce from lodash to delay filtering a list by 500ms.
$scope.$watch('filter.keywords', _.debounce(function () {
$scope.$apply(function () {
$scope.filtered = _.where(list, filter);
});
}, 500));
I am trying to write a Jasmine test that simulates entering filter keywords that are not found followed by keywords that are found.
My initial attempt was to use $digest after assigning a new value to keywords, which I assume didn't work because of the debounce.
it('should filter list by reference', function () {
expect(scope.filtered).toContain(item);
scope.filter.keywords = 'rubbish';
scope.$digest();
expect(scope.filtered).not.toContain(item);
scope.filter.keywords = 'test';
scope.$digest();
expect(scope.filtered).toContain(item);
});
So I tried using $timeout, but that doesn't work either.
it('should filter list by reference', function () {
expect(scope.filtered).toContain(item);
$timeout(function() {
scope.filter.keywords = 'rubbish';
});
$timeout.flush();
expect(scope.filtered).not.toContain(item);
$timeout(function() {
scope.filter.keywords = 'test';
});
$timeout.flush();
expect(scope.filtered).toContain(item);
});
I have also tried giving $timeout a value greater than the 500ms set on debounce.
How have others solved this problem?
EDIT: I've found a solution which was to wrap the expectation in a $timeout function then call $apply on the scope.
it('should filter list by reference', function () {
expect(scope.filtered).toContain(item);
scope.filter.keywords = 'rubbish';
$timeout(function() {
expect(scope.filtered).not.toContain(item);
});
scope.$apply();
scope.filter.keywords = 'test';
$timeout(function() {
expect(scope.filtered).toContain(item);
});
scope.$apply();
});
I'm still interested to know whether this approach is best though.
This is a bad approach. You should use an angular-specific debounce such as this that uses $timeout instead of setTimeout. That way, you can do
$timeout.flush();
expect(scope.filtered).toContain(item);
and the spec will pass as expected.
I've used this:
beforeEach(function() {
...
spyOn(_, 'debounce').and.callFake(function (fn) {
return function () {
//stack the function (fn) code out of the current thread execution
//this would prevent $apply to be invoked inside the $digest
$timeout(fn);
};
});
});
function digest() {
//let the $watch be invoked
scope.$digest();
//now run the debounced function
$timeout.flush();
}
it('the test', function() {
scope.filter.keywords = ...;
digest();
expect(...);
});
Hope it helps
Using the spyOn to replace the _.debounce, check this link. http://gotoanswer.stanford.edu/?q=Jasmine+test+does+not+see+AngularJS+module
How can call a method defined in child scope from its parent scope?
function ParentCntl() {
// I want to call the $scope.get here
}
function ChildCntl($scope) {
$scope.get = function() {
return "LOL";
}
}
http://jsfiddle.net/wUPdW/
You can use $broadcast from the parent to a child:
function ParentCntl($scope) {
$scope.msg = "";
$scope.get = function(){
$scope.$broadcast ('someEvent');
return $scope.msg;
}
}
function ChildCntl($scope) {
$scope.$on('someEvent', function(e) {
$scope.$parent.msg = $scope.get();
});
$scope.get = function(){
return "LOL";
}
}
Working fiddle: http://jsfiddle.net/wUPdW/2/
UPDATE: There is another version, less coupled and more testable:
function ParentCntl($scope) {
$scope.msg = "";
$scope.get = function(){
$scope.$broadcast ('someEvent');
return $scope.msg;
}
$scope.$on('pingBack', function(e,data) {
$scope.msg = data;
});
}
function ChildCntl($scope) {
$scope.$on('someEvent', function(e) {
$scope.$emit("pingBack", $scope.get());
});
$scope.get = function(){
return "LOL";
}
}
Fiddle: http://jsfiddle.net/uypo360u/
Let me suggest another solution:
var app = angular.module("myNoteApp", []);
app.controller("ParentCntl", function($scope) {
$scope.obj = {};
});
app.controller("ChildCntl", function($scope) {
$scope.obj.get = function() {
return "LOL";
};
});
Less code and using prototypical inheritance.
Plunk
Register the child's function on the parent when the child is initialising. I used "as" notation for clarity in the template.
TEMPLATE
<div ng-controller="ParentCntl as p">
<div ng-controller="ChildCntl as c" ng-init="p.init(c.get)"></div>
</div>
CONTROLLERS
...
function ParentCntl() {
var p = this;
p.init = function(fnToRegister) {
p.childGet = fnToRegister;
};
// call p.childGet when you want
}
function ChildCntl() {
var c = this;
c.get = function() {
return "LOL";
};
}
"But", you say, "ng-init isn't supposed to be used this way!". Well, yes, but
that documentation doesn't explain why not, and
I don't believe the documentation authors considered ALL possible use cases for it.
I say this is a good use for it. If you want to downvote me, please comment with reasons! :)
I like this approach because it keeps the components more modular. The only bindings are in the template, and means that
the child Controller doesn't have to know anything about which object to add its function to (as in #canttouchit's answer)
the parent control can be used with any other child control which has a get function
doesn't require broadcasting, which will get very ugly in a big app unless you tightly control the event namespace
This approach more closely approaches Tero's idea of modularising with directives (note that in his modularised example, contestants is passed from parent to "child" directive IN THE TEMPLATE).
Indeed another solution might be to consider implementing the ChildCntl as a directive and use the & binding to register the init method.
You can make child object.
var app = angular.module("myApp", []);
app.controller("ParentCntl", function($scope) {
$scope.child= {};
$scope.get = function(){
return $scope.child.get(); // you can call it. it will return 'LOL'
}
// or you can call it directly like $scope.child.get() once it loaded.
});
app.controller("ChildCntl", function($scope) {
$scope.obj.get = function() {
return "LOL";
};
});
Here child is proving destination of get method.
I'm attempting to set up a watch in AngularJS and I'm clearly doing something wrong, but I can't quite figure it out. The watch is firing on the immediate page load, but when I change the watched value it's not firing. For the record, I've also set up the watch on an anonymous function to return the watched variable, but I have the exact same results.
I've rigged up a minimal example below, doing everything in the controller. If it makes a difference, my actual code is hooked up in directives, but both are failing in the same way. I feel like there's got to be something basic I'm missing, but I just don't see it.
HTML:
<div ng-app="testApp">
<div ng-controller="testCtrl">
</div>
</div>
JS:
var app = angular.module('testApp', []);
function testCtrl($scope) {
$scope.hello = 0;
var t = setTimeout( function() {
$scope.hello++;
console.log($scope.hello);
}, 5000);
$scope.$watch('hello', function() { console.log('watch!'); });
}
The timeout works, hello increments, but the watch doesn't fire.
Demo at http://jsfiddle.net/pvYSu/
It's because you update the value without Angular knowing.
You should use the $timeout service instead of setTimeout, and you won't need to worry about that problem.
function testCtrl($scope, $timeout) {
$scope.hello = 0;
var t = $timeout( function() {
$scope.hello++;
console.log($scope.hello);
}, 5000);
$scope.$watch('hello', function() { console.log('watch!'); });
}
Or you could call $scope.$apply(); to force angular to recheck the values and call watches if necessary.
var t = setTimeout( function() {
$scope.hello++;
console.log($scope.hello);
$scope.$apply();
}, 5000);
You can use without $interval and $timeout
$scope.$watch(function() {
return variableToWatch;
}, function(newVal, oldVal) {
if (newVal !== oldVal) {
//custom logic goes here......
}
}, true);
It can also happen because the div is not registered with the controller. Add a controller to your div as follows and your watch should work:
<div ng-controller="myController">