Calling function inside AngularJS goes in endless loop - javascript

I am new to AngularJS and I was building a sample app. I want to display the result of Google maps response on my web page. Here I pass the sample value, but the page goes on a loop and gives this error: Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
var app = angular.module('myApp', ['ui.bootstrap']);
app.controller('myCon', function($scope, $http) {
$scope.getAddress = function(url){
return $http.get('https://maps.googleapis.com/maps/api/distancematrix/json?origins=Toronto+ON&destinations='+url+'&mode=driving').then(function(response){
return response.rows[0].elements[0].distance.text;
});
};
And this is the HTML page :
<!DOCTYPE html>
<html lang="en" ng-app="myApp" ng-controller="myCon">
<head>
<meta charset="UTF-8">
<title>Angular Demo</title>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js"></script>
<script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.12.0.js"></script>
<script src="js/app.js"></script>
<link href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="page">
{{getAddress('Ottawa')}}
</div>
</body>
</html>

When you invoke a function within interpolation {{xxx()}} you need to be careful. Interpolation runs every digest cycle and you are calling a function within that, All is well, but then within the function you are making an $http call which again triggers a digest cycle (after it has resolved/rejected and every promise chain) and interpolation expression gets evaluated again to stabilize the view. Angular will just go on hoping the view will be stable after every digest cycle, but apparently not. Angular does this loop till an max limit of 10 times (internally set but configurable though it will not solve your problem) internally and stops trying to stabilize displaying the error which you see in your console.
Just make the call when an event is triggered and not sure why exactly you want to do that. But you could as well do it in the controller right away when it is instantiated and bind the data as property to the view. Or bind the function getAddress(url) during a specific event happens (i cant recommend further with the limited knowledge of why you are invoking getAddress('ottava') from the interpolation)
An example, in your view
{{distance.text}}
In the controller:
$scope.distance = {};
//After getAddress definition call it directly from controller
$scope.getAddress('ottava').then(function(text){
$scope.distance.text = text
});

Related

Angularjs: How To Call Controller Function After Routing

I'm learning angularjs and there is one aspect of it that I'm struggling to understand.
My desired/expected behavior of the code below is:
User clicks the Paris link (anchor tag)
The routeProvider intercepts the request, loads the paris.html page into the ng-view.
The 'getCity' function in the controller gets the data and sets the scope variables, which are displayed in the london.html expressions.
However, I can't figure out how to config angularjs to use the 'getCity' function when the html page is loaded into the ng-view. The closest I can get is calling the 'getCity' function from within the CityController itself, bit this seems to have the undesired effect of calling the function when the whole app (index.html) is loaded instead of only when the link is clicked. The controller will have a number of different functions.
I also know you can use ng-click to call a controller's function, but I'm unsure how this would work with loading a html page into an ng-view through the route provider.
Any help would be appreciated. Please see code below from a small app built for learning purposes:
index.html
<!DOCTYPE html>
<html ng-app="mainApp">
<head>
<meta charset="ISO-8859-1">
<title>Insert title here</title>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular-route.js"></script>
</head>
<body>
<ol>
<li>Paris</li>
</ol>
<div class="content-wrapper" ng-controller="CityController">
<div ng-view></div>
</div>
<script src="resources/js/app.js"></script>
<script src="resources/js/CityController.js"></script>
</body>
</html>
app.js
var app = angular.module("mainApp", [ 'ngRoute' ]);
app.config([ '$routeProvider', function($routeProvider) {
$routeProvider.
when('/cities/paris', {
templateUrl : 'resources/paris.html',
controller : 'CityController'
}).
otherwise({
redirectTo : ''
});
} ]);
CityController.js
app.controller('CityController', function($scope, $http) {
$scope.getCity = function() {
$http.get('city')
.success(function(response) {
$scope.name = response.name;
$scope.country = response.country;
}).error(function() {
//Output error to console
});
};
//$scope.getCity();
});
I don't want to call getCity here because it means the http get request to
the 'city' endpoint is called when index.html is loaded
paris.html
This is Paris.
<br><br><br>
Name: {{name}}<br>
Country: {{country}}
<br><br><br>
I think what you are looking for is the router resolve option.
A resolve contains one or more promises that must resolve successfully before the route will change. This means you can wait for data to become available before showing a view, and simplify the initialization of the model inside a controller because the initial data is given to the controller instead of the controller needing to go out and fetch the data.
Check the explanation and usage here
You can call getCity() from paris.html using ,ng-init=getCity() ,ng-init will call your function as soon as paris.html is loaded into your ng-view .
For Eg.
This is Paris.
<br><br><br>
<div ng-init=getCity() >
Name: {{name}}<br>
Country: {{country}}
</div>
<br><br><br>

Bootstrap / initialization flow of AngularJS apps

I would like to know if the initialization flow of AngularJS apps is predictable in terms of the order of execution of different blocks within an HTML document.
I read this question What is meant by Bootstrapping in angular JS?, which explains a lot of the process, but does not answer my question in detail.
I have a plunker example http://plnkr.co/edit/boVFjHWoxdbiADq41dXC?p=preview, where I console.log() numbers, in the order that I thought they would execute. What was a bit surprising though was that the execution of the .run() block seems to be deferred.
<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="angular.js#1.4.0-rc.0" data-semver="1.4.0-rc.0" src="https://code.angularjs.org/1.4.0-rc.0/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<!--<script src="script.js"></script>-->
<script>
// Code goes here
console.log(1);
angular.module('app', [])
.run(function () {
console.log(2);
setTimeout(function () {
console.log(6);
});
});
</script>
<script>
console.log(3);
</script>
</head>
<body>
<img src="http://www.mrwallpaper.com/wallpapers/Sicily-Italy.jpg" />
<script>
setTimeout(function () {
console.log(5);
})
</script>
<img src="http://www.wishsicily.com/gallery/1370_scicli-ragusa.jpg" />
<script>
console.log(4);
</script>
<h1>Hello Plunker!</h1>
</body>
</html>
So I have a few questions:
Is it safe to say that the run() block, flagged in the console by the number "2", will always be executed after the rest of the page was processed?
I get that what initiates the execution of the .run() block is the call angularInit(), which is executed by the call ready() on DOM ready. Is that correct?
Does that mean that, in effect, the execution of the run() block happens on the same tick, as it would if it was assigned to window.onload?
There is actually documentation about the run block on https://github.com/angular/angular.js/blob/ce669edfa14dc7eb7c389d2f82c9c98399a9009b/docs/content/guide/module.ngdoc
Run Blocks
Run blocks are the closest thing in Angular to the main method. A run
block is the code which needs to run to kickstart the application. It
is executed after all of the service have been configured and the
injector has been created. Run blocks typically contain code which is
hard to unit-test, and for this reason should be declared in isolated
modules, so that they can be ignored in the unit-tests.
The angularInit function initializes the modules which then call their own runblocks. That happens in src/angular.suffix
jqLite(document).ready(function() {
angularInit(document, bootstrap);
});
So the run function will always be called after the document loaded.
Since Angular will start initializing then, there may be some time between the window.load and the initializing.

AngularJS private variable in controller function

I am new to Angularjs. I came across a example online and it got me really confused. Here is the code:
angular.module("testApp",[]).controller("testCtrl", function($scope){
var data = "Hello";
$scope.getData = function(){
return data;
}
$scope.setData = function(newData){
data = newData;
}
});
Here is the view:
<html ng-app = "testApp">
<head>
<script src="lib/Angular.js"></script>
<script src = "foo.js"></script>
</head>
<body ng-controller="testCtrl">
<div ng-click="setData('Hello Hello')">{{getData()}}</div>
</body>
</html>
My question is how does angular know when to trigger the getData() method in the view. The click event will change the data. However its a private variable, not attaching to the $scope, which means $scope does not watch the change of it, then how does angular know when to call the getData() in the view? I know it maybe a dumb question, but please help! thank you so much!!
The double-curly expression is what AngularJS calls an observing directive. During the compilation phase, this directive registers listeners on the expression using the $watch method of the scope.
On the other hand, ng-click is what AngularJS calls a listener directive. This type of directive registers a listener with the DOM instead. Whenever the DOM event fires, the directive executes the associated expression inside an $apply call.
This means that after the click expression is executed, a $digest cycle will begin. In this cycle, the scope examines all the registered $watch expressions (e.g. the double-curly expression containing getData()) and calls the listener in case there's a difference from the previous value.
In the end, it is this digest cycle that ensures that all your bound expressions are evaluated.
The top level controller function runs immediately before it renders the view, in order to initialise the scope. Next the view loads and any logic in the view executes. So when it reaches getData() it returns the output of that function at that time.
The clever part is that Angular automatically binds the data in your views all the way back to the data model, so whenever there is a change in the model (i.e. the source of the data) that automatically updates the value in the view and if necessary will run your getData() method several times.
I saved it here as a Plnkr
Your binding {{getData()}} is a "run on evaluation". So when the DOM renders and angular parses it, it sees the () at the end and runs the function. I'll provide citation in a minute when I find it.
You don't need the getData in angularjs ... or maybe for other uses than the one you are showing right there.
So the right code would be (without getData) :
<html ng-app = "testApp">
<head>
<script src="lib/Angular.js"></script>
<script src = "foo.js"></script>
</head>
<body ng-controller="testCtrl">
<div ng-click="setData('Hello Hello')">{{data}}</div>
</body>
</html>
And with getData :
$scope.getData = function(){
data = 'Hello World';
}
<html ng-app = "testApp">
<head>
<script src="lib/Angular.js"></script>
<script src = "foo.js"></script>
</head>
<body ng-controller="testCtrl">
<div ng-init="getData()" ng-click="setData('Hello Hello')">{{data}}</div>
</body>
</html>

AngularFire – Nested Firebase requests don't work

I've got the following code in a controller:
FirebaseService.$child('tasks').$on('loaded', function(tasks) {
console.log('tasks loaded');
FirebaseService.$child('taskTemplates').$on('loaded', function(taskTemplates) {
console.log('taskTemplates loaded');
});
});
The console only shows tasks loaded and the second console.log is never executed. If I request those children sequential (the request for taskTemplates is not in the loaded-callback) it works seamlessly. Is there a reason for this strange behaviour?
Some background information: When the tasks are loaded, I need to do check something with them and in some case – and only then – I want to load the taskTemplates.
Here's my FirebaseService, if that's helpful:
app
.value('FIREBASE_URL', 'XXXXXXXXXXXX.firebaseIO.com/')
.service('FirebaseService', function($firebase, FIREBASE_URL) {
return $firebase(new Firebase(FIREBASE_URL));
});
Thank you very much for your help!
My first suspicion was that this error looks similar to this series of bugs, one of which is that loaded only triggered once if declared after initial load. Since FirebaseService calls $firebase on the parent path, all the child data is already cached locally, which would cause the loaded event to be fired synchronously (maybe before you've attached your listener and thus triggering the bug listed above).
However, I was unable to reproduce the behavior you've shown above in angularFire versions 0.6.0, 0.7.0, or the latest master branch. Here's my repro:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title></title>
<script src="http://cdn.firebase.com/js/client/1.0.6/firebase.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/0.7.0/angularfire.min.js"></script>
</head>
<body ng-app="app" ng-controller="ctrl">
<ul>
<li ng-repeat="log in logs">{{log}}</li>
</ul>
<script type="text/javascript">
angular.module('app', ['firebase']).controller('ctrl', function($firebase, $scope) {
$scope.logs = [];
var fb = new Firebase('https://kato-sandbox.firebaseio-demo.com/');
var ref = new $firebase(fb);
var childA = ref.$child('alpha');
childA.$on('loaded', function() {
$scope.logs.push('alpha loaded');
var childB = ref.$child('bravo');
childB.$on('loaded', function() {
$scope.logs.push('bravo loaded');
})
})
});
</script>
</body>
</html>
Hopefully that will help you narrow the scope of the error, although it's not in itself a resolution.

angular-ui view nested in a directive does not load view content

if I nest ui-view inside a directive with transclude=true, the view content does not load. It works fine without the intervening directive.
so with a page containing:
<body>
<div ng-controller="MainController">
<div ui-view="sampleView"></div>
</div>
</body>
the sampleView content appears.
But if i put
<body>
<div ng-controller="MainController">
<div sample-directive>
<div ui-view="sampleView"></div>
</div>
</div>
</body>
then the view content doesn't appear.
I have created a simple html page to demonstrate the problem, see below.
As far as I can see, the angular compiling process does correctly call updateView in the ui-view directive in angular-ui, and that does build the view content and insert it in the dom under the sample-directive node, but that doesn't seem to be the actual visible sample-directive node, but a clone of it. i'm guessing it has to do with the order of compilation and therefore I need to do something clever in the directive, but i can't find anything in the angular api help that covers this point.
i've tried adding a post-link function and calling $transclude from there but it makes no difference.
can anyone advise what i need to add to the directive so this will work.
thanks
UPDATE
New info from further investigation:
It seems the cause is this (not at this point a solution, but I can see why it happens).
In angular's function applyDirectivesToNode (angular.js line 5919), if a directive specifies transclude=true, then the original directive node is cloned to make the template node. ie the template is not the original node that's visible in the dom. Now, when the compile function of ui-view in angular-ui-router.js line 2204 is called, it grabs a copy of the parent of the ui-view node, storing it in parentEl. But, and here's where the problem occurs - this parent is the parent in the dom of the ui-view node. what it's most certainly not is the instance of the parent that actually ends up in the dom after linking. Later when the ui-view updates for the initial route change, it builds the view content and inserts it under parentEl (angular-ui-router.js line 2273), but as we saw earlier this isn't in the visible dom after linking. it's the source html node and not the clone created by compiling and linking the directive in which the ui-view is nested.
I think this may be a bug in ui-view.
There may be a way to add a workaround to the directive, to get the post-link view instance and put it into the directive. If I figure it out I'll add an answer.
html to demonstrate the issue as follows:
<!DOCTYPE html>
<html lang="en" ng-app="viewInDirectiveApp">
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
<title>View inside a directive</title>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js"></script>
<script src="modules/angular-ui-router.js"></script>
<script>
var viewInDirectiveApp = angular.module('viewInDirectiveApp', ['ui.router']);
viewInDirectiveApp.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('sampleState', {
url: '/sampleState',
views: {
sampleView: {
template: 'This is the view content',
controller: function ($scope) {
}
}
}
});
$urlRouterProvider.otherwise("/sampleState");
})
.controller('MainController', ['$scope',
function ($scope) {
}])
.directive('sampleDirective', function () {
return {
template: 'Start directive content <div ng-transclude></div> End directive content',
transclude: true
};
});
</script>
</head>
<body>
<div ng-controller="MainController">
Before sampleDirective
<div sample-directive>
Before sampleView
<div ui-view="sampleView"></div>
After sampleView
</div>
After sampleDirective
</div>
</body>
</html>
Confirmed bug in ui-router 0.2.8: https://github.com/angular-ui/ui-router/issues/774
Fixed in 0.2.10: https://github.com/angular-ui/ui-router/pull/858
Plunkers are much appreciated: http://plnkr.co/edit/TZ8hvkSbCIa0dTj0NkcG?p=preview - Seems to work in Angular-routing: http://dotjem.github.io/angular-routing/

Categories

Resources