Building AngularJS variable name during ng-repeat - javascript

I have a set of items that come in as follows. (I have no control over the variable's name)
pubDate0, pubDate1, pubDate2
They are accessed like so
<div>
<i>
{{newsData.pubDate0[0]}}
</i>
</div>
<div>
<i>
{{newsData.pubDate1[0]}}
</i>
</div>
<div>
<i>
{{newsData.pubDate2[0]}}
</i>
</div>
Is there anyway to concatenate this variable name using ng-repeat so that I do not have to write all of this repetitive code?
I have tried the following inside a ng-repeat, and many more similar combinations.
<p ng-repeat="t in getTimes(10) track by $index"> //force looped 10 times
{{(newsData.pubDate+$index+[0])}}
</p>
//Tried the following as well
{{(newsData.pubDate+$index+[0])}}
{{('newsData.pubDate'+$index+[0])}}
{{('newsData.pubDate'+$index+'[0]')}}
{{newsData.pubDate+$index+[0]}}
{{newsData.pubDate+($index)+[0]}}
{{newsData.pubDate+{{$index}}+[0]}}
{{newsData.pubDate($index)[0]}}
{{newsData.pubDate$index[0]}}
{{newsData.pubDate{{$index}}[0]}}
{{newsData.pubDate+$index+[0]}}
Running out of guesses. :(

You have two ways to access a js property:
var obj = {prop1: 'p1', prop2: 'p2'};
console.log(obj.prop1); //p1
var propName = 'prop';
var index = 1;
console.log(obj[propName + index]); //p1
Simply, use the second way:
newsData['pubDate'+$index][0];
JSFIDDLE.

You can achieve simply using new Array(10) if you know the length see the below code;
//Controller:
$scope.getTimes = function(x) {
return new Array(x);
}
<p ng-repeat="t in getTimes(10) track by $index"> //force looped 10 times
{{(newsData['pubDate'+$index][0])}}
</p>
But I would convert the data the way I wanted just before resolving the promise (or assigning the data to the scope) and if you use the data all over your application I definitely suggest that.
Assuming the below code is your service call;
var getAllNews = function() {
var deferred = $q.defer();
$http.get('/api/news')
.success(function (resultNews) {
var pubDates = []; //will be new pubDate array
for (var key in resultNews) {
//check if it is a pubDate
if (key.indexOf("pubDate") === 0) {
pubDates.push(resultNews[key]); // add it to array
delete resultNews[key]; // remove from the object
}
}
//finally assign new array as a property of result
resultNews.pubDates = pubDates;
deferred.resolve(resultNews);
});
return deferred.promise;
};
But the second approach might have some problem related to order of items as browsers new promise to return them in order (even they are).

Related

angularjs : filter array with unsaved changes by name using an input tag

I normally filter an array using input tag like this :
$scope.filtername = function (vname) {
//repopulate the array from a copy every time input changes
$scope.items = $scope.items_filter;
//filter array if vname is a substring from item.name
if (vname && vname.trim() != '') {
$scope.items = $scope.items.filter((item) => {
return (item.name.toLowerCase().indexOf(vname.toLowerCase()) > -1);
})
}
};
<input type="search" ng-model="namev" ng-change="filtername(namev)">
But since the user needs to filter the array while editing hundreds of items with unsaved changes, I'm not sure how to proceed. Should I use ng-show instead ? or is there a better vanilla javascript way?
do you have a better way to filter array using a search input?
One approach is to create a custom filter:
app.filter("myFilter", function() {
return function(inputArr,vname) {
//repopulate the array from a copy every time input changes
var result = inputArr.concat();
//filter array if vname is a substring from item.name
if (vname && vname.trim() != '') {
result = result.filter((item) => {
return (item.name.toLowerCase().indexOf(vname.toLowerCase()) > -1);
});
};
return result;
};
})
Usage:
<input type="text" ng-model="namev" />
<div ng-repeat="item in items | myFilter : namev">
{{item.name}} {{item.contents}}
</div>
For more information, see AngularJS Developer Guide - Creating Custom Filters
can you explain the purpose of inputArr ?
The first argument to the filter function is the array to be filtered. This way it can be used with other scope variables:
<div ng-repeat="item in items | myFilter : namev">
{{item.name}} {{item.contents}}
</div>
<div ng-repeat="o in otherItems | myFilter : namev">
{{o.name}} {{o.contents}}
</div>
When AngularJS pipes data to a filter, it invokes the filter function with the data as the first argument. Subsequent arguments come from expressions separated by colons (:).
For more information, see AngularJS Developer Guide - Using filters in view templates

ng-repeat track by $index display the item only once if the similar key exists multiple times in angularjs

I was getting [ngRepeat:dupes]: error in my angularjs code.
So I searched on stackoverflow and found the solution which stated to use track by $index. This is working for me now. But, it displays the item multiple times if the same key is present multiple times. I want to display only one item if the same key exists multiple times.
here is my sample code from the view :
<div ng-repeat="user in users | filter:myFilter track by $index">
</div>
here is the current output :
Insted of showing these two cards, I want the code to show only one card as they both are the same.
How do I do it?
pass array into a function and create an array without the repeating values
like myFunction(users) so the repeat section will become ng-repeat="user in myFunction(users)". Write the function yourself.
I fixed it like this by creating a filter:
var app = angular.module('angularTable', []);
app.filter('unique', function() {
// we will return a function which will take in a collection
// and a keyname
return function(collection, keyname) {
// we define our output and keys array;
var output = [],
keys = [];
// we utilize angular's foreach function
// this takes in our original collection and an iterator function
angular.forEach(collection, function(item) {
// we check to see whether our object exists
var key = item[keyname];
// if it's not already part of our keys array
if(keys.indexOf(key) === -1) {
// add it to our keys array
keys.push(key);
// push this item to our final output array
output.push(item);
}
});
// return our array which should be devoid of
// any duplicates
return output;
};
});
app.controller('listdata', function($scope, $http) {
your controller logic for the users array
});
});
In the view :
<div ng-repeat="user in users |unique:'name' | filter:myFilter track by $index">
</div>

Angular2 bind ngModel from ngFor

So I'm rendering my textarea dynamically using ngFor however I'm not sure how I can pass the ngModel to bind it in my function.
<div *ngFor="let inputSearch of searchBoxCount; let i = index" [ngClass]="{'col-sm-3': swaggerParamLength=='3', 'col-sm-9': swaggerParamLength=='1'}">
<textarea name="{{inputSearch.name}}" id="{{inputSearch.name}}" rows="3" class="search-area-txt" attr.placeholder="Search Product {{inputSearch.name}}"
[(ngModel)]="inputSearch.name"></textarea>
</div>
textarea example:
textarea is render based on the length of the response I get from api call in my case searchBoxCount is basically searchBoxCount.length, so if it length is = 1 then it will only render 1 textarea if its 3 then it will show 3 textareas. The objs have different names (example: id/email/whatever), so ngModel is based on the obj name from the json object.
How do I bind inputSearch.name to my function getQueryString()
getQueryString() {
this.isLoading = true;
let idInputValue = inputSearch.name; //bind it here
return "?id=" + idInputValue
.split("\n") // Search values are separated by newline and put it in array collection.
.filter(function(str) {
return str !== ""
})
.join("&id=");
}
Search func where getQueryString() is called
searchProduct() {
let queryString1 = this.getQueryString();
this._searchService.getProduct(queryString1)
.subscribe(data => {
console.log(data);
});
}
I know how to do it if the ngModel is not coming from the ngFor, is there another way to get the value from the textarea without ngModel? maybe that's the only way or if I can still use ngModel.
Summary of current state
First, let me summarize where your data is. You have a list of one or more objects named searchBoxCount. Each of the elements in the list is an object which has a name property, so you could, for example, call let name = this.searchBoxCount[0].name; to get the name of the first object in the list.
In the HTML template you use ngFor to loop through all of the objects in the searchBoxCount list, and in each iteration you assign the object to a local (to the ngFor) variable named inputSearch. You then bind the input from the textarea created in each loop iteration to the name property for that iteration's inputSearch object.
How to get your data
The key here is that the inputSearch is the same Object as is stored in searchBoxCount at some particular index (index 0 for the first object, etc...). So when the ngModel is tied to inputSearch.name it is also bout to searchBoxCount[n].name. External to the ngFor, you would loop through the searchBoxCount list to get each name you need.
As a consequence
Based on the comments on the original post, it sounds like you can have one or
more names that you need to include in the query string output. That means for your getQueryString() to work, you have to loop through the list (or as in this case, let the list loop for us):
getQueryString() {
this.isLoading = true;
let result : string = "?id=";
this.searchBoxCount.forEach(
(inputSearch:any) => { //Not the same variable, but same objects as in the ngFor
result = result + inputSearch.name + "&id=";
});
result = result.slice(0, result.length - 4); //trim off the last &id=
return result;
}
Edit: Multiple different fields with different names
From the comments on this post, it now is clear each inputSearch has its own key to be used in the query string, that is stored in the name property. You need to preserve that name, which means you can't bind the ngModel to it. Otherwise the user will destroy the name by typing in their own text and there will be no way to get the correct key back. To that end, you need to store bind the ngModel to some other property of the inputSearch object. I am going to assume the object has a value property, so it looks like this:
{
name: "id",
value: "33\n44"
}
That is, each inputSearch has a name, and the value will have one or more values, separated by new line. You would then have to change the HTML template to this:
<div *ngFor="let inputSearch of searchBoxCount; let i = index"
[ngClass]="{'col-sm-3': swaggerParamLength=='3', 'col-sm-9':
swaggerParamLength=='1'}">
<textarea name="{{inputSearch.name}}"
id="{{inputSearch.name}}" rows="3" class="search-area-txt"
attr.placeholder="Search Product {{inputSearch.name}}"
[(ngModel)]="inputSearch.value"></textarea>
</div>
Notice that I changed the ngModel from inputSearch.name to inputSearch?.value (the ? allows for null if there is no value to begin with) inputSearch.value. The getQueryString() method then looks something like this:
getQueryString() {
let result:string = "?";
//for each of the input search terms...
this.searchBoxCount.forEach( (inputSearch:any) => {
// first reparse the input values to individual key value pairs
let inputValues:string = inputSearch.value.split("\n")
.filter(function(str) { return str !== "" })
.join("&" + inputSearch.name + "=");
// then add it to the overall query string for all searches
result = result +
inputSearch.name +
"=" +
inputValues +
"&"
});
// remove trailing '&'
result = result.slice(0, result.length - 1);
return result;
}
Note, using RxJs this is probably easier but I am testing vanilla javascript.
Using this, if the user entered two IDs (33 and 44), a single sku, and two emails, the result would be ?id=33&id=24&sku=abc123&email=name#compa.ny&email=an.other#compa.ny

Custom filter on 'ng-repeat' does overwrite the scope

My aim is to replace the teacher-id(f_teacher) of one outputted array with the teacher name of another array. I wrote a custom filter, that should do the job:
angular.module('core')
.filter('replaceId', function () { //filter, which replaces Id's of one array, with corresponding content of another array
return function (t_D, s_D, t_prop, s_prop) { //data of target, data of source, target property, source property
var replacment = {};
var output = [];
angular.forEach(s_D, function (item) {
replacment[item.id] = item[s_prop]; //replacment - object is filled with 'id' as key and corresponding value
});
angular.forEach(t_D, function (item) {
item[t_prop] = replacment[item[t_prop]]; //ids of target data are replaced with matching value
output.push(item);
});
return output;
}
});
I use a 'ng-repeat' like this:
<tr ng-repeat="class in $ctrl.classes | filter:$ctrl.search | replaceId:$ctrl.teachers:'f_teacher':'prename' | orderBy:sortType:sortReverse">
<td>{{class.level}}</td>
<td>{{class.classNR}}</td>
<td>{{class.f_teacher}}</td>
</tr>
But it only outputs an empty column. Now the strange thing: If I follow the steps with the debugger, it works for the first time the filter is performed. But when it is performed a second time it outputs an empty column.
I noticed that the returned object of the filter overwrites the $ctrl.classes - array, but normally this shouldn't be the case?
Here is a plnkr:
https://plnkr.co/edit/EiW59gbcLI5XmHCS6dIs?p=preview
Why is this happening?
Thank you for your time :)
The first time through your filter the code takes the f_teacher id and replaces it with the teacher name. The second time through it tries to do the same thing except now instead of getting a teachers ID in f_teacher it finds the teacher's name so it doesn't work. You could fix it by making a copy of the classes instead of modifying them directly. e.g.
angular.forEach(t_D, function (item) {
var itemCopy = angular.copy(item);
itemCopy[t_prop] = replacment[itemCopy[t_prop]];
output.push(itemCopy);
});
https://plnkr.co/edit/RDvBGITSAis3da6sWnyi?p=preview
EDIT
Original solution will trigger an infinite digest because the filter returns new instances of objects every time it runs which will cause angular to think something has changed and retrigger a digest. Could you just have a getter function that gets a teachers name instead of using a filter?
$scope.getTeacherName = function(id) {
var matchingTeachers = $scope.teachers.filter(function(teacher) {
return teacher.id == id;
})
//Should always be exactly 1 match.
return matchingTeachers[0].prename;
};
And then in the HTML you could use it like
<tr ng-repeat="class in classes">
<td>{{class.level}}</td>
<td>{{class.classNR}}</td>
<td>{{getTeacherName(class.f_teacher)}}</td>
</tr>
https://plnkr.co/edit/gtu03gQHlRIMsh9vxr1c?p=preview

angularjs function failing because of extra $$hashKey

I have the below HTML:
<li ng-click="toggleBeep(beep)" ng-class-odd="'gradient-two'"
ng-class-even="'gradient-three'" ng-repeat="beep in beeps">
<span>{{beep.name}}</span>
<label class="bold" ng-show="isSelected(beep)">selected</label>
</li>
JavaScript (AngularJS):
$scope.beeps = $sounds.getAll();
// get stored beep from localStorage
var notification_beep =
angular.fromJson(localStorage.getItem('notification_beep'));
console.log($scope.beeps[0]);
console.log(notification_beep);
// handle change sound on click event
$scope.toggleBeep = function (beep) {
$cbSounds.play(beep.file);
$scope.selected = beep;
localStorage.setItem('notification_beep', angular.toJson(beep));
};
$scope.isSelected = function (beep) {
return $scope.selected === beep;
};
Now, when I click on any li I get the selected label is shown because of the $scope.isSelected function. However, when I try to add this line $scope.selected = notification_beep which is the beep object stored in the localStorage the label is not shown and I get the below return values.
The only difference I could spot is that $$hashkey is present on $scope.beeps[0] while it's not on notification_beep. Could this be the cause? Thanks.
The following comparison:
$scope.selected === beep
Will only return true if the two variables reference the same object.
The following line will create a new object:
var notification_beep = angular.fromJson(localStorage.getItem('notification_beep'));
So it will not reference the same object as $scope.selected.
To clarify, this will return false: { name: 'Beep 1' } === { name: 'Beep 1' }
The simplest solution is to instead compare against a unique primtive of the objects.
For example:
return $scope.selected.name === beep.name;
The $$hashkey property is inserted into the object by ng-repeat and is used to track which object corresponds to which DOM template. The reason it doesn't exist in notification_beep is because angular.toJson removes the property from the object.

Categories