Behavior of assignment expression invoked by ng-click within ng-repeat - angularjs

I'm attempting to update my model using ng-click attached to a <p>.
I have no problem with an assignment expression outside of an ng-repeat, or with calling a scope method inside the ng-repeat. However, if I use an assignment inside the ng-repeat, it appears to be ignored. I don't see any messages reported in the Firefox console, but haven't tried setting breakpoints to see if the event is being fired.
<!DOCTYPE html>
<html>
<head>
<title>Test of ng-click</title>
<script type='text/javascript' src='http://code.angularjs.org/1.1.1/angular.js'></script>
<script type='text/javascript'>//<![CDATA[
function MyCtrl($scope) {
$scope.selected = "";
$scope.defaultValue = "test";
$scope.values = ["foo", "bar", "baz"];
$scope.doSelect = function(val) {
$scope.selected = val;
}
}
//]]>
</script>
</head>
<body ng-app>
<div ng-controller='MyCtrl'>
<p>Selected = {{selected}}</p>
<hr/>
<p ng-click='selected = defaultValue'>Click me</p>
<hr/>
<p ng-repeat='value in values' ng-click='selected = value'>{{value}}</p>
<hr/>
<p ng-repeat='value in values' ng-click='doSelect(value)'>{{value}}</p>
</div>
</body>
</html>
Fiddle is here, if you prefer (along with a couple of earlier variants).

Directive ngRepeat creates a new scope for each iteration, so you need to reference your variables in parent scope.
Use $parent.selected = value, as in:
<p ng-repeat='value in values' ng-click='$parent.selected = value'>{{value}}</p>
Note: Function call propagates due to prototypal inheritance.
If you want to learn more: The Nuances of Scope Prototypal Inheritance.

As #Stewie mentioned, $parent is one way to solve this issue. However, the recommended (by the Angular team) solution is to not define primitive properties on the $scope. Rather, the $scope should reference your model. Using references also avoids the issue (because primitive properties will not be created on the child scopes which hide/shadow the parent scope properties of the same name), and you don't have to remember when to use $parent:
HTML:
<p>Selected = {{model.selected}}</p>
<hr/>
<p ng-click='model.selected = defaultValue'>Click me</p>
<hr/>
<p ng-repeat='value in values' ng-click='model.selected = value'>{{value}}</p>
<hr/>
<p ng-repeat='value in values' ng-click='doSelect(value)'>{{value}}</p>
JavaScript:
$scope.model = { selected: ""};
...
$scope.doSelect = function (val) {
$scope.model.selected = val;
}
Fiddle.
I recently updated the wiki page that #Stewie mentioned to always recommend this approach.

Related

this vs $scope in AngularJS, 'this' showing peculiar behavior with ng-if

I have following controller and view in AngularJS and I'm trying to learnt the difference between this and $scope.
//index.js
angular.module('angularApp', [])
.controller('indexCtrl', ($scope)=>{
// Initialize variables
$scope.name1 = '';
this.name2 = '';
$scope.greeting1 = `Hello ${$scope.name1}`;
this.greeting2 = `Hi ${this.name2}`;
})
//index.html
<!doctype html>
<html ng-app="angularApp">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.4/angular.min.js"></script>
<script src="index.js"></script>
</head>
<body>
<div ng-controller="indexCtrl as app">
<input
type="text"
ng-model="name1"
placeholder="Using $scope variable name"
/>
<br />
<input
type="text"
ng-model="app.name2"
placeholder="Using this variable name"
/>
<br /><br />
<h2 ng-if="name1 != ''"> Hello {{name1}} </h2>
<br />
<h2 ng-if="app.name2 != ''"> Hi {{app.name2}} </h2>
</div>
</body>
</html>
Ideally, when I first run the localhost, it should not show the 'Hello' or the 'Hi' text because of ng-if. While 'Hello' is not displayed, 'Hi' gets displayed. Why is this happening?
$scope
The scope is the binding part between the HTML (view) and the JavaScript (controller).
The scope is an object with the available properties and methods. It is used to get all the controls on the controller.js files.
The scope is available for both the view and the controller.
this
When the controller constructor function is called, this is the controller.
Within functions defined on $scope, this is set to the $scope in effect where/when the function was called".
<!doctype html>
<html ng-app="angularApp">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.4/angular.min.js"></script>
<script src="index.js"></script>
</head>
<body>
<div ng-controller="indexCtrl as app">
<input
type="text"
ng-model="name1"
placeholder="Using $scope variable name"
/>
<br />
<input
type="text"
ng-model="app.name2"
placeholder="Using this variable name"
/>
<br /><br />
<h2 ng-if="name1 != ''"> Hello {{name1}} </h2>
<br />
<h2 ng-if="app.name2 != ''"> Hi {{app.name2}} </h2>
</div>
</body>
</html>
<script>
angular.module('angularApp', [])
.controller('indexCtrl', ($scope)=>{
// Initialize variables
$scope.name1 = '';
this.name2 = '';
$scope.greeting1 = `Hello ${$scope.name1}`;
this.greeting2 = `Hi ${this.name2}`;
})
</script>
In the controller, you are setting this.name2=''; In this case, this is not on $scope. So app.name2 != '', it equals undefined. It's generally considered better not to use $scope directly, but rather to use the ControllerAs syntax (see this explanation).
$scope is just a JavaScript object like any other, but it is the implicit scope of anything referred to in the template. Therefore if you have something like ng-if="name2 == ''", name2 refers to a field on $scope.
When you use ControllerAs, it creates an object on $scope by that name ($scope.app in your case). $scope.app corresponds to the controller's this. It's exactly the same as if you put $scope.app = this; in the controller's JavaScript. So then, in the controller, you could set this.name2='Harry' and in the HTML, have:
<span> Hi {{app.name2}} </span>
And you would see Hi Harry.
However, you should not use an arrow function to define your controller, since arrow functions do not have their own this scope, but inherit it from their parent. When you define functions on the controller, use this.myFunc = () => {...} and access it in the HTML with app.myFunc, but when defining the controller itself, use the normal function syntax.
To make things simpler, rather than using app in the HTML and this in the controller, it is a good idea to use the same name, by assigning this to a variable. Commonly people use vm (for View Model). So you would have ControllerAs='vm' and then in the JavaScript, set let vm = this; at the top of your controller code.
Then you can refer to vm.name2 (for example) in both your HTML and your JavaScript.

What is the advantage of controller without scope?

Just see the code
var angApp = angular.module('angApp',[]);
angApp.controller('testController', ['$http', function ($http) {
var self = this;
self.name ='Hello';
self.btnClick=function(){
self.name ='Hello! Button Clicked';
}
}]);
<html>
<head>
</head>
<body data-ng-app="angApp" data-ng-controller="testController as model">
<div>
{{model.name}}
</br>
<input type="button" value="Click" data-ng-click="model.btnClick();"/>
</div>
</html>
Now, tell me if we avoid scope and declare controller like this way testController as model then what will be an advantage or is it only syntactic sugar?
Basically, $scope has been removed as of Angular 2. In addition to that, the angular documentation specifically recommends using this instead of $scope.
Take a look at this article for more information: https://johnpapa.net/angularjss-controller-as-and-the-vm-variable/
And also check the accepted answer on this question: 'this' vs $scope in AngularJS controllers
Once advantage I can think about is, if you have nested controllers, for instance
<div ng-controller="myFirstController as ctrl1">
<div ng-controller="mySecondController as ctrl2">
{{ctrl1.someValue}}
</div>
</div>
It is easier and more clear when trying to reference a variable on the parent controller

angularjs ng-if for defined but not valued objects

I have a controller that has a scope object named asd and,
var app = angular.module("app",[]);
app.controller("appCtrl", function($scope){
$scope.asd = {
"name": ""
}
});
And I am using it in html code with ng-if like this.
<div ng-app="app">
<div ng-controller="appCtrl">
<p ng-if="asd.name">
Defined {{asd.name}}
</p>
<p ng-if="!asd.name">
Non Defined
</p>
</div>
</div>
This code writes Non Defined but it defined. Demo
Does ng-if looks for object value? How can I use defined property of an object?
Using angular.isDefined is a good suggestion. In that case, it is best to change your code by removing:
<p ng-if="asd.name">
Defined {{asd.name}}
</p>
<p ng-if="!asd.name">
Non Defined
</p>
And instead replacing it with a binding to a scope variable - then the checking for asd.name can be done inside your controller like this:
$scope.varStatus = "";
if(angular.isDefined($scope.asd.name)){
$scope.varStatus = "Defined" $scope.asd.name;
}
else{
$scope.varStatus = "Non Defined";
}
Then in your template, you do this (notice I only use scope variable varStatus):
<div ng-app="app">
<div ng-controller="appCtrl">
<p>
{{varStatus}}
</p>
</div>
</div>
Give it a try, I hope this helps.

angular call a function and pass id

How can i pas values to a function with ng-init in a angular project?
i have tried this and it works fine:
<div ng-controller="myCtrl" ng-init="myfunction(1)">
but the problem is, when i do this
<div ng-controller="myCtrl" ng-init="myfunction({{id}})">
or
<div ng-controller="myCtrl" ng-init="myfunction(id)">
it doesn't work!!
if i show the value of {{id}} in my template i get the id: 1
so the id does exist.
what is here the problem?
As Brendan Green mentioned, if id is a scope variable you don't need to pass it to the function. Would something like this work?
$scope.myfunction = function(){
// do whatever with $scope.id
}
If you really need to use it as you are your third example should work. Here is a plunker.
Created a fiddle here: http://jsfiddle.net/frishi/HB7LU/22553/
Basically, pass in the variable itself to the function and not the interpolated value, i.e. {{boo}}
<div ng-controller="MyCtrl" ng-init="boo=2">
<div ng-click="foo(boo)">Click Me</div>
</div>
In order to use myfunction in ng-init, the function should be defined in your $scopebecause ng-init evaluate expressions whatever they may be.
So here is what you should do:
HTML:
<body ng-controller="MainCtrl" ng-init='myfunction("john");'>
<p>Hello {{name}}!</p>
</body>
JS:
app.controller('MainCtrl', function($scope) {
$scope.name = 'Mike';
$scope.myfunction = function(otherName){
$scope.name = otherName;
};
});
Example
Keep in mind that it is not recommended to use ng-init this way:
The only appropriate use of ngInit is for aliasing special properties
of ngRepeat, as seen in the demo below. Besides this case, you should
use controllers rather than ngInit to initialize values on a scope.

How to pass JSON response to model for use in GET request

I'm accessing an API which returns some JSON about nutritional information. I'm able to store the response in the model, display it, all that good stuff. I just chuck it into $scope.response = data.item.name but there is another property which I would like to use to form a new request to the server.
I want to take data.item.ndbno, store it in $scope.querySelection = data.item.ndbno and use it as a parameter in the new request. I have tried using ng-value={{item.ndbno}}, and accessing it directly via index in the model alone. Basically for the end resoult, I would like to be able to click on any result in the list, and send a get request with that ndbno to the server. Currently the code sends an undefined ndbno like so
POST http://api.nal.usda.gov/ndb/reports/?ndbno=undefined&type=b&api_key=DEMO_KEY&format=json&callback=JSON_CALLBACK 400 (Bad Request).
I don't know what I'm doing wrong. I'm sorry if this has an obvious solution to someone who's seasoned, but I'm a newbie, and I never used jQuery.
Here's my complete code. And just as a show of gratitude, whomever can help me, I will credit in the credits of the finished app.
<!DOCTYPE html>
<html lang="en" ng-app="sugarApp">
<head>
<meta charset="UTF-8"/>
<title>Sugar App</title>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/angular_material/0.9.4/angular-material.min.css">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.css"/>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular.min.js"></script>
</head>
<body ng-controller="loadingController">
<div class="alert alert-info" ng-show="loading">Loading</div> <!-- show/hide based on the truthy or falsey value of $scope.loading-->
<div ng-controller="sugarController">
<form name="searchForm" ng-submit="doSearch()">
<input type="text" ng-model="queryInput" placeholder="Search a food or food product">
<input type="submit" value="Search">
</form>
<ul>
<div ng-show="query">
Results for '{{baseList.q}}'
<li ng-repeat="item in queryResult">
<!-- the problem : ndbno parameter is not being passed, and as a result the request has an undefined value
tried: baseList.ndbno, item.ndbno, $scope.baseList.ndbno, ng-value, bracket syntax [], other things i'm forgetting-->
<a href ng-click="showResults()">{{item.name}}, {{item.ndbno}}</a>
<span ng-model="ndbnum" ng-bind="item.ndbno"></span>>
</div>
</li>
<md-card>
<md-card-content>
The amount of sugar in <strong>{{prodDetail.nutrients[8].measures[3].eqv * .0353}}</strong>oz of <strong>{{prodDetail.name}}</strong> is <strong>{{prodDetail.nutrients[8].measures[3].value}}g</strong>, or <strong>{{prodDetail.nutrients[8].measures[3].value / 7}} circus peanuts</strong>, or <strong>{{prodDetail.nutrients[8].measures[3].value / 4}} teaspoons</strong> of granulated sugar.
<footer>
<small>Source: National Nutrient Database for Standard Reference - Release {{baseReport.sr}}</small>
</footer>
</md-card-content>
</md-card>
</div>
</ul>
<div ng-show="result"> <!-- show/hide based on the truthy or falsey value of $scope.result-->
The amount of sugar in {{baseList.sr}} of {{prodDetail.name}} is {{prodDetail.amount}} or circus peanuts or teaspoons of granulated sugar.
</div>
</div>
<script>
/* TODO
high
clean up code: remove redundancies
figure out how to pass the ndbno result to $scope.querySelection
low
replace deprecated .success with .then
reformat get requests into factory
*/
angular.module('sugarApp',[]).controller('sugarController',['$scope','$http',function($scope,$http){
$scope.doSearch = function(){
$scope.$emit('LOAD'); //using $emit to signal when/vhen not to show the loading indicator
$scope.$emit('UNQUERY'); //using $emit to signal when/when not to show the search results view
$scope.searchUrl = 'http://api.nal.usda.gov/ndb/search/';
$scope.testUrl = 'http://api.nal.usda.gov/ndb/reports/?ndbno=14400&type=f&format=json&api_key=DEMO_KEY';
$scope.api_key = '&api_key=DEMO_KEY';
$scope.fmtJson = '?format=json';
$scope.q = '&q=';
$scope.callback = '&callback=JSON_CALLBACK';
$scope.query = $scope.queryInput;
$scope.fullUrl= $scope.searchUrl + $scope.fmtJson + $scope.q + $scope.query + $scope.api_key + $scope.callback;
$scope.placeholder = "Search for a food or food product";
//temporary comment out $scope.baseReport = {}; //initialize a blank baseReport array
$http.get($scope.fullUrl)
.success(function(data){
//$scope.baseReport=data.report;
$scope.baseList=data.list;
$scope.queryResult=data.list.item;
$scope.ndbnum = data.list.item.ndbno;
$scope.$emit('UNLOAD');
$scope.$emit('QUERY');
$scope.$emit('NORESULT');
})}
$scope.showResults = function(){
$scope.$emit('UNQUERY');
$scope.$emit('LOAD');
$scope.$emit('RESULT');
$scope.resultBaseUrl = 'http://api.nal.usda.gov/ndb/reports/';
$scope.ndb = '?ndbno=';
$scope.querySelection = $scope.ndbnum;
$scope.rtype = '&type=b'
$scope.fmtJson = '&format=json';
$scope.api_key = '&api_key=DEMO_KEY';
$scope.callback = '&callback=JSON_CALLBACK';
//temporary comment out $scope.baseReport = {};
$scope.resultUrl= $scope.resultBaseUrl + $scope.ndb + $scope.querySelection + $scope.rtype
+ $scope.api_key + $scope.fmtJson + $scope.callback;
$http.post($scope.resultUrl)
.success(function(data){
$scope.baseReport=data.report;
$scope.prodDetail=data.report.food;
$scope.$emit('UNLOAD');
})
}
}])
//setting up scope emit flags for use in showing/hiding view elements
//used for showing or hiding results div
.controller('resultController', ['$scope', function($scope){
$scope.$on('RESULT',function(){$scope.result=true});
$scope.$on('NORESULT',function(){$scope.result=false});
}]) //used for showing or hiding query list view div
.controller('queryController', ['$scope', function($scope){
$scope.$on('QUERY',function(){$scope.query=true});
$scope.$on('NOQUERY',function(){$scope.query=false});
}]) // used for showing or hiding the loading indicator div
.controller('loadingController',['$scope',function($scope){
$scope.$on('LOAD',function(){$scope.loading=true});
$scope.$on('UNLOAD',function(){$scope.loading=false});
}])
</script>
</body>
</html>
In your code you used ndbno as a property of item
$scope.ndbnum = data.list.item.ndbno;
but ndbno is not a direct property of data.list.item instead it's a property of a object which is in array
If wanna access ndbno you have to access via index
like
console.log(data.list.item[0].ndbno);
Or without an index you could try something like this.
The html:
<li ng-repeat="item in queryResult track by $index">
<a href ng-click="showResults(item.ndbno, $index)">{{item.name}}, {{item.ndbno}}</a>
</li>
The angular:
$scope.showResults = function(ndbno, id){
.
.
.
// with an index
$scope.ndbnum = queryResult[id].ndbno
// OR a bit easier without an index
$scope.ndbnum = ndbno
.
.
.
}
It can get overly complicated to track by $index, because each ng-repeat has its own $index, and those can be nested, so it can be difficult to tell which $index is being used in a large template.
I would recommend just passing the value directly to the controller. However, if you need to know which object it did come from then using $index is your next best.
Angular will automatically attach a $index to the ng-repeat scope, and its not necessary to do a track by you can just use $index, but then it gets confusing which $index is being used, figuring out a parents $index can get tricky unless you explicitly set the track by to some other variable name other than $index

Resources