$watch not updating in ng-repeat - angularjs

I'm trying to calculate a total by multiplying 2 ng-models.
The models are in a ng-repeat, where I have a own controller for the rows.
On reload, it does the console log, but when I update the ng-models, they don't console log anymore and just don't work.
The controller:
app.controller('RowCtrl', function ($scope) {
$scope.unit_price = 0;
$scope.quantity = 0;
$scope.$watchCollection('[unit_price, quantity]', function(newValues) {
console.log(parseInt(newValues));
}, true);
$scope.total = $scope.unit_price * $scope.quantity;
});
UPDATED with fiddle: http://jsfiddle.net/r9Gmn/

Watch a function that calculates the total:
$scope.$watch(function() {
return unit_price * quantity;
}, function(newVal) {
$scope.total = newVal;
});

I agree with #pixelbits answer.
Just to add that as of angular 1.3 there is a new scope method $watchGroup:
An example http://plnkr.co/edit/2DvSmxUo5l8jAfBrSylU?p=preview
Solution:
$scope.$watchGroup(['unit_price', 'quantity'], function(val) {
$scope.total = val[0] * val[1];
});

Here's your fiddle working: http://jsfiddle.net/mikeeconroy/r9Gmn/1/
In your $scope.rows array on the controller you never defined the properties to be used in the RowCtrl's scope. Also you should make sure you use track by with ng-repeat so you don't get the dupes error.
var app = angular.module('myApp', []);
app.controller('RowCtrl', function ($scope) {
$scope.total = 0;
$scope.$watchCollection('[row.unit_price, row.quantity]', function(newValues) {
$scope.total = parseInt(newValues[0]) * parseInt(newValues[1]);
});
});
app.controller('MainCtrl', function ($scope) {
$scope.rows = [
{ unit_price: 10, quantity: 0 },
{ unit_price: 12, quantity: 0 },
{ unit_price: 15, quantity: 0 },
];
});

This should work fine (if implemented correctly), i.e. your logic is correct:
<div ng-controller="myCtrl">
Unit price:
<input type="number" ng-model="unit_price" />
<br />
Quantity:
<input type="number" ng-model="quantity" />
<hr />
Total: {{total}}
</div>
app.controller('myCtrl', function ($scope) {
$scope.unit_price = 0;
$scope.quantity = 0;
$scope.$watchCollection('[unit_price, quantity]', function(newValues) {
$scope.total = $scope.unit_price * $scope.quantity;
});
});
See, also, this short demo.

Related

AngularJs ng-repeat does not order time list after scope change

I try to reorder ng-repeat list after $scope change
<div ng-repeat="item in model.itemList | orderBy:order">
<input ng-blur="setOrder('start')"
ng-change="timeSpanChange(item.start,start.newTime);"
ng-model="start.newTime" ng-value="item.start|showTimeSpan"
type="time" />
</div>
And I have angular code:
app.controller('ctl', function ($scope, $http, $timeout) {
$scope.order = 'start';
$scope.model = {
"itemList": [
{ "start":new Date(2000,01,01,10,00,00) },
{ "start":new Date(2000,01,01,11,00,00) },
{ "start":new Date(2000,01,01,12,00,00) }
]
}
$scope.setOrder = function (order) {
$scope.order = order;
};
$scope.timeSpanChange = function (item, time) {
item.Hours = time.getHours();
item.Minutes = time.getMinutes();
};
}).filter('showTimeSpan', function () {
return function (time) {
return String(time.Hours).padStart(2, '0') + ":" + String(time.Minutes).padStart(2, '0');
};
});
I see that model has change after ng-blur, but ng-repeat is still ordered as on first load
What i'm doing wrong, should I refresh scope before reorder?
order should be a property in the itemList. I'm assuming you are not changing the sort order.
<div ng-repeat="item in model.itemList | orderBy:'start'">
The orderBy filter sorts JavaScript Date objects by date and time. Use a custom predicate function to sort by time only:
$scope.timeOnlyFn = function(item) {
var timeOnly = item.datetime.valueOf() % (24*60*60*1000);
return timeOnly;
}
The DEMO
angular.module("app",[])
.controller('ctrl', function ($scope, $http, $timeout) {
$scope.order = 'datetime';
$scope.model = {
"itemList": [
{ "datetime": new Date("2019-02-07T09:00"), name: "Baker" },
{ "datetime": new Date("2019-07-07T08:00"), name: "Charlie" },
{ "datetime": new Date("2019-02-07T10:00"), name: "Adam" }
]
}
$scope.timeOnlyFn = function(item) {
var timeOnly = item.datetime.valueOf() % (24*60*60*1000);
return timeOnly;
}
$scope.setOrder = function (order) {
$scope.order = order;
};
});
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app" ng-controller="ctrl">
<div ng-repeat="item in model.itemList | orderBy:order">
<input ng-model="item.datetime" type="date" />
<input ng-model="item.datetime" type="time" />
{{item.name}}
</div>
<br>
<button ng-click="order='name'">Order by Name</button>
<button ng-click="order='datetime'">Order by DateTime</button>
<button ng-click="order=timeOnlyFn">Order by TimeOnly</button>
</body>
Thanks a lot georgeawg, that's exactly what I need, but I suppose my problem starts in the model.
In fact I'm getting json from controller and it isn't UTC but C# TimeSpan object {"Hours":16,"Minutes":8,"Seconds":45,"Milliseconds":....., that's why i used ng filter to map it with <input type='time'>
Is the properly way to make two-way binding model property with C# time span; or
should I convert it in controller?

Never ending loop through array in AngularJS + setTimeout after each iteration

How can I achieve a never ending loop in AngularJS?
my try:
item_list = [{'id':1, 'photo':'path/src'}, {'id':2, 'photo':'path/src'}, {'id':3, 'photo':'path/src'}];
item_list.map(function(item) {
setTimeout( function () {
setCoverImage(item.photo);
}, 5000);
)
I'm going to change cover image using setCoverImage() every 5 s using data from item_list.
First you should use AngularJS's $interval. Then simply increment a counter and use it to access the current element's photo property in your controller and use ng-src to reflect that URL in your img tag.
<img ng-src="{{myCtrl.item_list[myCtrl.currentIndex].photo}}">
Be careful that you never assign to the counter a value that would not correspond to an element in your array.
if ((that.currentIndex + 1) >= that.item_list.length)
See full example below
angular.module('appModule', []).controller('MyController', ['$scope', '$interval', function($scope, $interval) {
this.item_list = [{
'id': 1,
'photo': 'https://i.pinimg.com/736x/32/76/a2/3276a2111c65b2131ef834736f47162b--birthday-kitten-birthday-hats.jpg'
}, {
'id': 2,
'photo': 'http://img.playbuzz.com/image/upload/f_auto,fl_lossy,q_auto/cdn/154cb38e-55e3-4294-bffe-6906b6a41a6b/c33bcc8b-40be-49c9-bad1-ee85f8275189.jpg'
}, {
'id': 3,
'photo': 'http://4.bp.blogspot.com/-J4ioK5aRks8/Tx8d9D5K54I/AAAAAAAAABM/iTL4sbsNYmc/w1200-h630-p-k-no-nu/Surprised+and+serious+kitten.jpg'
}];
var that = this;
this.currentIndex = 0;
$interval(function() {
if ((that.currentIndex + 1) >= that.item_list.length) {
that.currentIndex = 0;
} else {
that.currentIndex++;
}
}, 5000);
}])
angular.bootstrap(window.document, ['appModule'], {
strictDi: true
});
<div ng-controller="MyController as myCtrl">
<img ng-src="{{myCtrl.item_list[myCtrl.currentIndex].photo}}">
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js"></script>
Use the $interval service of AngularJS like this:
$interval( function () {
setCoverImage(item.photo);
}, 5000);
Inject $interval service in your controller/service (whatever) and then you can use something like this:
let currentPhotoIdx = 0;
const maxAvailablePhotoIdx = item_list.length - 1;
$interval(() => {
setCoverImage(item.photo[currentPhotoIdx]);
currentPhotoIdx === maxAvailablePhotoIdx ? currentPhotoIdx = 0 : currentPhotoIdx++;
}, 5000);
Alternative solution to $interval is $timeout:
function setCoverImageLoop(photo) {
$timeout(function () {
setCoverImage(photo);
setCoverImageLoop(photo);
}, 5000);
}
item_list = [{'id':1, 'photo':'path/src'}, {'id':2, 'photo':'path/src'}, {'id':3, 'photo':'path/src'}];
item_list.map(function(item) {
setCoverImageLoop(item.photo);
});
This is probably a bit old-school, and mainly for people who preferred coming from setTimeout.

Dynamic default values for input box in angularJs

I have a value which i get from a controller and two input box.
I need that whenever i enter any value in one input box. difference of the value retrieved from controller and input box get displayed in the other input box using angularJs.
e.g:-
{{sum}} --> is the value which I get from controller.
<input type="number" ng-model="firstnumber" />
<input type="number" ng-model="secondnumber"/>
What I tried was making service for setting and getting the sum value and watch to change the value every time a value is changed.
My service is :-
angular.module('myapp').service("cmModalService", function($scope){
var sum= ={};
getSum = function(){
return sum;
}
setSum = function(value){
sum=value;
};
});
In a controller I have defined
$scope.$watch('firstnumber', function(newValue, oldValue) {
var sum=cmModalService.getSum();
if(newValue!=null)
{ secondnumber = sum -firstnumber;
}
});
$scope.$watch('secondnumber', function(newValue, oldValue) {
var sum=cmModalService.getSum();
if(newValue!=null)
{ firstnumber = sum -secondnumber;
}
});
But whenever i change values in input box the flow never goes to watch method, and the values doesn't change.
Is there any other method also to achieve this.?
I have tried using ng-change also but still not able to get the exact result.
And inside controller i have defined the change methods as
$scope.changefirstnumber=function(firstnumber, sum){
$scope.secondnumber = sum- firstnumber;
};
$scope.changesecondnumber=function(secondnumber, sum){
$scope.firstnumber= sum- secondnumber;
};
and in html
[Plunker link]
You are not setting the scoped variable. Try this.
$scope.$watch('firstnumber', function(newValue, oldValue) {
var sum=cmModalService.getSum();
if(newValue!=null)
{ $scope.secondnumber = sum -$scope.firstnumber;
}
});
$scope.$watch('secondnumber', function(newValue, oldValue) {
var sum=cmModalService.getSum();
if(newValue!=null)
{ $scope.firstnumber = sum - $scope.secondnumber;
}
});
Working Plunkr
EDIT
With some new information you provided, is this what you're after? http://jsfiddle.net/37gv1kbe/
var app = angular.module('myApp',[]);
app.controller('MyController', function($scope,cmModalService)
{
$scope.firstnumber = 5;
$scope.secondnumber = 10;
$scope.$watch('firstnumber', function()
{
$scope.total = cmModalService.getSum() - $scope.firstnumber
});
$scope.$watch('secondnumber', function()
{
$scope.total = cmModalService.getSum() - $scope.secondnumber;
});
});
app.controller('MySecondController', function($scope,cmModalService)
{
$scope.rand = Math.round(Math.random() * 100);
cmModalService.setSum($scope.rand);
});
app.service('cmModalService', function()
{
var sum;
return {
getSum: function()
{
return sum;
},
setSum: function(value)
{
sum = value;
}
}
});
ORIGINAL ANSWER
regarding my comment, if you need to access the total in your controller, you can just save the val of firstnumber and secondnumber like so
http://jsfiddle.net/pvqm4tcw/
var app = angular.module('myApp',[]);
app.controller('MyController', function($scope)
{
$scope.firstnumber = 5;
$scope.secondnumber = 10;
$scope.$watch('firstnumber', function()
{
$scope.total = $scope.firstnumber + $scope.secondnumber;
});
$scope.$watch('secondnumber', function()
{
$scope.total = $scope.firstnumber + $scope.secondnumber;
});
});
html:
<body ng-app="myApp">
<div ng-controller="MyController">
<input type="number" ng-model="firstnumber" />
<br>
<input type="number" ng-model="secondnumber"/>
<br>
{{total}}
</div>
</body>
If you're using Angular 1.3+ they have a $watchGroup which can make the code even smaller
var app = angular.module('myApp',[]);
app.controller('MyController', function($scope)
{
$scope.firstnumber = 5;
$scope.secondnumber = 10;
$scope.$watchGroup(['firstnumber','secondnumber'], function()
{
$scope.total = $scope.firstnumber + $scope.secondnumber;
});
});

Computed values in angular-schema-form

I have a form which is used to enter a bunch of values. I want to show various calculation on the values, but dynamically, so that when a number is changed the results immediately update. I thought that this should work, but it doesn't - i.e. the calculation is never run:
angular.module('calcs', ['schemaForm'])
.controller('CalcCtrl', function ($scope) {
$scope.schema = {
type: 'object',
properties: {
width: {
type: 'number',
title: 'Width'
},
depth: {
type: 'number',
title: 'Depth'
}
}
};
$scope.form = ['*'];
$scope.model = {};
$scope.$watch('[model.width, model.depth]', function() {
// This function is never called
$scope.area = $scope.model.width * $scope.model.depth;
});
});
I have seen this question, but I am doing quite a number of calculations and I really don't want to have to create a directive for each, so I am hoping there is another way. For reference, here is my template:
<div ng-controller="CalcCtrl">
<form sf-schema="schema" sf-form="form" sf-model="model"></form>
<p>Area: {{area}}</p>
</div>
I believe what you want is $watchCollection:
$scope.$watchCollection('[model.width, model.depth]', function() {
// This function is never called
$scope.area = $scope.model.width * $scope.model.depth;
});
example:
var app = angular.module('app', []);
app.controller('myController', function($scope) {
$scope.model = {}
$scope.$watchCollection('[model.width,model.height]', function() {
$scope.area = $scope.model.width * $scope.model.height;
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="myController">
<input ng-model="model.width">
<input ng-model="model.height">
{{area || ''}}
</div>
<div>

Alert() twice in simple angular app

Alert function is firing off twice, I can't figure out why: http://jsfiddle.net/ntzLrkmz/2/
It's going to alert when a !number is inserted at <input type="number">
EDIT: thanks all, I'll be playing with this useful information
This happens because angular evals the list when the times variable change and $diggest to build the ng-reapeat with the new value. You should use $watch instead:
angular.module('helloApp', [])
.controller('helloController', ['$scope', function ($scope) {
$scope.times = 1;
$scope.$watch('times', function (newValue, oldVaue) {
if (!angular.isNumber(newValue)) {
$scope.times = oldVaue;
alert('ENTER A VALID POSITIVE NUMBER PLEASE');
}
});
$scope.getTimes = function (n) {
if (angular.isNumber(n)) {
return new Array(n);
}
};
}]);
Working example here: http://jsfiddle.net/fals/75uv4ugb/
By restructuring your logic and a slight change to your ng-repeat you can solve this issue:
DEMO EXAMPLE
HTML:
<p ng-repeat="obj in array track by $index" ng-show="name.length">Welcome {{name}} !</p>
JS:
angular.module('helloApp', [])
.controller('helloController', ['$scope', function ($scope) {
$scope.array = [];
$scope.populateArray = function (n, t) {
$scope.array = [];
if (t > 0) {
for (i = 0; i < t; i++) {
$scope.array.push(n);
}
}
}
}]);

Resources