Angular digest loop - angularjs

I have a problem with my app because it creates infinite loop cycle when trying to generate form. My form is generated based on json (example in code). Problem occurs only when i go to another step and go back to form. I have no idea what causes that problem because it only happens when state is loaded for the second time. Is it possible that angular keep watchers that was created in previous state and it just overlaps each other?
Whole idea about this app is that you have form definition in "form" section, then there is a "schema" which define each model element and at the end there is a "model" which stores all variables.
<schema-form
data-name = "theemployeeform"
data-schema = "$ctrl.json.all_fields.schema"
data-form = "$ctrl.json.all_fields.form"
data-model = "$ctrl.json.model">
</schema-form>
To see error please open chrome inspector and:
Home -> Form -> Home -> Form
Error: [$rootScope:infdig]
App example on Plunker https://plnkr.co/edit/nkdzwLuEO0RauZT1jpOJ?p=preview

See if you have created a watch of your own on the property that are on scope, and trying to modify the value of that property every time when the watch gets executes like below
var app = angular.module('test', [])
app.controller('ctrl', function($scope) {
$scope.val = 100;
$scope.$watch('val',function(n,o){
$scope.val= $scope.val+1;
})
});
The above example creates a infinite digest loop. Normally digest loops gets executed for a maximum of 10 times and minimum of 2 times just to check if any property has been changed in the watch or not...basically it does a dirty checking

Problem was in ng-include directive which was dependent on variable from controller. Because when state changed ng-include couldn't find template it tried to load index.html and that caused a problem. Solution for that is to compile template manually inside component. Because component doesn't have compile as a directive has we need to change $postLink() and put compilation in it.
https://plnkr.co/edit/N2kUSCljMxaWTS7bZ2uC?p=preview
$element.contents().remove();
$templateRequest(templateUrl).then(function(html){
var template = angular.element(html);
var compiledContents = $compile(template);
compiledContents($scope, function(clone){
$element.append(clone);
});
});
This solves a problem.

Related

AngularJS: After $compile I get an infdig error on $rootscope

I am simply trying to add a dynamic element to the end of a list but I want that dynamic element to be evaluated by Angular so that when it is clicked it can register an ng-click event. This works as expected but when I click it a second time I get an infdig error.
This may be important, not sure, the list it gets appended to is a list of links, they go to different pages in the application (using angularUI router) and those "pages" are in the same view so the page doesn't change just the content on the page. All of those "pages" use the same controller and template.
feedbacklink = angular.element('<li><a ng-click="test()" id="reviewPageLink">Customer Feedback</a></li>');
$('#section_2 ul').first().append(feedbacklink);
$compile(feedbacklink)($scope);
The ng-click triggers (simplified to show the issue):
$scope.test = function () {
alert('test');
};
It seems to me like the $compile is being called again but that doesn't seem to be the case when I put a console.log immediately following the compile, it only shows once. Is there a way to assure that compile only runs once, or is that not even the problem?

How can I execute code after $digest is complete?

I am having a sortable list that gets populated by data in my Angular Controller. This list also contains an extra element containing some controls that can also be dragged
What I want do do, is make sure that the extra element stays in place after the $digest cycle is run, and I made a directive just for that case.
App.directive('ngIgnore', ['$timeout',function($timeout){
return {
link: function(scope, element){
scope.$watch(function(){
// Keep track of the original position of the element...
var el = element[0];
var siblings = Array.prototype.slice.call(el.parentNode.children);
var parent = el.parentNode;
var index = siblings.indexOf(el);
$timeout(function(){
// After the digest is complete, place it to it's previous position if it exists
// Otherwise angular places it to it's original position
var item;
if(index in parent.children)
item = parent.children[index];
if(!!item){
parent.insertBefore(el, item);
}
});
});
}
}
}]);
It worked, but not as I wanted it to... As you can see in this example shuffling the list does not move the ignored element, but the problem is that the $watch function gets executed infinitely since the $timeout triggers another $digest cycle... I tried changing the $timeout with setTimeout but no luck...
Is there any Angulary way to run code right after $digest is executed? Or is my entire logic wrong here?
Another (highly recommended) solution is to stop thinking in DOM manipulations.
Common problem with developers who start writing AngularJS code is tend to do DOM manipulations as the result of some event. Its common in jQuery:
$('#someid').click(function() { this.toggleClass('clicked') })
In AngularJS you should design your View to visualize your Model state (that should be in $scope). So
<div ng-click="clicked = !clicked" ng-class="{clicked: clicked}">I was clicked</div>
Same logic should be applied when designing components. In a HTML code you should put all visual logic - hide some elements using ng-show/ng-if, ng-switch. Add/remove classes using ng-class etc. So you define all possible model states.
Then by just changing model state you will get your view automatically updated reflecting current model state.
Same goes for repeated elements: you can repeat on some collection and then, depending on what element is present, you define how it would look. Keep in mind, that in ng-repeat each element will have own child (so mostly independed) Scope, so you can define some per-element manipulations.
See also this great answer: "Thinking in AngularJS" if I have a jQuery background?
You can try to use Scope.evalAsync Scope.evalAsync:
it will execute after the function that scheduled the evaluation
(preferably before DOM rendering).

Assuring $digest Is called Following non Angular Code Execution

I have a UI component which has a $watch callback on its width (the reason is not relevant for this post).
The problem is that in some cases:
The width is changed from a non angular context ->
There is no $digest cycle ->
My $watch callback is not called.
Eventhough my application is a full angular application there are still cases in which code is executed in non angular context. For example:
JQuery calles window.setTimeout - so even if my code called JQuery from within angular context the timeout callback is called in non angular context and my $watch callback will not be executed afterwards.
By the way, even angular themselves call window.setTimeout in their AnimatorService...
So my question is:
How can I make sure a $digest cycle is always performed after any code is executed? (even when the code is a 3rd party code...)
I thought about overriding the original window.setTimeout method but:
It feels a bit ugly and dangerous.
I'm afraid it won't cover all use cases.
Adding a plunker.
The plunker sample contains:
An element which can be hidden using JQuery fadeOut method.
A button which executes the fadeOut call for hiding the element.
A text showing the element display status (Shown!!! or Hidden!!!). This text is updated by $watching on the element display property.
A button which does nothing but to initiate some angular code so that a $digest cycle is called.
Flow:
Click the Fade Out button -> the element will be hidden but the status text will remain Shown!!!.
You can wait forever now - or:
Click the Do Nothing button -> suddenly the text will change.
Why?
When clicking the Fade Out button JQuery.fadeOut calls the window.setTimeout method. After that my $watch callback is called but the element is still not hidden.
The element is only hidden after the timeout callback is called - but then there is no $digest cycle (and i have no way that i know of to trigger one).
Only on the next time an angular code will run my $watch function will be called again and the status will be updated.
AngularJS provides a special $apply method on the scope object to allow you to execute code from outside the AngularJS framework.
The $apply function will execute your code in the correct context and apply a $digest cycle afterwards so you don't have to deal with that yourself.
To implement it in your code, you can:
// Get element
var $element = $('#yourElement');
// Get scope
var scope = angular.element($element).scope();
// Execute your code
scope.$apply(function(scope){
// Your logic here
// All watchers in the scope will be triggered
});
(The scenario above can change depending on your actual application).
You can read more about the $apply method of the scope object right here: http://docs.angularjs.org/api/ng.$rootScope.Scope
Hope that helps!
Looking at your plunker, you could add a callback on the call to animate to manually trigger an update to the Angular scope once the animation is complete:
Before:
$scope.fadeOut = function() {
animatedElement.fadeOut('slow');
};
var getDisplay = function() {
return animatedElement.css('display');
};
$scope.$watch(getDisplay, function(display) {
console.log('$watch called with display = `' + display + '`');
$scope.display = display === 'none' ? 'Hidden!!!' : 'Shown!!!';
});
After:
$scope.fadeOut = function() {
animatedElement.fadeOut('slow', function() { $scope.$digest(); });
};
This would cause your watch on getDisplay to be called when the animation is complete, by which time it will return the correct value.

How do I access the $scope variable in browser's console using AngularJS?

I would like to access my $scope variable in Chrome's JavaScript console. How do I do that?
I can neither see $scope nor the name of my module myapp in the console as variables.
Pick an element in the HTML panel of the developer tools and type this in the console:
angular.element($0).scope()
In WebKit and Firefox, $0 is a reference to the selected DOM node in the elements tab, so by doing this you get the selected DOM node scope printed out in the console.
You can also target the scope by element ID, like so:
angular.element(document.getElementById('yourElementId')).scope()
Addons/Extensions
There are some very useful Chrome extensions that you might want to check out:
Batarang. This has been around for a while.
ng-inspector. This is the newest one, and as the name suggests, it allows you to inspect your application's scopes.
Playing with jsFiddle
When working with jsfiddle you can open the fiddle in show mode by adding /show at the end of the URL. When running like this you have access to the angular global. You can try it here:
http://jsfiddle.net/jaimem/Yatbt/show
jQuery Lite
If you load jQuery before AngularJS, angular.element can be passed a jQuery selector. So you could inspect the scope of a controller with
angular.element('[ng-controller=ctrl]').scope()
Of a button
angular.element('button:eq(1)').scope()
... and so on.
You might actually want to use a global function to make it easier:
window.SC = function(selector){
return angular.element(selector).scope();
};
Now you could do this
SC('button:eq(10)')
SC('button:eq(10)').row // -> value of scope.row
Check here: http://jsfiddle.net/jaimem/DvRaR/1/show/
To improve on jm's answer...
// Access whole scope
angular.element(myDomElement).scope();
// Access and change variable in scope
angular.element(myDomElement).scope().myVar = 5;
angular.element(myDomElement).scope().myArray.push(newItem);
// Update page to reflect changed variables
angular.element(myDomElement).scope().$apply();
Or if you're using jQuery, this does the same thing...
$('#elementId').scope();
$('#elementId').scope().$apply();
Another easy way to access a DOM element from the console (as jm mentioned) is to click on it in the 'elements' tab, and it automatically gets stored as $0.
angular.element($0).scope();
If you have installed Batarang
Then you can just write:
$scope
when you have the element selected in the elements view in chrome.
Ref - https://github.com/angular/angularjs-batarang#console
This is a way of getting at scope without Batarang, you can do:
var scope = angular.element('#selectorId').scope();
Or if you want to find your scope by controller name, do this:
var scope = angular.element('[ng-controller=myController]').scope();
After you make changes to your model, you'll need to apply the changes to the DOM by calling:
scope.$apply();
Somewhere in your controller (often the last line is a good place), put
console.log($scope);
If you want to see an inner/implicit scope, say inside an ng-repeat, something like this will work.
<li ng-repeat="item in items">
...
<a ng-click="showScope($event)">show scope</a>
</li>
Then in your controller
function MyCtrl($scope) {
...
$scope.showScope = function(e) {
console.log(angular.element(e.srcElement).scope());
}
}
Note that above we define the showScope() function in the parent scope, but that's okay... the child/inner/implicit scope can access that function, which then prints out the scope based on the event, and hence the scope associated with the element that fired the event.
#jm-'s suggestion also works, but I don't think it works inside a jsFiddle. I get this error on jsFiddle inside Chrome:
> angular.element($0).scope()
ReferenceError: angular is not defined
One caveat to many of these answers: if you alias your controller your scope objects will be in an object within the returned object from scope().
For example, if your controller directive is created like so:
<div ng-controller="FormController as frm">
then to access a startDate property of your controller, you would call angular.element($0).scope().frm.startDate
To add and enhance the other answers, in the console, enter $($0) to get the element. If it's an Angularjs application, a jQuery lite version is loaded by default.
If you are not using jQuery, you can use angular.element($0) as in:
angular.element($0).scope()
To check if you have jQuery and the version, run this command in the console:
$.fn.jquery
If you have inspected an element, the currently selected element is available via the command line API reference $0. Both Firebug and Chrome have this reference.
However, the Chrome developer tools will make available the last five elements (or heap objects) selected through the properties named $0, $1, $2, $3, $4 using these references. The most recently selected element or object can be referenced as $0, the second most recent as $1 and so on.
Here is the Command Line API reference for Firebug that lists it's references.
$($0).scope() will return the scope associated with the element. You can see its properties right away.
Some other things that you can use are:
View an elements parent scope:
$($0).scope().$parent.
You can chain this too:
$($0).scope().$parent.$parent
You can look at the root scope:
$($0).scope().$root
If you highlighted a directive with isolate scope, you can look at it with:
$($0).isolateScope()
See Tips and Tricks for Debugging Unfamiliar Angularjs Code for more details and examples.
I agree the best is Batarang with it's $scope after selecting an object (it's the same as angular.element($0).scope() or even shorter with jQuery: $($0).scope() (my favorite))
Also, if like me you have you main scope on the body element, a $('body').scope() works fine.
Just assign $scope as a global variable. Problem solved.
app.controller('myCtrl', ['$scope', '$http', function($scope, $http) {
window.$scope = $scope;
}
We actually need $scope more often in development than in production.
Mentioned already by #JasonGoemaat but adding it as a suitable answer to this question.
Inspect the element, then use this in the console
s = $($0).scope()
// `s` is the scope object if it exists
You can first select an element from the DOM that's within the scope you want to inspect:
Then you can view the scope object by querying the following in the console:
angular.element($0).scope()
You can query any property on the scope, e.g.:
angular.element($0).scope().widgets
Or you can inspect the controller attached to the scope:
angular.element($0).scope().$myControllerName
(Another option that can work is to put a breakpoint in your code. If the $scope is currently in the current 'plain old JavaScript' scope, then you can inspect the value of $scope in the console.)
I've used angular.element($(".ng-scope")).scope(); in the past and it works great. Only good if you have only one app scope on the page, or you can do something like:
angular.element($("div[ng-controller=controllerName]")).scope(); or angular.element(document.getElementsByClassName("ng-scope")).scope();
I usually use jQuery data() function for that:
$($0).data().$scope
The $0 is currently selected item in chrome DOM inspector.
$1, $2 .. and so on are previously selected items.
Say you want to access the scope of the element like
<div ng-controller="hw"></div>
You could use the following in the console:
angular.element(document.querySelector('[ng-controller=hw]')).scope();
This will give you the scope at that element.
At the Chrome's console :
1. Select the **Elements** tab
2. Select the element of your angular's scope. For instance, click on an element <ui-view>, or <div>, or etc.
3. Type the command **angular.element($0).scope()** with following variable in the angular's scope
Example
angular.element($0).scope().a
angular.element($0).scope().b
Chrome's console
This requires jQuery to be installed as well, but works perfectly for a dev environment. It looks through each element to get the instances of the scopes then returns them labelled with there controller names. Its also removing any property start with a $ which is what angularjs generally uses for its configuration.
let controllers = (extensive = false) => {
let result = {};
$('*').each((i, e) => {
let scope = angular.element(e).scope();
if(Object.prototype.toString.call(scope) === '[object Object]' && e.hasAttribute('ng-controller')) {
let slimScope = {};
for(let key in scope) {
if(key.indexOf('$') !== 0 && key !== 'constructor' || extensive) {
slimScope[key] = scope[key];
}
}
result[$(e).attr('ng-controller')] = slimScope;
}
});
return result;
}
in angular we get jquery element by angular.element()....
lets c...
angular.element().scope();
example:
<div id=""></div>
For only debugging purposes I put this to the start of the controller.
window.scope = $scope;
$scope.today = new Date();
And this is how I use it.
then delete it when I am done debugging.
Also, we can access the scope by name of HTML element like this:
angular.element(document.getElementsByName('onboardingForm')[0]).scope()
Just define a JavaScript variable outside the scope and assign it to your scope in your controller:
var myScope;
...
app.controller('myController', function ($scope,log) {
myScope = $scope;
...
That's it! It should work in all browsers (tested at least in Chrome and Mozilla).
It is working, and I'm using this method.

AngularJS better way to manage partials and controller data

I've been using directives in AngularJS which build a HTML element with data fetched from the $scope of the controller. I have my controller set a $scope.ready=true variable when it has fetched it's JSON data from the server. This way the directive won't have to build the page over and over each time data is fetched.
Here is the order of events that occur:
The controller page loads a route and fires the controller function.
The page scans the directives and this particular directive is fired.
The directive builds the element and evaluates its expressions and goes forward, but when the directive link function is fired, it waits for the controller to be "ready".
When ready, an inner function is fired which then continues building the partial.
This works, but the code is messy. My question is that is there an easier way to do this? Can I abstract my code so that it gets fired after my controller fires an event? Instead of having to make this onReady inner method.
Here's what it looks like (its works, but it's messy hard to test):
angular.module('App', []).directive('someDirective',function() {
return {
link : function($scope, element, attrs) {
var onReady = function() {
//now lets do the normal stuff
};
var readyKey = 'ready';
if($scope[readyKey] != true) {
$scope.$watch(readyKey, function() {
if($scope[readyKey] == true) {
onReady();
}
});
}
else {
onReady();
}
}
};
});
You could use $scope.$emit in your controller and $rootScope.on("bradcastEventName",...); in your directive. The good point is that directive is decoupled and you can pull it out from project any time. You can reuse same pattern for all directives and other "running" components of your app to respond to this event.
There are two issues that I have discovered:
Having any XHR requests fire in the background will not prevent the template from loading.
There is a difference between having the data be applied to the $scope variable and actually having that data be applied to the bindings of the page (when the $scope is digested). So if you set your data to the scope and then fire an event to inform the partial that the scope is ready then this won't ensure that the data binding for that partial is ready.
So to get around this, then the best solution is to:
Use this plugin to manage the event handling between the controller and any directives below:
https://github.com/yearofmoo/AngularJS-Scope.onReady
Do not put any data into your directive template HTML that you expect the JavaScript function to pickup and use. So if for example you have a link that looks like this:
<a data-user-id="{{ user_id }}" href="/path/to/:user_id/page">My Page</a>
Then the problem is that the directive will have to prepare the :user_id value from the data-user-id attribute, get the href value and replace the data. This means that the directive will have to continuously check the data-user-id attribute to see if it's there (by checking the attrs hash every few moments).
Instead, place a different scope variable directly into the URL
My Page
And then place this in your directive:
$scope.whenReady(function() {
$scope.directive_user_id = $scope.user_id;
});

Resources