Condition
I have an array of HTML element constructed using ngRepeat.
<li ng-repeat="person in persons">Name: {{ person.name }}. Age {{ person.age }}</li>
In my controller I am trying to update (random) the person's "age" dynamically using Angular $interval.
$scope.persons = [
{name: "John Doe", age: 30},
{name: "Peter Parker", age: 21},
{name: "Clark Kent", age: 45},
{name: "John Wayne", age: 33}
];
var promiseOfYouth = [];
$scope.makeYoung = function() {
//since persons are store in array object.
//loop persons to update dynamically each person age
for(var x = 0; x < $scope.persons.length; x++) {
//check age randomizer already running
if ( angular.isDefined(promiseOfYouth[x]) ) return;
//make them young!
promiseOfYouth[x] = $interval(function() {
console.log('x value in $interval function: '+x);
try {
$scope.persons[x].age = Math.floor(Math.random() * (99 - 17 + 1)) + 17;
} catch(err) {
console.log('"$scope.persons['+x+'].age" is not valid. Error message:'+err.message);
}
}, 100, 50);
}
}
Problem
In the makeYoung function the "x" value always return "4"
Question
How do I passed value of "x" (in this case it should 0,1,2,3) into the $interval function?
For further details please take a look at my jsFiddle example here
That's a classical JavaScript gotcha. You need to have x in a new scope to make this work. Otherwise, all the closures use the value of the same x variable, which is 4 at the end of the loop.
See the jsfiddle
function makePersonYoung(x) {
//check age randomizer already running
if ( angular.isDefined(promiseOfYouth[x]) ) return;
//make them young!
promiseOfYouth[x] = $interval(function() {
$scope.persons[x].age = Math.floor(Math.random() * (99 - 17 + 1)) + 17;
}, 100, 50);
}
$scope.makeYoung = function() {
//since persons are store in array object.
//loop persons to update dynamically each person age
for(var x = 0; x < $scope.persons.length; x++) {
makePersonYoung(x);
}
}
This solution has the added advantage of making the code more readable.
This is a quirk of javascript. That blog post suggests several solutions. Copy the x variable as an immediately executing function:
(function(x2) {promiseOfYouth[x2] = $interval(function() {
console.log('x value in $interval function: '+x2);
try {
$scope.persons[x2].age = Math.floor(Math.random() * (99 - 17 + 1)) + 17;
} catch(err) {
console.log('"$scope.persons['+x2+'].age" is not valid. Error message:'+err.message);
}
}, 100, 50)}(x);
Or bind the the function
promiseOfYouth[x] = $interval(function(x2) {
console.log('x value in $interval function: '+x2);
try {
$scope.persons[x2].age = Math.floor(Math.random() * (99 - 17 + 1)) + 17;
} catch(err) {
console.log('"$scope.persons['+x2+'].age" is not valid. Error message:'+err.message);
}
}.bind(null, x), 100, 50);
you can inject it like a parameter
promiseOfYouth[x] = $interval(function(x) {
console.log('x value in $interval function: '+x);
try {
$scope.persons[x].age = Math.floor(Math.random() * (99 - 17 + 1)) + 17;
} catch(err) {
console.log('"$scope.persons['+x+'].age" is not valid. Error message:'+err.message);
}
}, 100, 50);
fiddle example
Related
I am trying to create pagination for my table that lists the objects returned from my DB as an object. My data structure will look something like:
$scope.myJSONObj = {
app1: {
id: 1,
appName: "appIntegrated1",
status: "Pending"
},
app2: {
id: 2,
appName: "appIntegrated2",
status: "Pending"
},
app3: {
id: 3,
appName: "appIntegrated3",
status: "Completed"
},
app4: {
id: 4,
appName: "appIntegrated4",
status: "Pending"
},
app5: {
id: 5,
appName: "appIntegrated5",
status: "Pending"
},
app6: {
id: 6,
appName: "appIntegrated6",
status: "Pending"
},
app7: {
id: 7,
appName: "appIntegrated7",
status: "Pending"
},
app8: {
id: 8,
appName: "appIntegrated8",
status: "Pending"
},
app9: {
id: 9,
appName: "appIntegrated9",
status: "Pending"
},
app10: {
id: 10,
appName: "appIntegrated10",
status: "Pending"
}
I am trying to split my structure in half, and display the first five results. I have a prev/next button, and when I click next, it should display the next 5 results (in this case the last 5). However, for everything to work, I need to be able to split my object, and so far every method I've researched involves arrays, and objects requiring some hack. I was wondering if I was missing something, or I have to create a solution to work with?
In pure JavaScript :
function getEntries(from, to) {
var entries = [];
for(var key in myJSONObj) {
// extract index after `app`
// var index = key.substring(3);
// Better way : extract index using regular expression, so it will match `something1`, `foo2`, `dummy3`
var index = parseInt(key.replace( /^\D+/g, ''));
if(index >= from && index <= to) {
entries.push(myJSONObj[key]);
}
}
return entries;
}
console.log(getEntries(0, 5));
Try _.chunk
https://lodash.com/docs/4.17.4#chunk
$scope.pages = _.chunk($scope.myJSONObj,5);
$scope.getPage = function( pageIndex ){
return $scope.pages[pageIndex];
}
It's untested - but I wrote a chunk method for you in vanilla JS since you can't use lodash.
function chunk(obj, chunkSize) {
var resultArray = [];
var resultArrayCurrentIndex = 0;
for (var key in obj) {
var item = obj[key];
if (resultArray[resultArrayCurrentIndex].length <= chunkSize) {
if (!resultArray[resultArrayCurrentIndex]) {
resultArray[resultArrayCurrentIndex] = [item];
} else {
resultArray[resultArrayCurrentIndex].push(item)
}
} else {
resultArrayCurrentIndex++
resultArray[resultArrayCurrentIndex] = [item];
}
}
return resultArray;
}
Then you can access it like this:
$scope.pages = chunk(yourObject, 5);
$scope.getPage = function(index){
return $scope.pages[index];
}
EDIT - changed it to accept an obj.
Used Object.keys, Array.prototype.slice and Array.prototype.reduce to solve your issue. Hope this helps
angular.module('app',[])
.controller('TestCtrl', function($scope){
$scope.myJSONObj = {"app1":{"id":1,"appName":"appIntegrated1","status":"Pending"},"app2":{"id":2,"appName":"appIntegrated2","status":"Pending"},"app3":{"id":3,"appName":"appIntegrated3","status":"Completed"},"app4":{"id":4,"appName":"appIntegrated4","status":"Pending"},"app5":{"id":5,"appName":"appIntegrated5","status":"Pending"},"app6":{"id":6,"appName":"appIntegrated6","status":"Pending"},"app7":{"id":7,"appName":"appIntegrated7","status":"Pending"},"app8":{"id":8,"appName":"appIntegrated8","status":"Pending"},"app9":{"id":9,"appName":"appIntegrated9","status":"Pending"},"app10":{"id":10,"appName":"appIntegrated10","status":"Pending"}};
$scope.currentPage = 0;
$scope.pageSize = 5;
$scope.totalPage = Math.ceil( Object.keys($scope.myJSONObj).length/$scope.pageSize);
//pageNumber starts from 0 here
$scope.goToPage = function(pageNumber) {
pageNumber = pageNumber>=0?pageNumber:0;
var from = pageNumber*$scope.pageSize;
var to = from + $scope.pageSize;
return Object.keys($scope.myJSONObj).slice(from,to).reduce(function(a,b){
a[b] = $scope.myJSONObj[b];
return a;
},{});
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="TestCtrl">
<button ng-disabled="currentPage===0" ng-click="currentPage = currentPage - 1">prev</button>
<button ng-disabled="currentPage===totalPage-1" ng-click="currentPage = currentPage + 1">next</button>
<b>Page: {{currentPage+1}}/{{totalPage}}</b>
<pre>{{goToPage(currentPage) | json}}</pre>
</div>
I'm trying to avoid duplicates. If I send the same name (say, Jason) again, I don't want it to be added to the $scope.people for the second time. How can I check it before adding?
$scope.people = [
{
name : "Jason",
age : 26,
height : 176,
},
{
name : "Mark",
age : 34
height : 190
}
];
$scope.add = function(name,age,height) {
// How can I make sure that Jason or Mark won't get added again?
}
If it was a simple array, I would have solved it like below, but this is different.
$scope.add = function (name) {
if ($scope.people.indexOf(name) == -1) {
$scope.people.push(name);
}
};
You can use AngularJS equals like the following example will show you. This is the common AngularJS way to handle such kind of logic. Well, this solution will completly compare two object instead of only comparing one attribute of an object.
$scope.add = function(name, age, height) {
//init
var found = false;
//equal logic
angular.forEach($scope.people, function (people) {
if (angular.equals(people, {
name: name,
age: age,
height: height
})) {
found = true; //set found state
return; // break angular.forEach()
}
});
//proceed
if (!found) {
$scope.people.push({
name: name,
age: age,
height: height
});
}
};
I got this code snippet from an StackOverflow post which I can't find right now. But this should do the trick:
function unique(collection, keyname) {
var output = [],
keys = [];
angular.forEach(collection, function (item) {
var key = item[keyname];
if (keys.indexOf(key) === -1) {
keys.push(key);
output.push(item);
}
});
return output;
};
Usage:
$scope.people = unique(jsonArray, 'name');
$.grep might help you over here
var result = $.grep(people, function(e){ return e.name == x.name; });
result will be an array of matches so you will come to know is there any match or not.
you can use a $filter to easily find existing people like this
$scope.people = [{
name: "Jason",
age: 26,
height: 176,
}, {
name: "Mark",
age: 34,
height: 190
}];
$scope.add = function(name, age, height) {
if(doesntExist(name)){
$scope.people.push({name: name, age: age, height: height})
}
}
function doesntExist(name){
return $filter('filter')($scope.people, {name: name}).length === 0
}
working plunker
http://plnkr.co/edit/4z4ofqTMUH4rMzpoIsI5?p=preview
I've been stuck on something simple for a little bit. I have the following response JSON:
{
"terminalName": "Montreal",
"shipThruLocationCodes":[
{
"shipThruLocationId": 112,
"shipThruLocationCode": "B84"
}
]
}
I have a select where I need to display terminalName (shipThruLocationCode) for each item in the shipThruLocationCodes array, there will only be one terminalName. The data is stored in an array in the controller called $scope.shipThrus. This is what I tried in my ng-repeat but it did not work:
data-ng-options="shipThru.terminalName for shipThru in shipThrus, item.shipThruLocationCode for item in shipThru.shipThruLocationCodes"
I think my idea is correct, but the comma (since I'm trying to display two values) is throwing an error.
So to summarize, the select should show the following for each item
"terminal Name" (shipThruLocationCode)
There will be only one terminal name and can be multiple location codes in the shipThrulocationCodes array.
Use a function to generate the options. Here's a Plunker to show you an example:
https://plnkr.co/edit/hxlowXWCS6BWh6gGfMMl?p=preview
HTML:
<select ng-model="main.selectedOption" ng-options="option.name for option in main.options"></select>
JS:
var app = angular.module('angularApp', []);
app.controller('MainCtrl', function($scope, $http) {
var vm = this;
vm.terminals = [
{
"terminalName": "Montreal",
"shipThruLocationCodes":[
{
"shipThruLocationId": 112,
"shipThruLocationCode": "B84"
}
]
},
{
"terminalName": "Somewhere else",
"shipThruLocationCodes":[
{
"shipThruLocationId": 113,
"shipThruLocationCode": "B9999"
}
]
}
];
vm.options = [];
generateOptions();
function generateOptions() {
for(var i = 0; i < vm.terminals.length; i++) {
var selectOption = {
name: vm.terminals[i].terminalName + " (" + vm.terminals[i].shipThruLocationCodes[0].shipThruLocationCode + ")"
};
vm.options.push(selectOption);
}
}
});
Check Plunker http://plnkr.co/edit/Vs2mC9zt3HmO9KGMzV6D?p=preview
If you have the list as:
$scope.shipThrus = [{
terminalName: "Montreal",
shipThruLocationCodes: [{
shipThruLocationId: 112,
shipThruLocationCode: "B84"
}, {
shipThruLocationId: 112,
shipThruLocationCode: "B89"
}]
}];
Just create this function:
function getLocationCodes(shipThru) {
return shipThru.shipThruLocationCodes.map(function(locationCode) {
return locationCode.shipThruLocationCode;
}).join(', ');
};
Which will parse the locationCodes to B84, B89.
Then parse the shipThrus:
$scope.shipThrus.forEach(function(shipThru) {
shipThru.label = shipThru.terminalName + ' (' + getLocationCodes(shipThru) + ')';
});
Now you can create the select element with the ng-options attribute as:
ng-options="shipThru.shipThruLocationCodes as shipThru.label for shipThru in shipThrus"
I am trying to put together Dygraphs charting library and AngularJS. I have downloaded https://github.com/cdjackson/angular-dygraphs and following the provided example. I was able to have it working with my code. My next step is to assign data to a graph in a callback after data is returned by the server. That is not working well. Have someone any sample of setting up data after it's been returned from the server?
Thanks
<div id="graphdiv" class="col-md-8">
<ng-dygraphs
options="graph.options"
legend="graph.legend"
data="graph.data"/>
</div>
charts.controller('dygraphController', function($scope, chartsService) {
$scope.graph = {
data: [
],
options: {
labels: ["x", "A", "B"]
},
legend: {
series: {
A: {
label: "Series A"
},
B: {
label: "Series B",
format: 3
}
}
}
};
// This function is a callback
function result(data) {
var base_time = Date.parse("2008/07/01");
var num = 24 * 0.25 * 365;
for (var i = 0; i < num; i++) {
$scope.graph.data.push([new Date(base_time + i * 3600 * 1000),
i + 50 * (i % 60), // line
i * (num - i) * 4.0 / num // parabola
]);
};
};
});
I am getting the following in the Console:
Can't plot empty data set
Heights null 320 320 320 320 0 0
Position [object Object]
Using Handlebarjs, I'd like to loop over an array, and display values, separated by a separator. It would be easy if the content I want to display wasn't a template too ;)
Here's my case :
accounts = [
{'name': 'John', 'email': 'john#example.com'},
{'name': 'Malcolm', 'email': 'malcolm#example.com'},
{'name': 'David', 'email': 'david#example.com'}
];
{{#each accounts}}
{{ name }},
{{/each}}
The problem with this implementation is that I will have this output :
John, Malcolm, David,
And I'd like it to be :
John, Malcolm, David
How can I do that ?
You can use CSS pseudo class :after, together with content, to achieve the required "formatting".
(:after and content support in most browser today, and IE8+.)
For example:
HTML:
Foo1
Foo2
Foo3
CSS:
a {
color: red;
}
a:after {
content: ", ";
}
a:last-child:after {
content: "";
}
Result:
Foo1, Foo2, Foo3
I implemented a new foreach helper that can do the trick :
Handlebars.registerHelper('foreach', function (array, fn) {
var total = array.length;
var buffer = '';
//Better performance: http://jsperf.com/for-vs-foreach/2
for (var i = 0, j = total; i < j; i++) {
var item = array[i];
// stick an index property onto the item, starting with 1, may make configurable later
item['_index'] = i+1;
item['_total'] = total;
item['_isFirst'] = (i === 0);
item['_isLast'] = (i === (total - 1));
// show the inside of the block
buffer += fn.fn(item);
}
// return the finished buffer
return buffer;
});
And then :
{{#foreach accounts}}
{{ name }}{{#unless _isLast}}, {{/unless}}
{{/foreach}}