Display AngularJS array to a 'human' readable string - javascript

I have outputted a variable that is an array within my view and while it is displaying the values as following :-
Inbound bound posts will contain ["phone", "car"]
How do I change this to display this in a human format eg. (as below)
Inbound bound posts will contain phone, car

if you simply want to list the array you can use join:
arr = ["phone", "car"];
arr.join(", ");
would output: "phone, car".

To make array displayed as series of texts
Inbound bound posts will contain {{ ["phone", "car"].join(', ') }}

Create a method on your controller to handle the array->string:
let arrayToWrite = ['phone', 'car'];
aToS() {
return arrayToWrite.join(' ');
}
Then your view:
Inbound bound posts will contain {{ vm.aToS() }}
(assuming 'vm' is your controller, as per standard convention)
You could input the array to the method too, if you need, rather than defining the array outside the method.

There are many alternatives. You could also do this:
<div>
Inbound bound posts will contain
<span ng-repeat="obj in objects">{{obj}} </span>
</div>
and in your controller hold the list on scope:
$scope.objects = ["car", "phone"];

You could use a filter:
myApp.filter('join', function () {
return function join(array, separator, prop) {
if (!Array.isArray(array)) {
return array; // if not array return original - can also throw error
}
return (!!prop ? array.map(function (item) {
return item[prop];
}) : array).join(separator);
};
});
And in your HTML, simply write this:
<div ng-controller="MyCtrl">
Inbound bound posts will contain {{things | join: ', ' }}
</div>
A working JSFiddle: http://jsfiddle.net/Lvc0u55v/12345/

Related

Angular *ngFor over a map is running multiple times but should only run once

Stackblitz Demo of my app
In my app I try to loop over a map (with *ngFor) and display every map key in a new expansion panel and the values in the "body" of the expansion panel:
The map has strings as keys and string arrays as values. After I have filled my map I pass it into the this.showMap. The only reason for this is that I can wait for showMap with *ngIf="showMap" in my HTML to make sure that all items are in the map before I show the Website to the user:
showMap: any;
ngOnInit() {
let myMap: Map<string, string[]> = new Map<string, string[]>();
myMap.set("food", ["apple", "sausage"]);
myMap.set("money", ["bitcoin", "dollar"]);
//... add more key, value pairs dynamically
this.showMap = myMap;
}
In my HTML I use an accordion and expansion-panels from material:
<div *ngIf="showMap">
<mat-accordion>
<mat-expansion-panel hideToggle *ngFor="let key of getKeys(showMap); index as i">
<mat-expansion-panel-header>
<mat-panel-title>
{{ key }}
</mat-panel-title>
</mat-expansion-panel-header>
<p>{{ getValues(showMap)[i] }}</p>
</mat-expansion-panel>
</mat-accordion>
</div>
and the getKeys() and getValues() function look like this and simply return the keys and values of the showMap:
getKeys(myMap: Map<string, string[]>) {
let keys = Array.from(myMap.keys());
console.log("keys: ", keys);
return keys;
}
getValues(myMap: Map<string, string[]>) {
let values = Array.from(myMap.values());
console.log("values: ", values);
return values;
}
My app is running as wanted, but in the console logs I see that the functions getKeys() and getValues() are called multiple times. I'd expect that getKeys() gets called once (because of the *ngFor statement) and the getValues() gets called twice (once for each key). But it's way more, about 10 times?:
It is not consider a good practice call a function on the template, as mentioned by #Raz Ronen, function is executed every time Angular change detection runs. And that can be too many times! I would extract the values and keys into instance variables and use them instead of calling the function directly on the template.
Check out this article about it: https://medium.com/showpad-engineering/why-you-should-never-use-function-calls-in-angular-template-expressions-e1a50f9c0496

How to pass ng-repeat item property to filter?

I created a filter that I want to pass an item property, and not the item itself. Is that possible?
The following does not work (item.param1 fails):
ng-reapeat="item in items | filter : fnFilter(item.param1)"
$scope.fnFilter = function(value) {
return value == "test";
}
Your question is quite unclear sdince you don't tell really what is your goal.
So if i just restrict to what i see there and following the link Angular filter exactly on object key already provided by #CeylanMumumKocabas you would have
ng-repeat="item in items | filter:{'param1': 'test'}"
Now let's consider you want something more complex : the only way i see would be to pass the name of the attribute to the filter :
ng-reapeat="item in items | myFilter:'param1'"
myApp.filter('myFilter', function () {
return function(inputs,attributeName) {
var output = [];
angular.forEach(inputs, function (input) {
if (input[attributeName] == 'test')
output.push(input);
});
return output;
};
});
Note that if you want to go more than one level, you'll have to use $eval or make add some code for this to work.

How to use angular filter on multiple properties of the object

I have a simple ng-repeat like that:
<input type="text" ng-model="vm.filter">
<div ng-repeat="tenant in vm.tenants | filter : vm.filter : vm.contains">
</div>
Now I want to filter tenants based on the value of the filter, e.g. to find if a name of a tenant contains the filter expression like that
function contains(actual, expected) {
return actual.name.indexOf(expected) >= 0;
}
What I do not understand is why I get a tenant.name in the contains function instead of the tenant itself. I know for a simple case I can do something like filter | {name: vm.filter} | vm.contains, but what if I want to do filtering based on multiple properties (e.g. name, phone, etc.)
What I do not understand is why I get a tenant.name in the contains
function instead of the tenant itself.
What's happening is that angular is providing the comparator all levels of the object in an attempt to do a deep filtering i.e. it attempts to match your filter to all hierarchy levels in the object. If you console.log all the values, you'll see that you get the full object too (after all the properties).
One way to make this work would be something like
...
<div ng-repeat="tenant in vm.tenants | filter : vm.contains(vm.filter)">
...
and then
...
contains: function(filter) {
return function(tenant) {
return tenant.name.indexOf(filter) !== -1;
}
}
...
Fiddle - https://jsfiddle.net/81384sd7/
You can use an object to store the filters you want to apply. For example:
$scope.filterBy = {
name: "Bob",
phone: "07"
};
Then configure fields to edit the filters:
<input type="text" ng-model="filterBy.name" />
<input type="text" ng-model="filterBy.phone" />
Then just filter by this object.
<div ng-repeat="tenant in vm.tenants | filter : filterBy">
Take a look at my previous answer which is very similar except also allows selections to be made before a filter being applied manually with a button:
Applying a angularjs filter after "Apply" button click
I hope this helps.
You'll want to test for the presence of both those conditions and return the value if both are present. Something like this:
function contains(actual, expected) {
if (actual.name.indexOf(expected) >= 0 && actual.phone.indexOf(expected) >= 0) {
return actual;
}
}

angularJs exclude already selected items from array

I have an array of objects in $scope.currentSChannels.scgsLink This array of objects have something like
$scope.currentSChannels.scgsLink = [{channelId:1, sCgsLinkId:1, groupNo:1, percentage: 50, expireHrs:4},{channelId:1, sCgsLinkId:2, groupNo:2, percentage:50, expireHrs:1}]
and I also have the following select list
<div class="col-md-4">
<select class="form-control" ng-model="newLink.groupNo"
name="groupNo" id="groupNo"
ng-options="t.value as t.text for t in metaData.spGroups"></select>
</div>
I need to filter that list to not show already selected items in the $scope.currentSChannels.scgsLink groupNo column. I looked at http://christian.fei.ninja/Angular-Filter-already-selected-items-from-ng-options/ and also at AngularJS ng-options to exclude specific object and both seem to be close but not enough as I need to filter against an array and a particular column in that array. How should I implement that filtering?
The template is getting a bit tricky. Assuming selectedLink is the variable that points to the selected groupNo
ng-options="t.value as t.text for t in metaData.spGroups | filter: {value: '!' + currentSChannels.scgsLink[selectedLink].groupNo}"
See this fiddle : the second select contains the same collection as the first one, excluded what is already selected.
Edit: Solution above is for excluding elements according to one value. So as to exclude the elements according to a collection of values, a custom filter would suit best:
Filter
app.filter('channelFilter', function () {
return function (metadata, exclusions) {
var filterFunction = function (metadata) {
// return the metadata object if exclusions array does NOT contain his groupNo
return !exclusions.some(function (exclusion) {
return exclusion.groupNo === metadata.value;
});
};
return metadatas.filter(filterFunction);
};
});
Usage
ng-options="metadata in metadatas | channelFilter: exclusions"
Template
ng-options="t.value as t.text for t in metaData.spGroups | channelFilter: currentSChannels.scgsLink"
Fiddle
That said, would be more efficient to group selected links by groupNo to avoid searches in the array, and filter in the controller.
I wanted to make it a bit more generic, so I've done the following
http://jsfiddle.net/96m4sfu8/
app.filter('excludeFrom', function () {
return function (inputArray, excludeArray, excludeColumnName, inputColumnName) {
if (inputColumnName==undefined)
inputColumnName = 'value';
var filterFunction = function (inputItem) {
return !excludeArray.some(function (excludeItem) {
return excludeItem[excludeColumnName] === inputItem[inputColumnName];
});
};
return inputArray.filter(filterFunction);
};
});

Filter ng-repeat json result against array

At the moment I have a json result which is displayed in an ng-repeat that I would like to have filtered based on a separate object or array of data:
Controller.js
$scope.jsonResult = [
{
"id": "a123"
},
{
"id": "b456"
}
]
HTML
<span ng-repeat="r in jsonResult">{{r.id}}</span>
What I am attempting to achieve is to create a separate array of info and then use this data to filter the ng-repeat results that display in my HTML.
$scope.itemsToFilter = ["b456","foo"]
Since an item within my itemsToFilter array matches an object within my jsonResult scope, I would like that not to display within my ng-repeat within the HTML. Is this something I should write a custom filter for? Sorry I am very new to Angular.
You can create an Angular filter which returns the array filtered by items' ids which match your blacklist:
angular.module("yourAppName")
.filter("filterArrayByBlacklist", function() {
blackList = ["b456","foo"];
return function(array) {
return array.filter(function(item) {
return blackList.indexOf(item.id) === -1; // item id not found in blacklist
});
};
});
You can then filter the ng-repeat by your filter function:
<span ng-repeat="r in jsonResult | filterArrayByBlacklist">{{r.id}}</span>
See this plnkr for a demo: http://plnkr.co/edit/VK3jiVBpL0e1G06WmPZq
The easiest way is by using a custom filter. A quick filter can be written like this:
$scope.filterByStrings = function (item) {
return ($scope.itemsToFilter.indexOf(item.id) === -1);
};
And called like this:
<div ng-repeat="item in data | filter:filterByStrings ">
<p>{{item.id}}</p>
</div>
Here is the fiddle.

Categories

Resources