ng-repeat - I'm confused on it works - angularjs

I'm pretty new to angular js and I have a question about the ng-repeat. I'm following examples at AngularJs : https://docs.angularjs.org/guide/concepts
The section that I'm currently on is 'Adding UI logic: Controllers'. If you look at the index.html, specifically at the code:
<span ng-repeat="c in invoice.currencies">
{{invoice.total(c) | currency:c}}
</span>
When I run in the browser, everything works as expected, but I notice that invoice.total function runs at least 6 times. How do I know? Well in the index.js I added a console.log function within the total function as seen here:
this.total = function total(outCurr){
console.log(outCurr)
console.log(this.convertCurrency(this.qty * this.cost, this.inCurr, outCurr))
return this.convertCurrency(this.qty * this.cost, this.inCurr, outCurr);
};
I would expect the code to run 3 times because the invoice.currencies only has 3 items:
this.currencies = ['USD', 'EUR', 'CNY'];
However, this is what I see in my browser console window:
Any ideas? Thank you in advance?

user2341963 has probably the good answer so i'll quote it first and i'll add some precision.
The digest cycle runs at least twice to check that a value hasn't
changed. First time it runs for each of the values in this.currencies.
It runs again to check that the first digest cycle hasn't changed any
values. It hasn't so it stops after the 2nd cycle
In angular you have what we call a digest cycle. This cycle watch for any change that happened in the application bind to angular. In order to refresh it. This permit to refresh your DOM when you need without refreshing the whole page.
"The bind to angular" means
Every block between {{}}
Every use of scope.$watch or attrs.$observe which are used massively by directives (ng-model/ng-repeat/...).
I don't know what makes this cycle to trigger twice at start this is probably because of some internal mechanics or the filter that you use. Check this link for more info : https://www.ng-book.com/p/The-Digest-Loop-and-apply/

Related

Why does my watcher gets called twice on the same change?

I have an AngularJS 1.4* application running locally (yet). This app is served by an Laravel 5.1 backend RESTFul API.
I have to make this app that represents a package trip. A package is composed by days, ranging from 0 to N days. Each day have a list of services, ranging from 0 to N services. And a hotel.
My web server, from which my laravel application consumes from, delivers me a pre-setted package, containing a list of days: each one with a list of services and a hotel data (unused so far). On the response I have a list of properties for the package (that don't matter for now) and an array of days, called days_info. That response is being put in the $scope.package, on my PackageController. The PackageController also declares an directive called packageBlock, that consists in a list of days, and some other data for the package.
<div ng-repeat="day in package.days_info" class='row'>
<div class='col-md-12'>
<package-days-block></package-days-block>
</div>
</div>
Inside <package-days-block> directive, I have another to iterate through the list of services inside every day.
<div class='container-fluid' ng-repeat='service in day.services' ng-controller="ServiceController">
<service-block></service-block>
</div>
That's where the problem begins: to my undestandment, I now have a $scope.service inside my ServiceController. So, I started to change it on my need inside the ServiceController through a $scope.service.
The $scope.service has an attribute called service_id. I put a listener/watcher on it, so at any time the $scope.service.service_id is changed, I ask for another service_table (holds the informations about the services, it's based on the service_id previously choosen or changed by the user), and put it in the $scope.service.table.
// ServiceController
$scope.reloadServicesTable = function(service_id, service_day, date, paxes){
MandatoryService.getServiceTable(service_id, service_day, date, paxes)
.then(
function(service_data) {
$scope.service.table = service_data;
},
...
);
The reloadServicesTable is called on the watcher for the service_id changes.
// ServiceController
$scope.$watch(
'service.service_id', // Places the watcher to watch the changes on the service's ID.
function(new_service, old_service) {
if( new_service === old_service )
return;
$scope.reloadServicesTable($scope.service.service_id, $scope.service.service_day, $scope.day.date, $scope.package.paxes);
}
);
The problem starts here: the request for the service's table is called twice when the service_id only changes once.
WHY, GOD, WHY?!
There's another part of my code where I, from the PackageController, run through the entire days_info array and reads the value of an attribute price inside the service.table: service.table.price. In there, I realise that there's two scope's: the one I handling and the other that I have no FREAKING IDEA where it came from!
If I put an console.log($scope); inside the method that runs through the days_info, I get two scopes for every request. This method is on the PackageController.
Any ideas why this is happening?
P.S.: It's my very first AngularJS application, so, take easy if I messed up on something basic...
EDIT:
As pointed out by an fellow on the comments, my question wasn't very reproducible. Sadly, I can't put here only the part I'm having doubts cause I don't have the slightest idea where the problem lies! (I know that this isn't much of help)
I took some screen shots from the Chrome Console:
First, the requestions fired on the change of the service_id
As you can see, every request is called twice everytime. This is not an one-time-thing. The /api/service/{id}... is the call for the service's table information. The /api/service/by_route/origin/... returns an list of services from one city to another (or the same). One does not interfere on the other.
The other image is the output from a console.log from the PackageController $scope, on the time that the service_id is being changed.
As you can see, there's two different scopes. And the b scope is son of the r scope. The r scope is also calling the watcher on the service_id?
The call for the sums price is been called twice from differente places, as you can see in the image below:
It may solve you issue. Even i had faced exactly the same as you are mentioning.
The reason for me was that, the controller was getting initialized again and there was a separate api call written in that, which was intended to load the page initially.
There can also be a scenario where you have assigned controller twice in the mark up.
<div ng-repeat="day in package.days_info" class='row'>
<div class='col-md-12'>
<package-days-block day="day"></package-days-block>
</div>
</div>
<div class='container-fluid' ng-repeat='service in day.services'>
<service-block service="service"></service-block>
</div>
Pass day and service down into directives. Use two way binding to pass your day and service changes back up to package.days_info.
Remove your ServiceController It does not make much sense to ng-repeat a controller. <service-block> and <package-days-block> are E directive that handle logic.
Write only one watcher in your PackageController that watch package.days_info When your day or service change, it can simply find out and do something about it.
Just Chill and fix it.

Need explanation on a angular direcive load

I just want to understand why in the following jsFiddle 'here is a lo' is printed three times.
http://jsfiddle.net/wg385a1h/5/
$scope.getLog = function () {
console.log('here is a log');
}
Can someone explain me why ? What should I change to have only one log "here is a log" (that's what I would like this fiddle do). Thanks a lot.
Angular uses digest cycles/iterations to determine when state has changed and needs to update the UI. If it finds any change on one of it's cycles, it keeps rerunning cycles until the data stabilizes itself. If it's done 10 cycles and the data is still changing, you'll see a rather know message: "angularjs 10 iterations reached. aborting".
Therefor, The fact that you are seeing the message displayed 3 times is because you have a simple interface. In fact, you can get up to many more such messages in the log, due to the fact that your directive uses {{getLog()}}. Angular keeps evaluating the expression to see if it changed.
To avoid such problems, under normal circumstances, you should store the value returned by the function you want called only once in the $scope object inside the controller and use that variable (not the function call) in the UI.
So in the controller you'd have $scope.log = getLog() [assuming it returns something, and not just writing to the console] and in the directive use the template {{log}}. This way, you'll get the value only once, per controller instance.
Hope I was clear enough.

Angular: how to force a recompile of a block

I am post-processing the output of prettify to highlight some lines in the code. I'm using code like this, which works fine:
x = angular.element('.prettify li:nth-child(' + zz['line'] + ')');
x.css('background-color', 'yellow');
x.prop('title', zz['message']);
Now, instead of using the title tag to show a message on the line, I want to use Bootstrap tooltip. The obvious change to the above code is:
x.prop('tooltip', zz['message']);
However, this doesn't work. I presume I need to tell Angular to recompile the block, so it picks up the directive for tooltop (hence the title of the question).
Update - here is a fiddle showing what I am trying to do: http://jsfiddle.net/6Y4d9/
to recompile thr block -
you should use $compile service, like so:
$compile(block)(scope)
BUT, for your task your need just:
scope.$apply()
And:
change x.prop to x.attr
http://jsfiddle.net/6Y4d9/1/
If you ever need to force angular to re-digest a certain scope, all you have to do is call:
$scope.$apply();
This will for the $scope to re-apply any changes that may have occurred since the last time it applied. These types of applies only need to be run when the initial event that created the interaction because from an event-handler that is outside of the angular app.
Another option you have is to call:
$scope.$apply(function(){
//put your code in here, and it will run your code and then apply it to the current scope
});

AngularJS jquery.flot chart directive DOM collision

i'm working with angular js now for about ke 4 months and despite all the "first step failures" like not emphasising the async way anuglar thinks, I'm facing a problem I don't really understand. It's not that easy to describe.
I have a provider which registrates directives within the routeprovider's resolve function - during config phase. To compile programmatically preconfigured directives I create them on the specific controller call of each route. Acutally the directive I'm adressing here is a complex flotchart directive. It retrieves data from a rest api, transformes the retrieved data and prepares different kinds of option setups like proper stacked line charts or simple piecharts. Every single step takes its time, so I introduced promises to be sure that everthing is at it's right place before I finally call something like "$.plot".
So now I have the following situation: Imagine a singlepage app with two tabs. Each tab - like a first class menu item - refers to a new page with a new controller to process and new partials to render. For each page i have beside other directives one of these heavy flotchart directives to render. actually it takes about 5 seconds to render the chart. So we assume that we really start the app from beginning - like pressing F5. Now I enter the first page the first time and within the mentioned 5 seconds I switch tabs to enter the next page. I get to the next page, see different partials, layouts and stuff and a loading chart - but actually the directive of the first page is still bound to its link phase of that heavy flotchart directive (still preparing options for flot and calculating data to output graphically).
My problem is that this link phase actually really ends within a completely different template/route/controller context and gets stuck. It crashes with a console "replace" error from jquery.flot. I think this error means that flot tries to plot into a div which does not exist anymore. But that error occures just when I switch tabs during the link phase of the first page's heavy flot directive. It doesn't happen when the first page's chart is fully rendered and doesn't happen when the first page's directive hasn't entered its link phase (or am i missing something??). I tried placing some console.logs directly BEFORE hitting the jquery "$.plot" - remember only of the first page chart directive to dive into what's acutally happening. And the strange thing is when I manage to switch tabs within these magic 5 seconds, I still get the console log entries from the first page entry although I'm on a different page. And now guess what. That's strange - acutally two directive link phases are running side by side and one of them on a completely different view (or isn't it completely different, because its a singel page app?). Imagine I plot ("render") the chart in exactly the same div id - like $('#flot-chart'). so I have html parts containing id="flot-chart" on the first page AND on the second. when I now switch from the first to the second page (not finishing the first chart) I get the chart from the first page rendered in the #flot-chart div of the second page and like half a second later the actually correct chart rendered in that same div. So actually the link phase of the first page's chart directive ends in a completely different page in a way showing 2 charts consecutively. I know jquery.flot depends on DOM manipulation via jquery and that might be the problem (perhaps THAT'S the only real explanation for my problem), because jquery DOM manipulation is out from the angular way of life.
Or are there other explanations? I acutally solved the problem via $routeChangeStart listening and killing the $.plot process, but are there some hints, suggestions, explanations for that behaviour?
Plunker flot chart directive DOM collision
I have prepared a plunker which shows kind of a similar behaviour. i've delayed the creation of the directive and the directive's async data and option retrieval methods to somehow mock the behaviour of my app. this is non production code but describes simplified the way my problems occure. when you "fast click" the menu item one after the other many times, you can sometimes force angular to show 2 charts in one page. acutally one directive is linking and doing stuff in a different partial ? i know i'm missing something in my mind ... please give me a hint.
i used chrome for reproducing the error. stop the the plunker and press 'run'. directly after pressing 'run' click as fast as you can both links a couple of times.
Thanks a lot!

dijit.byId("").is not defined in Worklight works with Angularjs

I make a project in worklight used dojo mobile 1.8.1 and angularjs 1.0.1,but i got a strange problem.
Here is my html part:
<div data-dojo-type="dojox.mobile.ScrollableView" data-dojo-props="selected:true" id="id1" ></div>
<div class="full" data-dojo-type="dojox.mobile.View" id="id2"></div>
and my JavaScript part:
require([
"dojo", "dijit/_base/manager","dojo/parser", "dijit/registry",
], function(dojo) {
dojo.ready(function() {
// dijit.byId("id1").performTransition("id2"); //////////place I
});
});
var angularApp = angular.module('app', [])
.run(['$rootScope','$templateCache','$route',
function($rootScope,$templateCache,$route) {
dijit.byId("id1").performTransition("id2");////////place II
}]);
The problem is at place I, it works well, but when I put "dijit.byId("id1")" at place II, it shows:
dijit.byId("").is not defined
The ready function is executed after dojo parsed your document & constructed the widgets you try to get using dijit.byId.
The second part is not placed within the ready function, so dojo can't find your elements yet !
Solution: Access your elements in the ready function OR do not declare them declaratively (like you did, using html code...) !
Lucian
The dojo.ready() function registers an event-handler function (callback) which will be fired after the DOM got completely parsed.
This comes in very handy if you want to be sure that every html element got rerendered as dojo-widget before you perform operations on them.
So, in your example, Code II will be executed before the dijit.byId() function has been made available by loading the necessary modules (dijit/registry, ...). Code II would only work after the dom-ready event got fired and your "dojo.ready()" function did load the required modules.
You should definately invest 5 minutes in reading what dojo.ready() is about:
http://dojotoolkit.org/reference-guide/1.8/dojo/ready.html
Sidenote:
You shouldn't use dijit.byId() any more in Dojo 1.8.1. Try using dijit.registry.byId() (you have to require the dijit/registry module).
http://dojotoolkit.org/reference-guide/1.8/dijit/registry.html#dijit-registry-byid

Resources