Angularjs watch is not triggering - javascript

I have three elements to this program. First, in the service, I have:
$scope.loaded = MySvc.loaded;
$scope.loaded = false;
Then, in the controller which imports this service, I have a series of introductory things. I need to trigger events in the directives once the controller is done with its asycnhronous work, so in the controller, when that trigger is ready, I do
MySvc.loaded = true;
And then in the directive, which also imports the service, I have,
$scope.loaded = MySvc.loaded;
$scope.$watch('loaded', function (newValue, oldValue) {
The directive triggers when loaded is initialized to 'false', but when I change the value to 'true', nothing happens. The watch simply does not trigger. Why not? How do I fix this?

This is a fine way to do things, and I view it as orthogonal to promises (which are indeed extremely useful). Your problem comes from breaking the linkage you've created with the assignment. Instead try:
$scope.data = MySvc.data;
And append to that, e.g. MySvc.data.loaded = false. This way the data variable is never reassigned, so your linkage between controller and service stays intact.
Then you can either watch data.loaded or watch data as a collection by passing true as the 3rd option to $watch.

As I said in comment, you may have problems with yours scopes overridding the loaded property.
Try using the angular events to solve your problem.
angular.module('test', [])
.controller 'MyController', ($scope, $timeout) ->
$timeout ->
$scope.$broadcast('READY')
, 2000
.directive 'myDirective', ->
scope: {}
template: '<span>{{ value }}</span>'
link: ($scope) ->
$scope.value = "I'm waiting to be ready..."
$scope.$on 'READY', ->
$scope.value = "I'm ready!!"
See this in action (CoffeeScript as it's faster for prototyping but should be clear enough).

You're making this complicated. Use promises instead!!! They're sweet. Promises keep state of their resolution.
.factory('hello_world', function ($q, $timeout) {
var promise = $q.defer;
$timeout(function () {
promise.resolve('hello world');
}, 1000)
return promise.promise;
})
.controller('ctrl', function ($scope, hello_world) {
hello_world.then(function (response) {
console.log("I will only be ran when the async event resolves!");
console.log(response);
});
});
As you can see promises are much better, no watches. No weird gates. Simpler, and sexy. ;)

Related

Modifying scope not working without $scope.$apply

I have a directive for a button, which when clicked displays a loading screen.
angular.module('randomButton', ['Data'])
.directive('randomButton', function(Data) {
return {
scope: '=',
restrict: 'A',
templateUrl: 'templates/components/randomButton.html',
link: function(scope, element, attrs){
element.click(function(){
scope._loading();
});
}
}
});
It does this by calling another function on the scope contained within a different directive:
angular.module('quoteContainer', [])
.directive('quoteContainer', function(Data) {
function quoteController($scope){
$scope._loading = function(){
console.log('loading');
$scope.loadMessage = Data.getRandomText();
$scope.loading = true;
};
}
return {
scope: '=',
restrict: 'A',
templateUrl: 'templates/components/quoteContainer.html',
controller: ['$scope', quoteController],
link: function(scope,element,attrs){
}
}
});
My problem is, for this change to occur, I'm having to call $scope.$apply within the ._loading() function. For example:
$scope._loading = function(){
console.log('loading');
$scope.$apply(function(){
$scope.loadMessage = Data.getRandomText();
$scope.loading = true;
});
};
I understand this is bad practice, and should only be used when interfacing with other frameworks/ajax calls etc. So why does my code refuse to work without $scope.$apply, and how can I make it work without it?
Basically you need to let angular know some way of the change happening, because since it's in an asynchronous event handler, angular's usual mechanisms for noticing changes are not applying to it.
Hence the need to wrap it in $apply, which triggers a digest cycle after your code has run, giving angular a chance to make changes according to the new data. However, the preferred way of doing this is using angular's built-in $timeout service, which effectively does the same (i.e. wrapping your code in an $apply block), but doesn't have problems with the possibility that an other digest cycle might be ongoing when it's triggered.
You can use it the same way as you're currently using $apply:
$timeout(function(){
$scope.loadMessage = Data.getRandomText();
$scope.loading = true;
});
(It can take a second parameter if you actually want to delay the application of the values, but it's not necessary in your case.)

Integrating non-Angular code?

I'm developing a Cordova/PhoneGap app, and I'm using the $cordovaPush plugin (wrapped for PushPlugin) to handle push notifications.
The code looks something like this:
var androidConfig = {
"senderID" : "mysenderID",
"ecb" : "onNotification"
}
$cordovaPush.register(androidConfig).then(function(result) {
console.log('Cordova Push Reg Success');
console.log(result);
}, function(error) {
console.log('Cordova push reg error');
console.log(error);
});
The "ecb" function must be defined with window scope, ie:
window.onNotification = function onNotification(e)...
This function handles incoming events. I'd obviously like to handle incoming events in my angular code - how can I integrate the two so that my onNotification function can access my scope/rootScope variables?
Usually, you'll wrap your 3rd party library in a service or a factory, but in the spirit of answering your particular scenario...
Here's one possibility:
angular.module('myApp').
controller('myController', function($scope, $window) {
$window.onNotification = function() {
$scope.apply(function() {
$scope.myVar = ...updates...
});
};
});
A couple of things to notice:
Try to use $window, not window. It's a good habit to get into as it will help you with testability down the line. Because of the internals of Cordova, you might actually need to use window, but I doubt it.
The function that does all of the work is buried inside of $scope.apply. If you forget to do this, then any variables you update will not be reflected in the view until the digest cycle runs again (if ever).
Although I put my example in a controller, you might put yours inside of a handler. If its an angular handler (ng-click, for example), you might think that because the ng-click has an implicit $apply wrapping the callback, your onNotification function is not called at that time, so you still need to do the $apply, as above.
...seriously... don't forget the apply. :-) When I'm debugging people's code, it's the number one reason why external libraries are not working. We all get bit at least once by this.
Define a kind of a mail controller in body and inside that controller use the $window service.
HTML:
<body ng-controller="MainController">
<!-- other markup .-->
</body>
JS:
yourApp.controller("BaseController", ["$scope", "$window", function($scope, $window) {
$window.onNotification = function(e) {
// Use $scope or any Angular stuff
}
}]);

$animate.removeClass not working without $evalAsync inside directive?

I have created a directive that fade out and fade in the view on model changes.
app.controller('myCtrl', function($scope, $interval) {
$scope.number = 0;
$interval(function() {
$scope.number++;
}, 1000);
});
app.directive('fadeOnChange', function($timeout, $animate) {
return {
restrict: 'E',
//We output the value which will be set between fades
template: '{{theFadedValue.nv}}',
link: function(scope, elem, attr) {
//The scope variable we will watch
var vtw = attr.valueToWatch;
//We add the anim class to set transitions
$animate.addClass(elem, 'anim');
//We watch the value
scope.$watch(vtw, function(nv) {
//we fade out
var promise = $animate.addClass(elem, 'fade-it-out');
//when fade out is done, we set the new value
promise.then(function() {
scope.$evalAsync(function() {
scope.theFadedValue = {"nv": nv};
//we fade it back in
$animate.removeClass(elem, 'fade-it-out');
});
});
})
}
};
});
And here's the view
<div ng-controller="myCtrl">
<h1><fade-on-change value-to-watch="number"></fade-on-change></h1>
</div>
It works perfectly but I'd like to understand why I need to use $apply, $digest, $timeout or $evalAsync to wrap my call to $animate.removeClass for it to work? If I don't, the class simply does not get removed (which caused me a lot of headaches this afternoon).
I red about those four methods and understand how they are different, but the need to use one of those in this case mystifies me.
plunker
Basically async methods are don't run $digest cycle directly.(exceptional case for $http because it internally wraps inside $scope.$apply())
In your case you are waiting for complete promise which async. That's why you could use $timeout(function(){ }) or $evalAsync, This angular service wrap inside $scope.$apply() & $scope.$apply() internally run the digest cycle and all scope variables gets updated.
$evalAsync run after the DOM has been manipulated by Angular, but
before the browser renders
Link here for more information on when to use $scope.$apply()
Hope this will clear your doubts. Thanks.

Why is $timeout is needed for watch to trigger

I have a directive that watches the height of an element. That element is updated by a controller that uses a factory method to get data. Unless I have a $timeout in that factory my watch never gets updated. Curious! Can anyone shed light onto why?
My controller:
$scope.update = function () {
apiService.getLinks(function (response) {
$scope.links = response;
// If I try $scope.$apply() here it says its already in progress, as you'd expect
});
}
quickLinksServices.factory('quickLinksAPIService', function ($http, $timeout) {
quickLinksAPI.getQuickLinks = function (success) {
//without this the watch in the directive doesnt get triggered
$timeout(function () { }, 100);
$http({
method: 'JSON',
url: '/devices/getquicklinkcounts'
}).success(function (response) {
quickLinksAPI.quicklinks = response;
quickLinksAPI.saveQuickLinks();
success(response);
});
}
The directive I'm using is here
Basically angularjs provides $timeout service to trigger function call after specified time, but as I know the use of $timeout is not strictly needed, basically people have habit of writing this I mean they need to trigger watch after specified interval. but in many cases $apply does the trick. The thing you need is $apply(). for your reference please check this
Also many times what happens is angulars $watch executes very faster than you expected so it may send incomplete updates or response In such cases $timeout plays the important role by delaying $watch. You can clear $timeout if $watch is fast enough to your need, which means $timeout is the way to explicitly trigger the $watch, where as $apply can do it by itself. So the use of $timeout or $apply is depends on how your requirement is. Hope this clears you. Good luck.
This is because you are not working with the $http promise in the right way.
Following code is not tested, but shows how you could solve your problem. Note the return $http. Here you return the promise and handle the promise in your code.
$scope.update = function () {
quickLinksServices.quickLinksAPI.getQuickLinks().then(function(data){
$scope.links = data;
}
);
};
quickLinksServices.factory('quickLinksAPIService', function ($http, $timeout) {
quickLinksAPI.getQuickLinks = function (success) { return $http({
method: 'JSON',
url: '/devices/getquicklinkcounts'
});
});
}
If you can't make it work this way because of design decisions, you still have to work with promises ($q). If you want to know more about promises here is a good thread on stackoverflow.

Attaching global functions and data to $rootScope on initialization in AngularJS

I'd like to have a "Global function" called the first time I launch my AngularJS application, or every time I refresh the page.
This function will call my server with $http.get() to get global information necessary to use my application. I need to access $rootScope in this function. After that, and only after this request finished, I'm using app.config and $routeProvider.when() to load the good controller.
app.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/',
{
/**/
});
}]);
I don't want the application do something before this action is finished. So I guess I have to use a "resolve", but I don't really know how to use it.
Any idea?
Thanks!
It's not the best way to solve your given problem, but here is a proposed solution for your question.
Anything inside run(...) will be run on initialization.
angular.module('fooApp').run(['$http', '$rootScope' function($http, $rootScope) {
$http.get(...).success(function(response) {
$rootScope.somedata = response;
});
$rootScope.globalFn = function() {
alert('This function is available in all scopes, and views');
}
}]);
Now an alert can be triggered in all your views, using ng-click="globalFn()".
Be aware that directives using a new isolate scope will not have access to this data if not explicitly inherited: $scope.inheritedGlobalFn = $rootScope.globalFn
As a first step for your solution, I think that you could monitor the $routeChangeStart event that is triggered before every route change (or page refresh in your case).
var app = angular.module('myApp').run(['$rootScope', function($rootScope) {
$rootScope.$on("$routeChangeStart", function (event, next, current) {
if (!$rootScope.myBooleanProperty)) {
$location.path('/');
}
else {
$location.path('/page');
}
});
});
You should have a look at this article about Authentification in a Single Page App. I think you could work something similar.
Please consider this answer:
https://stackoverflow.com/a/27050497/1056679
I've tried to collect all possible methods of resolving dependencies in global scope before actual controllers are executed.

Categories

Resources