AngularJs - Error: 10 $digest() iterations reached. Aborting - javascript

I am trying to create a Metro Tile type grid with Angular, to achieve this i want each of the tiles to be a different colour. So my plan of action was to create a function that would randomly pick a colour inside a loop (using ng-repeat). Here is what i have so far....
<div class={{RandomColourClass()}} ng-repeat="stockRecord in GridStockRecords | filter:searchText">
<div >
<h6>{{stockRecord.ProductGroupName}}</h6>
</div>
</div>
So as you can see i am setting the class name with a function called RandomColourClass, Here is the JS bits
$scope.TileColours = [{colour:'thumbnail tile tile-blue'},{colour:'thumbnail tile tile-green'},{colour:'thumbnail tile tile-red'}];
$scope.RandomColourClass = function(){
var randomColour = $scope.TileColours[Math.floor(Math.random() * $scope.TileColours.length)];
return randomColour.colour.toString();
};
This all works fine and the tiles are of different colours but i keep getting the following error
Error: 10 $digest() iterations reached. Aborting!".
I've had a look at other posts around the issue but i can't figure out what i need to change to get it working!? Any help or direction would be greatly appreciated :)

Angular performs a digest function to update the DOM when your data changes.
During the digest, it recomputes all the values you have bound in the DOM, in this case {{RandomColorClass()}}. If any of them change, it again performs a digest cycle (since some variables may depend on the value of of the changed variable, for example).
It does this repeatedly until two digests in a row result in the same values -- i.e, nothing has changed.
What's happening is that when a digest occurs, your RandomColorClass() function is being called and returns a different value. This triggers an additional digest, where RandomColorClass() again returns a different value, which triggers another digest...
Can you see where this is going? You shouldn't be generating random values in this manner -- instead, generate them in your scope and persist them.
One approach might be, in your scope:
function randomColourClass() { /* ... */ };
$scope.GridStockRecords.forEach(function(record) {
record.colorClass = randomColourClass();
});
and HTML:
<div ng-repeat="stockRecord in GridStockRecords | filter:searchText"
ng-class="stockRecord.colorClass">
<div>
<h6>{{stockRecord.ProductGroupName}}</h6>
</div>
</div>

I had the same problem in IE10 turns ut that the problem was that I was redirecting using window.location.
window.location = "#route/yada";
Changed the code to
$location.path("/route/yada);
And that solved my issues. =D

Answer unrelated to this particular question, but I'm adding it here because it's on the Google front page when you search for the error message and it took me a bit until I figured it out:
I had something like this in my view:
<custom-tag data="[1,2,3]"/>
And the controller of the custom tag had a watcher set up on $scope.data. This caused AngularJS to barf because every time it re-checked the value of data it got a new object from the view (remember, the array is an object) so it never finished digesting it properly.

I encountered this error when I mistyped ng-class="submit()" instead of ng-click="submit()". I can't imagine anyone else making such a silly error, but for the record, this is another way to create 10 $digest() iterations reached aborting!

Related

Dynamically adding step to mat-stepper and navigating to it throws out of bounds error

I am working with a simple mat-stepper where steps are generated from an array using *ngFor.
I'd like to dynamically add a step to the display after the stepper has been rendered and then immediately navigate to it programmatically.
Adding the steps dynamically works fine - the navigating part is the issue as it leads to an exception: "Error: cdkStepper: Cannot assign out-of-bounds value to selectedIndex" even though the assigned index should be in bounds based on the steps having been passed to the components.
Simplified example to illustrate the issue (the timeout is just a placeholder for async behaviour that leads to the addition of extra steps).
steps = ['step1', 'step2'];
selectedIndex = 1;
ngOnInit() {
setTimeout(() => {
this.steps.push('step3');
this.selectedIndex = 2;
});
}
<mat-stepper [selectedIndex]="selectedIndex">
<mat-step *ngFor="let step of steps">
{{step}}
</mat-step>
</mat-stepper>
Any ideas on how we might get around this?
Full Example: https://stackblitz.com/edit/angular-cdafd9?file=app%2Fstepper-overview-example.ts
It seems like the stepper needs a bit to initialize. If you add the step before setTimeout it works just fine.

probably another closure-loop issue

I am importing a set of notes into my webpage, this is to read a JSON file locally in a loop and append the read data into the main div. No problem till now. But then I'm producing a ckeditor instance beside each note for the client to become able to easily add comments to his note of interest. The comments are initially generated as several indexed empty div's in another HTML file, loaded into the ckeditor instances. However, all these happen in a really large for loop (I have almost 6000 notes to be loaded in a segmented manner using if conditions), and so now I'm engaged with the classic closure-loop problem. Have read several previous questions and answers foo this and other websites and tested a number of them to get rid of the closure-loop problem, but no success so far.
The related segment of my java script has the structure:
var q;
$.when(
$.ajax( ... loads the json file that contains the notes and set q=$.parseJSON(data) on success)
).then(function() {
for(var i in q) {
if(i is in a specific range){
... several lines of code for properly importing the notes ...
... and generating a place for the comments to appear as:
... +'<div id="CKEditor'+i+'" contenteditable="true" placeholder="Put your comment here!"></div>'
... which is appended to the main div of the webpage
... Now the main problematic part begins:
$('#temporary').empty(); // a hidden div defined somewhere in the page
var func = (function() {
var ilocal=i, tmp;
return function() {
tmp=document.getElementById('temporary').innerHTML;
alert(tmp);
CKEDITOR.instances['CKEditor'+ilocal].setData(tmp);
}
})();
$.when(
$('#temporary').load("NewComments.htm #verse-"+i)
).then(func);
};
};
CKEDITOR.disableAutoInline = true;
CKEDITOR.inlineAll();
})
maybe the problem is not for the loop but for the nested $.when().then(), any suggestion to resolve the issue?
The problem is that there is only a single $('#temporary') div in your page, which will be re-used and overwritten by every iteration. In particular, in your callback
document.getElementById('temporary').innerHTML;
…
CKEDITOR.instances['CKEditor'+ilocal]
the ilocal (and tmp) variables are indeed local to the IIFE and that particular iteration, but document.getElementById is global. It will return the same element every time.
A quick fix is to create a new element for every request, and assign it to tmp during the iteration (like you assign i to ilocal) instead of when the func is called.
A much better practice however would be not to use $('#temporary').load("NewComments.htm #verse-"+i) multiple times, and instead load the NewComments.htm only once per Ajax and process the result as you need.

Exclude HTML from script tag

I'm trying to learn Handlebars.js and thought of a way to get use of it in a site im making. It's a one page site which will have two containers with three divs in each which will contain embedded Soundcloud players through their API.
When including the divs which contain the API requests in the script tag handled by Handlebars, the site behave very unreliable and shows only some of the six players. It's not really consistent but can show different players all the time. The problem seems to be in the Soundcloud javascript SDK but I don't feel to familiar to dig around in there too much.
Therefore I thought of some way to exclude the player divs (see code) so that they're loading instantly and not being Handled as javascript, but still show up beneath the artist - title in the placeholder div (which is set to contain the result of the Handlebar script).
The problem is that I can't come up with a nice way of doing this, are there any easy function (with Handlebars helpers maybe) that will help me do what I want?
<div id="placeholder"></div>
<script id="player-template" type="text/x-handlebars-template">
<div id="container1">
Artist1 - {{title1}}
<div id="player1"></div>
Artist2 - {{title2}}
<div id="player2"></div>
Artist3 - {{title3}}
<div id="player3"></div>
</div>
<div id="container2">
Artist4 - {{title4}}
<div id="player4"></div>
Artist5 - {{title5}}
<div id="player5"></div>
Artist6 - {{title6}}
<div id="player6"></div>
</div>
</script>
<script src="js/handlebars_title_script.js"></script>
One solution is of course to make one Handlebar template for each Artist - Title div and set the placeholder of each template to a div containing only Artist1 - {{title1}} but that really destroys the point of using Handlebars to minimize my HTML coding.
Anyone got any tip for me how to solve this?
Edit 1:
I found another solution by changing in my javascript (which I didn't post at first so obviously you couldn't help me with that).
$(document).ready(function() {
var hey = "heya";
SC.get("/users/artist/tracks", {limit: 1}, function(tracks){
var title_data1 = tracks[0].title;
hey = tracks[0].title;
alert(title_data1);
alert(hey)
});
//Data that will replace the handlebars expressions in our template
var playerData = {
title1 : hey,
};
document.getElementById( 'player-placeholder' ).innerHTML = playerTemplate( playerData );
});
Sorry for bad intendetion. The only problem with this code is that title1 (in the variable playerData which is the Handlebars context) gets the first value of the variable hey ("heya"). When it's alerted it pops up the real title, how can I make title1 use this value instead without nesting the variable in more javascript (since that's what causes the before mentioned error with players showing up weird)?
Note: throughout the comments this answer has changed drastically. Please view the earlier revisions if you would like to see the evolution of this answer.
After getting a hold of your JsFiddle example I was able to get it working in a way I think you wanted.
Working Demo
HTML:
<body>
<div id="wrapper">
<div id="player-placeholder"><!-- rendered template goes here --></div>
<!-- handlebars template: -->
<script id="player-template" type="text/x-handlebars-template">
{{#each tracks}}
<div class="track">
<header class="header">
<span class="artist">{{user.username}}</span> - <span class="title">{{title}}</span>
</header>
<section class="player" data-uri="{{permalink_url}}">
</section>
</div>
{{/each}}
</script>
</div>
</body>
JavaScript:
$(document).ready(function() {
/*
get your template string See:
http://api.jquery.com/id-selector/
http://api.jquery.com/html/
*/
var source = $('#player-template').html();
// compile the template into a handlebars function
var template = Handlebars.compile(source);
// initialize sound cloud api
SC.initialize({
client_id: '90fb9e15c1e26f39b63f57015ab8da0d'
});
/*
This function will be called once the HTTP transaction
started by SC.get(...) completes. Note, there's nothing
wrong with doing this as an anonymous function, I'm
simply assigning it to a variable to show that this
is a distinct function that's called later
*/
var callback = function(tracksResponse){
/*
once a response has been received, we'll use the response
to generate a new context to pass to the template function.
Note, you can use the template function in here because its
within a closure. See:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures
*/
var context = { tracks: tracksResponse };
var html = template(context);
/*
assign the rendered html to your placeholder on the page
see: http://api.jquery.com/html/
*/
$('#player-placeholder').html(html);
/*
Now that the html is rendered and on the page, its time to
setup the sound cloud players. Note the css classes I assigned
to the track/player. This line selects all of the player's and
runs the function over each. See:
http://api.jquery.com/class-selector/
http://api.jquery.com/each/
*/
$('.track .player').each(function(index, e){
var $this = $(this); // jQuery reference to the current object in 'each loop'
/*
I assigned the permalink_url of each track to an attribute called 'data-uri'
This line gets the value of that attribute. See:
http://api.jquery.com/data/#data2
*/
var permalink = $this.data('uri');
var urlParameters = '/&maxheight=100&maxwidth=300&format=json&sharing=false';
/*
finally we call the sound cloud oEmbed function feeding it the url
stored in the element, as well as the actual element.
(see the second argument of the each function: http://api.jquery.com/each/)
*/
SC.oEmbed(permalink + urlParameters, e);
});
};
// get tracks for your artist
// Note the "limit" in the object controls the number of items returned
// by sound cloud
SC.get("/users/theshins/tracks", {limit: 5}, callback);
});
What went wrong?
JavaScript is a single-threaded, asynchronous, event-driven language. That giant mouth-full means JavaScript doesn't really have a notion of threading (I'm intentionally ignoring WebWorkers). To work around that limitation, almost all IO in JavaScript is non-blocking (asynchronous).
Whenever an asynchronous IO transaction begins it immediately returns to the caller and code execution continues. Almost all IO transactions take a 'callback' or have an event that will be called when the IO transaction completes. That means the basic pattern for all IO operations follows something like this:
Create a callback function
Call IO operation, passing it the arguments it requires to complete, plus the callback
Execution returns immediately
Sometime in the future, the callback function is called
In your original example $(document).ready(function() { ... }) queues an anonymous function to fire when the document.onReady event is raised. Your original example, however, had two callbacks assigned. This isn't a problem, an in fact .ready(...) is designed to accept and queue many callbacks. However, where you went wrong is you had two separate blocks of code that called SC.get(...).
Technically if done right this wouldn't be a problem, but your first on ready callback's purpose was tasked to setup the page's HTML while your second callback tried to initialize the player controls based on html on the page. Remember these events and IO operations are asynchronous, they'll fire in whatever order. Essentially this became a timing issue, you were attempting to initialize controls on the page, and generate HTML to display on the page at the same time.
How it was fixed
To fix the timing issue you need to synchronize when you get your info, when you build your template HTML, and when you initialize your controls. There's a lot of ways to do this and many frameworks support the idea of promises to help gain control over the order that asynchronous events are fired, and their callbacks called.
I took the simple route and combined all of your SC.get calls in to one, then within it's callback I render the handlebars template and initialize the SoundCloud players.

Since Meteor upgrade to 0.8.0, Template "rendered" callback is not fired when Session variable dependancy changes

I'm stuck with a problem since I upgraded to 0.8.0.
The Template rendered is not being fired anymore (except the first time).
I followed the recommendations as in:
https://github.com/avital/meteor-ui-new-rendered-callback/blob/master/new2/client/each.js
This didn't helped, and so I finally made this small piece of code (by modifying the new2 example).
The main difference is that the update is triggered by a Session variable change instead of a DB change.
This perfectly shows the problem, as rendered is fired only twice with this example:
client/each.js
Template.list.items = function () {
return (Session.get('items') || 'None')
};
var renderCount = 1;
var logRender = function () {
console.log("rendered #" + renderCount);
renderCount++;
};
Template.list.rendered = function () {
logRender();
};
Template.justName.rendered = function () {
logRender();
};
setInterval(function () {
Session.set('items', {name: Random.choice(["one", "two", "three"])});
}, 1000);
client/each.html
<body>
{{> list}}
</body>
<template name="list">
{{#with items}}
{{> justName}}
{{/with}}
</template>
<template name="justName">
{{name}}
</template>
How can I do to get the Template.justName.rendered callback properly fired when content update is triggered by a Session.set?
Thanks,
I do have an instant solution for you, but it probably requires a tiny bit of re-thinking your actual code. This is by the way the same problem as here:
Meteor 0.8.0 - Failed to operate DOM in rendered callback
But the question is posed in such a different context that it makes sense to answer it twice.
So why does it not trigger the rendered callback? Because it does not re-render.
Blaze treats the whole thing of "how to react on a changed dependencies" very differently, "better" one might say: it will identify the DOM node where your "one", "two" or "three" (in your case it's the template itself) was stored in and just replace the part that has changed, which is the text content "one", "two" or "three". The DOM node itself as well as the template stay completely intact. That also means, that everything you could have been doing with this DOM node won't have to be re-done in almost every practical scenario. I.e. if you animate it, change it's text color using jQuery, the color and animation will just stay on the screen, so you won't need the rendered callback to re-do that.
In your case, the problem is easily solved by just rearanging what you want to do on "rerender":
var whatever = function(){
// whatever you want to do on data-change, in your case calling "logRender" (which needs to be renamed with Blaze, anyway..)
logRender();
}
And then the only thing you have to do is firing it whenever your data change, either manually, like this:
setInterval(function () {
Session.set('items', {name: Random.choice(["one", "two", "three"])});
// calling the function when changing the data, knowing that it WON'T destroy the DOM node it affects
whatever();
}, 1000);
or reactively, like this:
Deps.autorun(function(){
Session.get("items"); // our dependency, just has to be there, but you can also use it
whatever(); // will be fired whenever dependency changes
});
The core idea is to eliminate the need to re-do something you did in the rendered callback, since the DOM and the identity of its objects (and all the beautiful jQuery effects) are still intact. So all that is left to re-do is something that only would depend on the particular reactive data-change, which is why there is the Deps.autorun().
In your particular example, your "logRender" function did not have any reactive dependencies, but if you add some and put it into the Deps.autorun(), it will be reliably re-run whenever the dependency changes.
As a conclusion, Meteor 0.7.x and below drove us to make the mistake of treating the "rendered" callback function as a general purpose autorun function, which is why we are running into trouble now and have to fix our apps.
As noted in the comments, this is a indeed a design change with Meteor.
Prior to Meteor 0.8, a template was a function that generated HTML. This function would be re-computed whenever any of its reactive dependencies changed, which resulted in a recreation of all the DOM nodes generated by the template (apart from any sub-templates or isolated nodes). Whenever this re-draw happened, the rendered callback was triggered.
This behavior creates quite a performance hit because it requires re-rendering of potentially a lot of HTML, including for identifiers and helpers depending on data that hadn't changed. Additionally, it made it difficult to use other libraries like jQuery to modify the DOM elements that were created, because Meteor basically had control of the entire process and the jQuery code would have to be carefully re-run each time.
Meteor 0.8 fixes this by only rendering the pieces of the DOM that have actually changed, down to the granularity of the identifiers in your template - it is much more fine-grained. As a result, the template's rendered callback is only triggered once when your template hits the page, and is never called again afterward. This solves a lot of performance issues and allows jQuery and other DOM manipulations to work seamlessly with Meteor, but also means that you won't get the automatic callback signalling when something has changed. You can, however, achieve this with helpers that use reactive variables for specific things that change.
For a more detailed listing of how Spacebars, the new Handlebars replacement, works, see https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md
See also the new documentation about the rendered callback: http://docs.meteor.com/#template_rendered
So, I was doing a lot of digging yesterday to try and figure out basically the exact same issues you are having. I am still digging, but I did come across this Devshop Talk about Integrating Other Clientside JS Libraries. In it Ted Blackman describes a package he made to trigger events when a Session variable changed. It sounds like what you need. This talk was given prior to 0.8.0 so I am not sure how the package would be effected, but it might be worth a shot.
Devshop Talk - https://www.youtube.com/watch?v=NdBPY98o6eM
Session Extras - https://atmospherejs.com/package/session-extras
Event Horizon - https://atmospherejs.com/package/event-horizon

Input[number] doesn't show the value using angular & twitter bootstrap

My model is populated at the controller, and when I bind the data acts like it is there, but the input box is blank. As soon as I enter a value everything works normally. But if the default value is blank this will confuse my users greatly.
<div class="row" ng-controller="RangeInputCtrl">
<div class="col-md-12">
<label class="leftwall">
Value:
<input type="number" class="form-control"
ng-model="percentOfMax.current"
min="{{percentOfMax.min}}"
max="{{percentOfMax.max}}"
ng-change="changeValue()" />
</label>
</div>
</div>
ng-value is useless with this directive, since angular hijacks the input element. The code was working even worst without the min and max, so I know that they are required (or at least act required).
I have the input directive wrapped in a label due to twitter-bootstrap 3.
(function () {
'use strict';
var controllerId = 'RangeInputCtrl';
angular.module('app').controller(controllerId,['$scope', buildRangeInput]);
function buildRangeInput($scope) {
$scope.percentOfMax = InitCalcPercentOfMax($scope);
$scope.changeValue = function () {
var percentOfMax = $scope.percentOfMax;
$scope.percentOfMax = calcPercentOfMax(percentOfMax);
}
}
})();
There is a function, not posted here that takes $scope and builds the initial data structure from a complex object. Not shown here. InitCalcPercentOfMax has been tested and works as planned. It is external, and called separately due to code reuse.
The other function calcPercentOfMax(percentOfMax) is used to recalculate the formulas used in some directives that have been pulled (for the purpose of troubleshooting).
In short somewhere I must have something acting or posting wrong, and I just don't see it.
I had console.log([some expression]) all over, to see if the data was incorrect or ever blank, but the percentOfMax.current value always has at least a 0, so I still don't see where I am getting a blank from.
No error in console. And if I put in a <span>{{percentOfMax.current}}</span> I get the correct value on the first load.
The value is there in the model, it is just blank in the input box.
I think this is all of details, if I missed something, I will try to reply with an edit ASAP.
Based on a discussion at the angular chat room, I made the following changes.
var d = $q.defer();
var p = d.promise;
//If I leave this off, it never resolves. So there is nothing in the directive to trigger resolve.
d.resolve(function () {
return InitCalcPercentOfMax($scope);
}())
$scope.percentOfMax = p.then(function (POM) {
$scope.percentOfMax = POM;
});
I also added <span>{{percentOfMax}}</span> back to my view. I can see the promise firing, and I can see that the expected data is in my precentOfMax, but the input box is still empty.
Before someone asks, I am using version Angular.js 1.2.14, downloaded from Nuget
It looks like it should work, my guess is that you either have an error on the console that you've missed, or that there's some other logical error. This JSBin example is a simplified version which works: http://jsbin.com/zumedezu/1/edit
Try changing Value: to Value: {{percentOfMax.current}} to output the current value.
Is it possible that InitCalcPercentOfMax performs the calculation async outside of Angular?
InitCalcPercentOfMax($scope) was returning .current as a string. CalcPercentOfMax wasn't correcting it, but knew how to work with a string as a number.
Since the input type is number, the new numeric value was causing the .current to change to a number, so its value was showing up.
It took a greek lunch to see that the initial value was quoted.

Categories

Resources