AngularJS filtering in multiple arrays - angularjs

I'm trying to apply filter on a list of objects but I can't manage to make it work. I've read that AngularJS does not provide "out of box" multiples objects filtering, may be that's why it's not working?
Here is my code:
<div class="list list-inset">
<label class="item item-input" id="events-search">
<i class="icon ion-search placeholder-icon"></i>
<input type="text" placeholder="Rechercher parmis les evenements" ng-model="nsearch">
</label>
</div>
<div class="list">
<a class="item item-thumbnail-left" href="#" ng-repeat="info in infos | filter:nsearch">
<img src="xxx">
<h2>{{ info.name }}</h2>
<p>{{ info.date}} à {{ info.hour }}</p>
</a>
</div>
For example, "infos" value would be something like:
Q5PAvIQ2x8TLNkZmhTr59s984ALI5s10 {
name: "This is an event",
...
},
jj8oB6WemYVsGZ1FSm31DFBtSlM0pfZK {
name: "This is a second event",
...
}
I'm trying to filter by name.
Does anyone have an idea?... thanks!

If your data is in a hash table rather than an actual array then you need to use the key, value notation for accessing the data in a ng-repeat:
<a class="item item-thumbnail-left" href="#" ng-repeat="(key, value) in searchedInfos()">
<h2>{{ value.name }}</h2>
</a>
In such arrangement angular's filter can not be simply applied to a non array set, so you have a create a custom filtering function on the your scope which would take it's value from a an input:
<input type="text" ng-model="view.searchStr"/>
and on the scope:
$scope.infos = {
Q5PAvIQ2x8TLNkZmhTr59s984ALI5s10: {
name: "This is an event"
},
jj8oB6WemYVsGZ1FSm31DFBtSlM0pfZK: {
name: "This is a second event"
}
};
$scope.view = {
searchStr : ""
}
$scope.searchedInfos = function(){
var searched = {};
if ($scope.view.searchStr) {
angular.forEach($scope.infos, function(value, key){
if (value.name.toLowerCase().indexOf($scope.view.searchStr.toLowerCase()) != -1) {
searched[key] = value;
}
});
}
else {
searched = $scope.infos;
}
return searched;
}
Here is a working fiddle

if infos will be an array of objects and you want to filter by the name not all the object variables in the system so all you need to do is to convert the search variable to be an object and but in it the same variable name that you want to search with
in your code you only need to convert the ng-model of the input to be like this
<input type="text" placeholder="Rechercher parmis les evenements" ng-model="nsearch.name">
and complete the rest of the code as it is

As the name is variable and unusable in a repeat you could reference by index
<h2>{{ info[0].name }}</h2>
<p>{{ info[0].date}} à {{ info[0].hour }}</p>
In your 'something like this' json array you probably need a comma
[Q5PAvIQ2x8TLNkZmhTr59s984ALI5s10 , {
name: "This is an event",
...},
jj8oB6WemYVsGZ1FSm31DFBtSlM0pfZK , {
name: "This is a second event",
...},
...]
, each info element will only has 1 element and can be referenced with [0]

Related

Binding values from ng-repeat in Angularjs

I'm loading data from a database and save it to an array as follows:
$scope.allGroups = [{id: 1, groupName: group1}, {id: 2, groupName: group2}, ..]
In my view, I'm trying to select multiple group names as follows:
<div
class="drag-container avaliable-groups-connect groups-container schedule-container"
>
<div class="group" ng-repeat="m in allGroups">
<input type="checkbox" ng-model="m.selected" />
<span>{{ m.groupName }}</span>
</div>
</div>
I want to save selected items (m.selected) to an array one-by-one and bind that array to ng-model="schedule.selectedGroups"
How can I perform that? Thank you..
<div class="group" ng-repeat="m in allGroups" ng-init="m.selected = false">
<input type="checkbox" ng-model="m.selected" />
<span>{{ m.groupName }}</span>
</div>
Now your checkbox ng-model with modify the variable to true or false and in your js code
you can do like below.
$scope.schedule.selectedGroups = $scope.allGroups.filter(function (data) {
return data.selected === true;
});

Storing dynamic form field as an array in angular

question.component.ts
ngOnInit() {
this.challengeId = this.route.snapshot.paramMap.get('challengeId');
// this.selectedNoOfAttempt = this.noofattempts[1];
this.myForm = this._fb.group({
question: ['', [Validators.required]],
correctAnswers: this._fb.array([
this.initCorrectAnswer(),
]),
selectedNoOfAttempt: ['', [Validators.required]]
});
}
initCorrectAnswer() {
return this._fb.group({
correctAnswers: ['', Validators.required],
});
}
addAnswer() {
const control = <FormArray>this.myForm.controls['correctAnswers'];
control.push(this.initCorrectAnswer());
}
removeAnswer(i: number) {
const control = <FormArray>this.myForm.controls['correctAnswers'];
control.removeAt(i);
}
question.component.html
<div formArrayName="correctAnswers">
<div *ngFor="let correctAnswers of myForm.controls.correctAnswers.controls; let i=index" class="panel panel-default">
<div class="panel-heading">
<span>Answer {{i + 1}}</span>
<span class="glyphicon glyphicon-remove pull-right" *ngIf="myForm.controls.correctAnswers.controls.length > 1" (click)="removeAnswer(i)">Remove</span>
</div>
<div class="panel-body" [formGroupName]="i">
<div class="form-group col-xs-3">
<input type="text" class="form-control" formControlName="correctAnswers">
<small [hidden]="myForm.controls.correctAnswers.controls[i].controls.correctAnswers.valid" class="text-danger">
Answer is required
</small>
</div>
</div>
</div>
</div>
<div class="margin-20">
<a (click)="addAnswer()" style="cursor: default">
Add another answer +
</a>
</div>
The code above works as it is intended, dynamically insert more fields if needed, and also remove. It also able to return all the dynamic fields, however it is storing the data as
correctAnswers (array)
[0] (object)
correctAnswers: "test" (string)
[1] (object)
correctAnswers: "abc" (string)
I would like to store the data as follow
correctAnswers (array)
[0] "test" (string)
[1] "abc" (string)
In your initCorrectAnswer() function you are adding form group with child form control to your form array, that is why you see it as array of objects. Instead of this, you should add form control:
initCorrectAnswer() {
return this._fb.control('', Validators.required);
}
I also added correctAnswers getter for convenient use in HTML:
get correctAnswers(): FormArray {
return this.myForm.get('correctAnswers') as FormArray;
}
updated add and remove functions:
addAnswer() {
this.correctAnswers.push(this.initCorrectAnswer());
}
removeAnswer(i: number) {
this.correctAnswers.removeAt(i);
}
here is HTML:
<form [formGroup]="myForm">
<div formArrayName="correctAnswers">
<div class="panel panel-default"
*ngFor="let correctAnswer of correctAnswers.controls; index as i">
<div class="panel-heading">
<span>Answer {{i + 1}}</span>
<span class="glyphicon glyphicon-remove pull-right"
*ngIf="correctAnswers.controls.length > 1"
(click)="removeAnswer(i)">Remove</span>
</div>
<div class="panel-body">
<div class="form-group col-xs-3">
<input type="text" class="form-control" [formControlName]="i">
<small [hidden]="correctAnswer.valid" class="text-danger">
Answer is required
</small>
</div>
</div>
</div>
</div>
</form>
StackBlitz: https://stackblitz.com/edit/angular-643srq?file=app%2Fapp.component.html
Official reference
There is no way for you to achieve it through Angular's reactive form, because every form group that is repeated (via your *ngFor) is an object. Note that your form group name is actually your *ngFor index: formGroupName = i; and that your form control is correctAnswers - that corresponds to your object property with name correctAnswers as you have expected it.
You can however, do some post processing though. Simply use an array map() to flatten your object into arrays:
var flattenedArray = this.myForm.correctAnswers.map(x=>x.correctAnswers);

Angular filter results when user clicks on repeat

I am trying to create an Angular filter that allows users to click on a record and have the filter then show only that one record within the same repeater.
For example, when a person searches for last name "knight," they see some results. Then, when the user clicks on the specific record they want, they should see only that one record displaying in the repeater.
My html is this:
<md-list-item class="md-3-line" ng-repeat="value in ctrl.results" ng-click="ctrl.showRecord(value)">
<div class="md-list-item-text" layout="column">
<h4>Employee Id: <b>{{ value.employeeId }}</b></h4>
<h4>NetId: <b>{{ value.netId }}</b></h4>
<p>Name: {{ value.preferredFirstName }} {{ value.preferredLastName }}</p>
</div>
</md-list-item>
and it passes the selected record to my controller in this function:
vm.showRecord = function (selectedRecord, ev) {
//need my filter here
};
I have looked over a number of the examples on filters, but I don't quite see how to make the filter update the very same repeat that the user clicked on.
** Edit to show answer based on Tom Chen's work **
For anyone doing this with controller as syntax, here is the answer.
html:
<md-list-item class="md-3-line" ng-repeat="value in ctrl.results | filter:ctrl.selectedId" ng-click="ctrl.showRecord(value.employeeId)">
<div class="md-list-item-text" layout="column">
<h4>Employee Id: <b>{{ value.employeeId }}</b></h4>
<h4>NetId: <b>{{ value.netId }}</b></h4>
<p>Name: {{ value.preferredFirstName }} {{ value.preferredLastName }}</p>
</div>
</md-list-item>
controller:
vm.showRecord = function (selectedRecord) {
vm.selectedId = selectedRecord;
};
You can achieve this simply by using angular filter expression like this:
<md-list-item class="md-3-line" ng-repeat="value in ctrl.results | filter:vm.selectedId" ng-click="ctrl.showRecord(value.employeeId)">
<div class="md-list-item-text" layout="column">
<h4>Employee Id: <b>{{ value.employeeId }}</b></h4>
<h4>NetId: <b>{{ value.netId }}</b></h4>
<p>Name: {{ value.preferredFirstName }} {{ value.preferredLastName }}</p>
</div>
</md-list-item>
and in your controller :
vm.showRecord = function (id) {
vm.selectedId = id;
};
UPDATED ANSWER WITH WORKING EXAMPLE
Here is an example in Plunker
You could do it with an ng-if
<md-list-item ng-if="!ctrl.selectedRecord || ctrl.selectedRecord === value" ng-repeat="value in ctrl.results" ng-click="ctrl.showRecord(value)">
...
</md-list-item>
JS
vm.showRecord = function (selectedRecord, ev) {
//if the record is already selected toggel it off
if(vm.selectedRecord === selectedRecord) {
vm.selectedRecord = undefined;
} else {
vm.selectedRecord = selectedRecord
}
};

Angular one-time binding on object keys? One-time add like button

Goal: Like button only adds once using Angular.
Theory: Angular's one-time binding will do the trick.
It works here on Angular Expression Doc and on the Plunker example
Problem: Doesn't work in my trials.
Here's the Controller info (from Codecademy's Book App exercise)
$scope.products = [
{
name: 'The Book of Trees',
price: 19,
pubdate: new Date('2014', '03', '08'),
cover: 'img/the-book-of-trees.jpg',
likes: 0,
dislikes: 0
},
{
name: 'Program or be Programmed',
price: 8,
pubdate: new Date('2013', '08', '01'),
cover: 'img/program-or-be-programmed.jpg',
likes: 0,
dislikes: 0
}
View Trial 1
<div ng-repeat="product in products" class="col-md-6">
<div class="thumbnail">
<img ng-src="{{ product.cover }}">
<p class="title">{{ product.name }}</p>
<p class="price">{{ product.price | currency }}</p>
<p class="date">{{ product.pubdate | date }}</p>
<div class="rating">
<p class="likes" ng-click="plusOne($index)">+ {{ ::product.likes}} </p> <!-- here's the damn pickle of a problem -->
<p class="dislikes" ng-click="minusOne($index)">+ {{ product.dislikes }} </p>
</div>
</div>
</div>
RESULT - The button doesn't add
View Trial 2
<div ng-repeat="product in products" class="col-md-6">
<div class="thumbnail">
<img ng-src="{{ product.cover }}">
<p class="title">{{ product.name }}</p>
<p class="price">{{ product.price | currency }}</p>
<p class="date">{{ product.pubdate | date }}</p>
<div class="rating">
<p class="likes" ng-click="plusOne($index)">+ {{ ::product.::likes}} </p> <!-- here's the damn pickle of a problem -->
<p class="dislikes" ng-click="minusOne($index)">+ {{ product.dislikes }} </p>
</div>
</div>
</div>
RESULT - No content shows, only Handlebars / Mustaches / Handlebar-Mustaches {{ }}
I tried implementing your code. The following works for me:
<div ng-repeat = 'product in products'>
<p ng-click="plusOne($index)"> + {{ product.likes}}</p>
<p ng-click="minusOne($index)"> + {{ product.dislikes }}</p>
</div>
Since products is an array, we need to iterate through each of its elements.
$scope.products = [
{
name: 'The Book of Trees',
price: 19,
pubdate: new Date('2014', '03', '08'),
cover: 'img/the-book-of-trees.jpg',
likes: 0,
dislikes: 0
},
{
name: 'Program or be Programmed',
price: 8,
pubdate: new Date('2013', '08', '01'),
cover: 'img/program-or-be-programmed.jpg',
likes: 0,
dislikes: 0
}];
$scope.plusOne = function(index){
if($scope.products[index].likes == 0){
$scope.products[index].likes++;
}
};
I am incrementing the value of likes only if it is 0. Hence it will get incremented only once.
Here's the quick error I see on your code. 1. remove the double colon (::) and change your + sign to -. See screenshot below:
https://i.gyazo.com/ae9d8bb8be57a7b9e9cf840d3f1d705b.png
Also, don't forget to attach your plus and minus properties on your scope(in your controller).
$scope.plusOne = function(index) {
$scope.products[index].likes += 1;
};
$scope.minusOne = function(index) {
$scope.products[index].dislikes += 1;
};
If I understand you correctly, you want the user to only be able to "like" or "dislike" a product once.
One-time binding is used to have the view only watch a value until it is defined, and then stop watching it. The main purpose it to not add extra watches when you are binding data that won't be changing to your view.
In the case of ng-click, the expression is not watched using a $watch, but rather, re-evaluated every time the button is clicked. Even if it worked with one-time and the expression was only evaluated once, that expression would still be used every time the button clicked.
You need to be tracking whether the user has already "liked" a product or not. You can then check that in your plusOne and minusOne functions, and simply short-circuit them if they've already been fired.
Alternately, you could simply have plusOne and minusOne simply replace themselves with empty functions after they fire, like so:
$scope.plusOne = function(index) {
// Do stuff...
// This function disables itself after it fires once
$scope.plusOne = function() {};
};

Get data, change one single element and return to api

I get Data from an api and easily list them, now i want to change one element of those, to "default". I dont know how to control that,
Here is the js.
$scope.updateDefault = function() {
if (AuthService.isAuth()) {
Account.one().get().then(
function(account) {
**account.data.defaultMsisdn= $scope.element.isSelected;
$log.d("account: ", account.data);
account.data.put();**
}
);
}
};
here is the HTML:
<div id="container" style="width:650px">
<ol ng-repeat="element in accountmsisdn">
**<li style=" text-align:left; float:left">
<input type="radio" ng-model="element.isSelected" ng-click="updateDefault()">
<span>{{element.msisdn}} </span>
</li>**
<li style="text-align:center;float: inside" ng-switch="element.active">
<span ng-switch-when=true>
{{'view.settings.sender.active'| translate}}</span>
<span ng-switch-when=false>
{{'view.settings.sender.notactive'| translate}}</span>
<span ng-switch-default>
<strong> - </strong> </span>
</li>
You need to pass the current element as an argument to updateDefault(), so it knows what element to update:
<input type="radio" ng-model="element.isSelected" ng-click="updateDefault(element)" />
$scope.updateDefault = function (element) {
...
account.data.defaultMsisdn = element.isSelected;
..
};

Resources