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

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?

Related

Angular digest loop

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.

init ng-controller when content is loaded dynamically in angular

I'm learning Angular I tried to init a controller after create a new content by ajax (with jQuery, maybe it's not a good idea but just I'm starting to learning step by step.)
this is my code.
angular.module('app').controller('TestController',function($scope){
$scope.products = [{
'code': 'A-1',
'name': 'Product 01'
}];
$scope.clickTest = function(){
alert('test');
}
});
$.ajax({..})
.done(function(html){
angular.element(document).injector().invoke(function($compile){
$('#content').html(html);
var scope = $('#content').scope();
$compile($('#content').contents())(scope);
});
});
In my html I use ng-repeat but not load nothing, however when I click in
ng-click="clickTest" it works! and ng-repeat is loaded. this is my problem I need that the ng-repeat load when I load for first time.
Thanks.
Sorry for my bad english.
with jQuery, maybe it's not a good idea
Yes spot on
Now getting into your issue:- When you click on element with ng-click on the html, it works because it then runs a digest cycle and refreshes the DOM and your repeat renders. $Compile has already instantiated the controller and methods are available and attached to DOM. But there is one digest cycle that runs in angular after controller initialization, which renders data in DOM, and that does not happen in your case since you are outside angular.
You could do a scope.$digest() after compile to make sure element is rendered and digest cycle is run.
Also probably one more better thing would be to wrap it inside angularRootElement.ready function, just to make sure before the injector is accessed the element is ready, in your case enclosing it inside ajax callback saves you (the time for it to be ready) but better to have it.
Try:-
$.ajax({..})
.done(function(html){
var rootElement = angular.element(document);
rootElement.ready(function(){
rootElement.injector().invoke(function($compile){
var $content = $('#content'),
scope = $content.scope();
$content.html(html);
$compile($content.contents())(scope);
scope.$digest();
});
});
});
Sample Demo
Demo - Controller on dynamic html
Instead of getting html from the server you could create partials and templates, use routing , use angular ajax wrapper with $http etc... Well i would not suggest doing this method though - however based on your question you understand that already. There was a similar question that i answered last day
If you are not looking for a better way to do what you are doing, ignore the following.
The beauty of using frameworks like AngularJS and KnockoutJS (or many more) is that we don't have to worry about the timing of when the data loads. You just set up the bindings between the controller and the UI and once the data is loaded into the respective properties, these frameworks will take care of updating the UI for you.
I am not sure why you are trying to set up the UI using JQuery and waiting for the data to loaded first to do so but normally you will not have to do all that.
You can create a UI with ng-repeat and click bindings ,etc and make an ajax call to get the data from anywhere. Once the data is loaded, in the callback, just push the necessary data into the collection bound to the ng-repeat directive. That is all you will have to do.

angularjs loading directive works part of the time

In order to standardize loading warnings, and avoid changes in a page when http ajax requests come back and change $scope item values, I set up a simple directive to wrap an element and add "loading" to it.
<loading><div>My content here</div></loading>
This is converted into:
<div><div ng-hide="!ready"><div>My content here</div></div> <div ng-hide="ready">Loading...</div></div>
Pretty straightforward. Then in my controller, I just need to set $scope.ready = true; when everything ajax-y is loaded.
Here is my simple directive:
.directive('loading',function () {
return {
restrict: 'E',
template: '<div><div ng-transclude ng-hide="!ready"></div><div ng-hide="ready">Loading</div></div>',
transclude:true,
replace:true
};
})
Problem is that it works consistently for one page and not the other, even though their layouts are identical.
When examining with firebug, it appears that the one that doesn't work has class="ng-hide" on the first element, as if $scope.ready was never set to true. Of course, both Firebug breakpoints and console logs show that it was set to true.
My suspicion is a scope issue, or possibly how Angular is applying the value of scope changes, but not sure?
UPDATE:
I added messages for when the value of the element gets changed inside the directive, and when it is getting changed in the controller:
// directive
link: function ($scope) {
$scope.$watch('ready',function (newval,oldval) {
console.log("ready changed from "+oldval+" to "+newval);
});
}
// controller
console.log("changing $scope.ready to true");
$scope.ready = true;
For the one that works, I get the following messages:
setting $scope.ready to true
ready changed from undefined to true
For the one that did not, I get the following messages:
setting $scope.ready to true
The directive is not being notified about the change. Is this a scope issue?
HTML:
I really stripped down the html, still managed to recreate the problem:
<!-- THIS ONE HAS THE PROBLEM -->
<loading>
<h3>{{item.name}} Second Page</h3>
</loading>
<!-- THIS ONE IS JUST FINE -->
<loading>
<h3>{{item.name}}</h3>
</loading>
Leads me to believe it must be something in the controller? The routes are basically identical, except for their loading different template html files and different controller names.
UPDATE:
scopes are identical. I had the console messages spit out $scope.$id from inside each controller and from inside the link function (and inside the watch, which is not getting called anyways for the bad one). In all cases, all 3 $id are the same (004) for the page that works, and all 3 $id are the same (006) for the page that does not.
ANOTHER:
setting the directive to isolate scope with scope:{ready:'=ready'} (i.e. 2-way binding has the same effect. Using text binding # makes the whole directive not run correctly.
Apparently, it depends entirely on the ID of the item. I load this via URL /items/:item. If the Item is one that I pre-seeded in the database, so probably has an ID like "40", it all works, both for item type 1 and for item type 2. If it is an auto-generated hex ID for a new item created on the fly, like "8474b2486ee6", then it stumbles and doesn't load.
Finally... if I hit the Back button in the browser, for a second it actually shows!

$location.path() updates after the second click?

[And a second question (see below) - rootScope variable set in one controller not visible in a second, sibling controller?]
Possible Duplicate: angularjs $location.path only updates URL on second click - the cause of the problem and the answer does not seem relevant in my, more basic, situation
As per the angularjs docs (https://docs.angularjs.org/guide/$location):
[The $location service]
Maintains synchronization between itself and the browser's URL when the user
...
Clicks on a link in the page.
I understood that $location.path() reflects the current url in the browser, but when I click a link to change the view, exhibits strange behaviour: $location.path() does not 'change' the first time one clicks on a link, and every time thereafter it will change to the link that was clicked the previous time
To see this go here: http://jsfiddle.net/7Ah2W/
I attempted a workaround whereby I must manually set $location.path() using the setter overload.
In doing so, I found another flaw in my understanding of angularjs. I tried setting a variable in the rootScope to reflect the 'current path.' The idea is that views would automatically detect the change in the variable and update. Does not every scope inherit from rootScope?
Here is a jsfiddle
Why is my expectation that $rootScope.currentPath, being changed in 'NavCtrl' and updated in 'CtrlTwo' not being met?
My end goal is to have my navigation bar automatically change when a link in the view is clicked. Similar to https://console.developers.google.com where if you click your project, the navigation to the left changes to API&Auth, settings, etc.
The reason it seems to always be "one behind" is that you're accessing the $location.path() before the actual angular page process can run. Strangely enough if you just add a $timeout with even 0ms delay, it'll work as intended.
$timeout(function () {
$scope.currentPath = $location.path();
}, 0);
jsFiddle example
$rootScope is the global scope, as opposed to the regular $scope which is basically the glue between the controller & view.
For example I set $rootScope.test = 123; in your first controller, and in the second controller I alert that variable, and get the result. jsFiddle $rootScope example. Be careful with $rootScope, it creates globally scoped variables

Error: $apply already in progress

I'm using this http://plnkr.co/edit/sqXVCJ?p=preview on my Angular UI accordion. I've put the directive attribute on the anchor (which is in the template) and I've overriden the template to remove the ng-click="isOpen = !isOpen" and replaced it with a function call "callSubmit". All the accordions have views loaded into them, all of the views have forms as well. The purpose of the callSubmit function is to submit the form which works fine but I get the error above.
I've read through various posts regrding adding the $timeout service (which didnt't work) and adding the safe apply method which gave me recursion and digest errors.
I'm not sure what else I can try, the function works fine, but I just keep getting that error.
!--- button
<button type="submit" class="btn btn-default hidden" id="btnSubmit_step1" ng-click="submitForm()">Trigger validation</button>
!-- function
$scope.callSubmit = function() {
document.getElementById('btnSubmit_step1').click();
};
edit: The reason that I have been triggering the button click from the controller is due to the fact that the form method is in another scope & a different controller.
So if I use broadCast from the rootScope like below. I get the broadcast event to the other controller without issue. The controller that receives the broadcast has the form in it's scope but when I try to execute the function I get nothing, no form submission at all.
$$scope.callSubmit = function() {
//document.getElementById('btnSubmit_step1').click();
$rootScope.$broadcast('someEvent', [1,2,3]);
};
$scope.$on('someEvent', function(event, mass) {
$scope.submitForm();
});
Don't click the button from a controller.
$scope.callSubmit = function() {
$scope.submitForm();
};
The documentation is clear...
Do not use controllers to:
Manipulate DOM — Controllers should contain only business logic.
Putting any presentation logic into Controllers significantly affects
its testability. Angular has databinding for most cases and directives
to encapsulate manual DOM manipulation.
Clicking a button on a page is manipulating the DOM.

Resources