AngularJS private variable in controller function - javascript

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>

Related

Aurelia throttle on printed value with value converter

Aurelia has string interpolation. If you bind a string, number or boolean to that variable, that is bound one-way.
However, if you bind an object and use a ValueConverter, like I want to, it gets bound one-time.
How could I use the value-converter to bind one-way instead of one-time?
I've tried using ${data | objectPrinter & oneWay} but that does not work.
Running code can be found on this gist
app.html
<template>
<div class="row">
<!-- this isn't updated on change -->
<pre>${data | objectPrinter}</pre>
</div>
<input value.two-way="data.branches">
<!-- this gets updated -->
${data.branches}
</template>
app.js
export class App {
data = {
branches: "test"
}
}
export class objectPrinterValueConverter {
toView(obj){
return JSON.stringify(obj, null, 4);
}
}
index.html
<!doctype html>
<html>
<head>
<title>Aurelia</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body aurelia-app>
<h1>Loading...</h1>
<script src="https://cdn.rawgit.com/jdanyow/aurelia-bundle/v1.0.3/jspm_packages/system.js"></script>
<script src="https://cdn.rawgit.com/jdanyow/aurelia-bundle/v1.0.3/config.js"></script>
<script>
System.import('aurelia-bootstrapper');
</script>
</body>
</html>
Aurelia parses the text of your bind expressions into an abstract syntax tree (AST) and uses it to determine which properties to observe. It only observes the properties that are referenced in the expression.
In your example, the binding expression looks like this: data | objectPrinter. The expression accesses the data property on the view-model. This will make Aurelia observe the data property for changes. But it never changes. In your example, the view model's data property remains the same object instance. The only thing that changes is the branches property which wasn't referenced in the objectPrinter expression, so it's updates don't cause that binding to re-evaluate.
Here's another way to create an object-printer:
https://gist.run/?id=9eea8902521f4523ee2c
Related question (ignore the accepted answer- it won't work in your case):
debug Aurelia ViewModel similar to ko.toJson

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>

Calling function inside AngularJS goes in endless loop

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
});

Service not in scope from within an Angular event handler

I've decided to learn Angular.js after having learned JavaScript and jQuery and am finding the going a bit rough. Here's my code.
index.html:
<html>
<head>
<title>Resolute</title>
<script src="js/angular 1.3.0-beta9/angular.js" type="text/javascript"></script>
<script src="js/index.js" type="text/javascript"></script>
</head>
<body ng-app="resoluteApp">
<form novalidation ng-controller="resoluteCtrl" ng-submit="submit()">
<button type="submit">Submit</button>
</form>
</body>
</html>
index.js:
var app = angular.module('resoluteApp', []);
app.service('resoluteService', function myService() {
this.bar = function() { return 'resolute'; };
});
app.controller('resoluteCtrl', function($scope) {
$scope.submit = function() {
alert(resoluteService.bar()); // resoluteService is undefined here
};
});
I would like to be able to call the function bar() defined in the service resoluteService from within the ng-submit event handler $scope.submit(). Upon pressing the Submit button, I get the following error (Chrome 35.0.1916.153) in the console:
ReferenceError: resoluteService is not defined
at Scope.$scope.submit (http://localhost:8383/resolute/js/index.js:9:19)
at http://localhost:8383/resolute/js/angular%201.3.0-beta9/angular.js:10797:21
at http://localhost:8383/resolute/js/angular%201.3.0-beta9/angular.js:19791:17
at Scope.$eval (http://localhost:8383/resolute/js/angular%201.3.0-beta9/angular.js:12699:28)
at Scope.$apply (http://localhost:8383/resolute/js/angular%201.3.0-beta9/angular.js:12797:23)
at HTMLFormElement.<anonymous> (http://localhost:8383/resolute/js/angular%201.3.0-beta9/angular.js:19790:21)
at http://localhost:8383/resolute/js/angular%201.3.0-beta9/angular.js:2860:10
at forEach (http://localhost:8383/resolute/js/angular%201.3.0-beta9/angular.js:327:20)
at HTMLFormElement.eventHandler (http://localhost:8383/resolute/js/angular%201.3.0-beta9/angular.js:2859:5)
As I'm still trying to get my head wrapped around the Angular way of doing things, I am not sure how to direct either resoluteService or the function bar within it to be in scope of $scope.submit. It may be I'm thinking about it in entirely the wrong way, or it may just be something simple I'm missing. Any thoughts as to how I can resolve this?
You need to inject the service in to controller ,
app.controller('resoluteCtrl', function($scope,resoluteService) {
}

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