Value not updating in the view - angularjs

With the code below, the age2 doesn't get updated even though age changes.
<div ng-app="myApp" ng-controller="myController">
<input type="text" ng-model="age" placeholder="Name"></input>
<input type="text" ng-model="firstName" placeholder="First name"></input>
<h1>Hi there, {{ firstName }}, you are {{ age }} year old, age2 is {{ age2 }}!</h1>
var app = angular.module('myApp', []);
app.controller('myController', function($scope){
$scope.age = 20;
$scope.age2 = parseInt($scope.age) + 1;
});
Could anyone tell me what's wrong with it? If I change age2 to a function:
{{ age2() }}
$scope.age2 = function(){return parseInt($scope.age) + 1};
it's working well though. I know that Angular checks if the value has changed, if it stays the same in two consecutive 'checks' then it updates the view. If it takes too long, then probably the values will be changing forever and it throws an error (from what I've understood).

There are a few ways of dealing with it.
Using a watcher:
$scope.$watch('age', function(newVal, oldVal) {
$scope.age2 = parseInt(newVal) + 1;
})
Using a function in the controller:
$scope.parseAge = function(age) {
return parseInt(age) + 1
}
{{ parseAge(age) }}
Or using a filter:
app.filter('parseAge', function() {
return function(input) {
return parseInt(input) + 1;
}
});
{{ age | parseAge }}

To directly answer the question:
In the first case you are only assigning the value of age2 when the controller first initializes. There is no inheritance or watch that would ever change that value.
When it is a function called in the view it will get re-evaluated each digest.
If you are working with a simple operation like what you have you could also do the addition in the view among numerous other ways to make it work
{{ +age +1 }}

Related

Function in ng-bind , multiple call?

I couldnt fint the answer on stack or google...
Why function in ng-bind call many times ?
html:
<li ng-if="byProviders" ng-repeat="(key, value) in byProviderGames | groupBy: 'provider'">
<p ng-bind="providersNames(key)"></p>
</li>
controller:
$scope.providersNames = function providersNames(key) {
// providersObject's length is 8
var index = $scope.providersObject.findIndex(function(x){ return x.name == key });
// Call more then 1000 times
console.log($scope.gamesProviders[index]);
var title = $scope.providersObject[index].title;
return title;
}
From the ngBind source code we can see that this directive registers a ngBindWatchAction callback to change element.textContent whenever the ng-bind attribute changes using scope.$watch:
scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
element.textContent = stringify(value);
});
The watchExpression that is used as a first argument of the scope.$watch method is called on every call to $digest() (at least 1 time) and returns the value that will be watched (but it may be executed multiple times to check if the model was not changed by by other watchers). This is mentioned in the docs:
Be prepared for multiple calls to your watchExpression because it will
execute multiple times in a single $digest cycle if a change is
detected.
In this example $scope.getName method will be called 11 times until its returned value become stable:
angular.module('bindExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.name = 'Arik';
$scope.count = 0;
$scope.getName = function() {
if ($scope.count < 10) { $scope.count++; }
console.log($scope.count);
return $scope.name + $scope.count;
};
}]);
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<body ng-app="bindExample">
<div ng-controller="ExampleController">
Hello <span ng-bind="getName()"></span>!
</div>
</body>
There are a couple things you can do to help with performance and how it affects the number of times the function is called.
First, you can add a track by $index statement to the end of the ng-repeat. It would end up looking like this.
ng-repeat="(key, value) in byProviderGames | groupBy: 'provider' track by $index"
Second, if just displaying data and no other interaction will take place you can unbind them from the scope. So it'll no longer dirty check for changes. It'll end up looking like this.
ng-repeat="(key, value) in ::byProviderGames | groupBy: 'provider'"
Superpower it with both.
ng-repeat="(key, value) in ::byProviderGames | groupBy: 'provider' track by $index"
You can even go as far as to unbind the actual value.
<p ng-bind="::providersNames(key)"></p>
I'm including a code pen, so you can see how many times the function is called. Nowhere as much as you indicated above. Hope it helps. CodePen
Unbind, properly known by (one-time-binding)[https://docs.angularjs.org/guide/expression]
One-time binding
An expression that starts with :: is considered a one-time expression. One-time expressions will stop recalculating once they are stable, which happens after the first digest if the expression result is a non-undefined value (see value stabilization algorithm below).
function exampleController($scope) {
$scope.exampleArray = [1, 2, 3, 4];
var exampleLookupArray = [
{ name: "Schnauzer" },
{ name: "Dachshund" },
{ name: "German Shepard" },
{ name: "Doberman Pinscher" }
];
$scope.breedLookUp = function(key) {
console.count();
return exampleLookupArray[key-1].name;
};
}
angular
.module("example", [])
.controller("exampleController", exampleController);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div class="container-fluid" ng-app="example">
<div class="container" ng-controller="exampleController">
<div ng-repeat="ex in ::exampleArray track by $index">
<span ng-bind="::breedLookUp(ex)"></span>
</div>
</div>
</div>

Share $scope between DOM elements with same ng-controller

I'm stuck with something (I've been reading similar questions but none of them get me to the solution).
I have 2 DOM elements (let's say 2 div's) with different id's but the same ng-controller (this is for basic example, in my real app I have 2 differente pages but works the same).
<div id="layer1" ng-controller="appCtrl">
<select ng-model="selectedType" ng-options="type.label for type in ptype track by type.value" ng-change="changeType(selectedType.value)"></select>
</div>
<div id="layer2" ng-controller="appCtrl">
<select ng-model="selectedType" ng-options="type.label for type in ptype track by type.value" ng-change="changeType(selectedType.value)"></select>
</div>
And in JS
var myAppModule = angular.module('myApp', [])
.factory('selectedType', function(){
return{}
})
.controller('appCtrl', ['$scope',function($scope){
$scope.ptype = [
{
value: 1,
label:'Kg'
},
{
value: 2,
label:'Pza'
}];
selectedType = $scope.ptype[0];
$scope.changeType = function(value){
if(value==1){selectedType = $scope.ptype[0];}
else{selectedType=$scope.ptype[1];}
};
}])
As you can see I have the options for the SELECT and the ng-model, what I need is when change the selected value in any SELECT (doesn't matter which DIV) the other gets updated too.
Here a Plunker with code SEE HERE.
Thanks!
Using a Shared service like your attempt:
you are in the good way but you just forgot that :
1 - you use the scope in ng-model,
2 - to have 2 way data binding for factory,
bind the factory itself to a scope,
and use an item inside the factory not the factory itself (eg: data).
code:
.factory('selectedType', function(){
return {
data: {} // <---- We use 'data' here for example
}
})
and in the controller now:
.controller('appCtrl', ['$scope', 'selectedType',function($scope, selectedType) {
$scope.selectedType = selectedType; // <-- Not the selectedType.data (Important)
/* but the factory object */
$scope.ptype = [
{
value: 1,
label:'Kg'
},
{
value: 2,
label:'Pza'
}];
$scope.selectedType.data = $scope.ptype[0];
})
Now we dont need the ng-change at all:
<div id="layer1" ng-controller="appCtrl">
<select ng-model="selectedType" ng-options="type.label for type in ptype track by type.value"></select>
</div>
<div id="layer2" ng-controller="appCtrl">
<select ng-model="selectedType" ng-options="type.label for type in ptype track by type.value"></select>
</div>
plunker: https://plnkr.co/edit/5qg45F?p=preview
NB: Instead of using shared service, you can also use $rootScope or $scope.$parent for that.

AngularJS - Currency exchange filter with external output

I am trying to build a filter based on this post. I want to be able to switch from one currency to the other by switching a select input with an ng-click on it, but I'm failing to get the input into the filter.
My html is:
<div ng-controller="myCtrl">
you have, {{money | currency}}...<br><br>
</div>
<select>
<option ng-click="currencySymbol = 'USD'; currencyRate=exchange.usd.rate">USD<option>
<option ng-click="currencySymbol = '£'; currencyRate=exchange.pound.rate">£<option>
<option ng-click="currencySymbol = '€'; currencyRate=exchange.euro.rate">€<option>
</select>
And the Angular part:
angular
.module('myApp', [])
.controller('myCtrl', function($scope) {
$scope.money = 100;
$scope.exchange = [
{"usd":{"rate":1}},
{"pound":{"rate":0.702846}},
{"euro":{"rate":0.885055}}
];
})
.filter('currency', function() {
var defaultCurrency = 'USD';
return function(input, currencySymbol, currencyRate) {
var out = "";
currencySymbol = currencySymbol || defaultCurrency;
currencyRate = currencyRate || 1.00;
out = input * currencyRate;
return out + ' ' + currencySymbol;
}
});
You can check the JsFiddle here
Thanks in advance!
Quite a few updates here:
First off, your dropdown is located outside of the controller
You could use the ng-options directive to clean up your select menu
You have ng-click on each option which doesn't work well with the complex model you are trying to maintain
If you pass the whole selected option into the filter, you can manage everything from that one scope variable keeping the data in one place
Here is a working example
http://jsfiddle.net/r0m4n/dLLtzqyr/1/
This will be your simplified html:
<div ng-controller="myCtrl">
you have, {{money | currency: selectedCurrency}}...
<br>
<br>
<select ng-model="selectedCurrency" ng-options="item as item.label for item in exchange">
</select>
</div>
And your js:
angular
.module('myApp', [])
// controller here
.controller('myCtrl', function($scope) {
$scope.money = 100;
$scope.exchange = [{
label: "USD",
rate: 1
}, {
label: "£",
rate: 0.702846
}, {
label: "€",
rate: 0.885055
}];
$scope.selectedCurrency = $scope.exchange[0];
})
.filter('currency', function() {
return function(input, selectedCurrency) {
var out = "";
out = input * selectedCurrency.rate;
return out + ' ' + selectedCurrency.label;
}
});
You define 3 parameter in your filter currency. Whenever you use your filter, you need to give these parameters to your filter. In your case :
<div ng-controller="myCtrl">
you have, {{money | currency:currencySymbol:exchange[2].euro.rate}}...<br><br>
</div>
In AngularJS world, you call your filter after the | and you use : as a separator for parameters (the first parameter input is always given implicitly).
To make the debug easier, you can add this in your filter :
return function(input, currencySymbol, currencyRate) {
console.info('currency parameter', input, currencySymbol, currencyRate);
Please note that I hardcoded the parameter currencyRate because it's not handy to retrieve with the way you store your array.

Get value from HTML page to angularJS file

I tried to get the HTML page value to angularJS function , The below steps are which i tried.
HTML page :
<label class="item-input item-stacked-label">
<span class="input-label cont_det_label">First Name</span>
<p class="contact_display" id="txtFirstName" ng-model="testName">Satya</p>
</label>
angularJS Page :
.controller('SocialNetworkCtrl', ['$scope','$http','$state','ContactsService','$ionicNavBarDelegate','$ionicLoading','$ionicPopup',function($scope, $http, $state, ContactsService, $ionicNavBarDelegate, $ionicLoading,$ionicPopup) {
$scope.showUserProfile = function() {
$state.go("linkedin");
var firstname = (document.getElementById("txtFirstName").value);
}
}])
So I need var firstname = Satya ?? Is it correct way please guide me to access this value .
var firstName = $scope.testName
<input ng-model="testName" />
testName is the ng-model name that you have give. It will be automatically binded to your controller. No need the get the value using document.getElementById
Wrong usage , why ng-model in <p> tag??
Update
Change your fiddle with the following code, it will work. Also make sure framework is selected properly (as in the image)
<div ng-app ng-controller="testController">
<input ng-model="testDataName" ng-change="check()" /> {{testDataName}}
After ng-change : {{checkName}}
</div>
function testController($scope) {
$scope.testDataName="Dummy Name";
$scope.check = function () {
$scope.checkName=$scope.testDataName;
console.log($scope.checkName);
};
}
its a text node, you will require .innerHTML or '.innerText', .value is for form inputs
var firstname = (document.getElementById("txtFirstName").innerHTML);
and don't use ng-model on a p element, change it to like this
<p class="contact_display" id="txtFirstName">{{testName}}</p>
just use $scope.testName to get the value, no need for firstname = (document.getElementById("txtFirstName").innerHTML); querying DOM for value is jQuery style, use angular the $scope for 2 way bindings
Read more at official doc
Update here is updated function on loginCtrl
.controller('loginCtrl', ['$scope', function ($scope) {
$scope.testNameData = 'Satya';
$scope.doLogin = function() {
alert($scope.testNameData);
};
}])
If you really want to go jQuery way here is what you can do, its not recommended, you should use angular directive to do DOM manipulation
$scope.showUserPro = function() {
$ionicLoading.show();
// Here i need the value of <p tag>
var name = document.getElementById("txtFirstName"),
firstNameFromHtmlPtag = name.innerText;
console.log(firstNameFromHtmlPtag, 'Doing API Call 1');
}

Angular.js. Two-way data binding breaks when using services

The problem is that SecondName attribute is not updating when I input text in the field.
please look at this code at jsfiddle: http://jsfiddle.net/HEdJF/253/
HTML:
<div ng-app="myApp">
<div ng-controller="FirstCtrl">
<div>
<input type="text" ng-model="Data.FirstName"><!-- Input entered here -->
<br>FirstName is : <strong>{{Data.FirstName}}</strong><!-- Successfully updates here -->
</div>
<hr>
<div ng-controller="SecondCtrl as Second">
SecondName: {{Second.Data.SecondName}}<!-- How do I automatically updated it here? -->
</div>
</div>
</div>
JS:
var myApp = angular.module('myApp', []);
myApp.service('Data', function(){
var obj;
return obj = { FirstName: '54',
SecondName: '22',
f: function(){
obj.FirstName = '1';
obj.SecondName = obj.FirstName;
}
};
});
myApp.controller('FirstCtrl', function( $scope, Data ){
Data.f();
$scope.Data = Data;
});
myApp.controller('SecondCtrl', function( Data ){
Second = this;
Second.Data = Data;
});
It's not going to work like you think it should. This line:
obj.SecondName = obj.FirstName;
creates a new property SecondName equal by value to the FirstName. However since both properties are primitive types (String) there is no connection between them. In other words obj.SecondName does not reference obj.FirstName.
You have two options.
Option 1. (bad) Set up additional watcher on FirstName change, and once that happens update SecondName respectively
$scope.$watch('Data.FirstName', function() { Data.SecondName = Data.FirstName; });
http://jsfiddle.net/HEdJF/254/
Option 2. Don't introduce additional watchers and change your architecture. For example, use FirstName in the second controller too, since they are supposed to be equal.
http://jsfiddle.net/HEdJF/255/
This is a scoping issue because of your nested scopes. Take a look at this website for a clear explanation: http://toddmotto.com/digging-into-angulars-controller-as-syntax/. There's a few different solutions to solve your problem under the Nested Scopes section.
The problem with your code is that the First Controller is just changing the value of Data.FirstName object, hence the changes are not reflecting on your second controller because the value of SecondName does not change after it is initialized in the first controller. So you have to set your Data.SecondName
in your data as well.
Alternatively, you can do this.
<div>
<input type="text" ng-model="Data.FirstName"><!-- Input entered here -->
<br>FirstName is : <strong>{{Data.SecondName=Data.FirstName}}</strong><!-- Successfully updates here -->
</div>
You can also use directives to achieve this functionality, but I guess you are just looking for the above solution.
Cheers!

Resources