I want to hide and show bootstrap columns inside a row based on search value.
Here is a simple template:
<div class="container py-5">
<p class="text-center"><input type="text" placeholder="search" [(ngModel)]="searchVal"></p>
<div class="row">
<div *ngFor="let val of values" class="col-4 my-2">
<ng-container *ngIf="val.toLowerCase().indexOf(searchVal.toLowerCase())!=-1">
{{val}}
</ng-container>
</div>
</div>
</div>
And the component:
export class AppComponent {
searchVal : string = "";
values = ['Manager', 'Model', 'Teacher', 'Student'];
}
The ideal solution is to use ngFor and ngIf on column:
<div class="row">
<div *ngFor="let val of values" class="col-4 my-2" *ngIf="val.toLowerCase().indexOf(searchVal.toLowerCase())!=-1">
{{val}}
</div>
</div>
But we all know that its not possible to use both on same element.
In the code I provide here, I used ng-container to solve this issue, however, since the ng-container is not inside the column, if a value does not match, the column remains displayed and its content is hidden which looks ugly. Try to write the letter "t" in the search field to see what I mean. The solution that I am looking for is to remove the entire column if its value does not match.
Here is a running code: https://angular-rgc8f7.stackblitz.io
Swap your div and ng-container:
ng-container has the for loop and div has the if condition
<div class="container py-5">
<p class="text-center"><input type="text" placeholder="search" [(ngModel)]="searchVal"></p>
<div class="row">
<ng-container *ngFor="let val of values">
<div *ngIf="val.toLowerCase().indexOf(searchVal.toLowerCase())!=-1" class="col-4 my-2">
{{val}}
</div>
</ng-container>
</div>
</div>
Try the following code.
<ng-container *ngFor="let val of values">
<div class="col-4 my-2"
*ngIf="val.toLowerCase().indexOf(searchVal.toLowerCase())!=-1">
{{val }}
</div>
</ng-container>
Couldn't you just add a function in your component to dynamically filter the values before iteration. e.g
<div class="container py-5">
<p class="text-center"><input type="text" placeholder="search" [(ngModel)]="searchVal"></p>
<div class="row">
<div *ngFor="let val of filterValues(values, searchVal)" class="col-4 my-2">
{{val}}
</div>
</div>
</div>
In your component;
filterValues(values: string[], searchVal: string) {
return values.filter((val: string) => {
return val.toLowerCase().indexOf(searchVal.toLowerCase())!=-1;
});
}
Change detection on your search value will cause a refilter, filtering on the current search value, and value list, and updating your table.
This way, you don't need the ng-container at all, and, if you ever need to filter based on other criteria too, you can do it all in one place :)
StackBlitz Example: https://stackblitz.com/edit/angular-miow53
Related
I am asking myself: How would you loop through different rows using *ngFor?
Lets say an array is given
let arr = [1,2,3,4] // arr can have a arbitrary number of elements
Now I want to loop trough that array and display the values. I want two elements to be in a row (using bootstrap 4.0). My approach:
<div class="row">
<div class="col-6" *ngFor="let el of arr"> {{el}} </div>
</div>
Now I would have this html-code
<div class="row">
<div class="col-6"> 1 </div>
<div class="col-6"> 2 </div>
<div class="col-6"> 3 </div>
<div class="col-6"> 4 </div>
</div>
but I want it this way:
<div class="row">
<div class="col-6"> 1 </div>
<div class="col-6"> 2 </div>
</div>
<div class="row">
<div class="col-6"> 3 </div>
<div class="col-6"> 4 </div>
</div>
because this would be correct regarding the elements in a row.
How can I achieve this?
You can achieve it without second loop provided col-6 is fixed.
<div>
<ng-container *ngFor="let el of arr; index as i">
<div class="row" *ngIf="i%2 === 0">
<div class="col-6" > {{arr[i]}} </div>
<div class="col-6" > {{arr[i+1]}} </div>
</div>
</ng-container>
</div>
This will do as you are expecting.
Option 1
The quickest option would be to use the slice pipe. But it might not be suitable for large data sets.
<div class="row">
<div class="col-6" *ngFor="let el of arr | slice:0:2"> {{el}} </div>
</div>
<div class="row">
<div class="col-6" *ngFor="let el of arr | slice:2:4"> {{el}} </div>
</div>
Option 2
Split the array into chunks of arrays and loop through them.
Controller
export class AppComponent {
arr = [1,2,3,4];
finalArr = []; // <-- [[1,2], [3,4]]
constructor(private http: HttpClient){
for (let i = 0, j = this.arr.length; i < j; i += 2) {
this.finalArr.push(this.arr.slice(i, i + 2));
}
}
}
Template
<div class="row" *ngFor="let arr of finalArr">
<div class="col-6" *ngFor="let el of arr"> {{el}} </div>
</div>
From the pattern it looks like a outer and a inner loop will solve the problem.
outer loop will control the "row" - div
inner loop will control the "col-6" - div
Kindly give it a try.
I'm having trouble using patchValue with formArray.
I have a custom select made with <ul>, <li>, the click on an option executes a method that should patch the id of the option selected in an <input type="hidden" /> that will contain the value of the property in the formarray.
This is how the form is built:
this.roleForm = this.fb.group({
profiles: this.fb.array([ this.fb.group({perfil: '', accion: ''}) ])
})
This is the method to add new profiles in the array:
addProfile() {
const prof = this.roleForm.get('profiles') as FormArray;
prof.push(this.fb.group({
perfil: [''],
accion: ['']
}))
}
And this is the html:
<div class="col-md-8" formArrayName="profiles">
<div class="row" *ngFor="let profile of getProfiles(); let i = index">
<div class="col-md-6" [formGroupName]="i">
<div class="col-12">
<div class="card card-primary bg-white">
<div class="card-header h4">
Perfil
</div>
<ul class="list-group list-group-flush register h5 scroll-list">
<li *ngFor="let profileType of profileTypes; index as i"
class="list-group-item rounded-0 pointer"
[ngClass]="{'selected bg-primary text-white font-weight-bold': this.selectedProfile === profileType.profileId}"
(click)="selectProfile(profileType.profileId, i)">
{{profileType.profileDescription}}
</li>
</ul>
<input formControlName="perfil" type="text" />
</div>
</div>
</div>
</div>
</div>
EDIT:
I'd have to use patchValue in this method to set the selected value in the input with the same index. But I can't make the at() method work, this always returns me an undefined:
selectProfile(profileType.profileId, i) {
let x = (<FormArray>this.roleForm.get('profiles')).at(i);
console.log(x)
}
i'm guessing your problem is a duplicate declaration of template variable i:
<div class="col-md-8" formArrayName="profiles">
<div class="row" *ngFor="let profile of getProfiles(); let outterI = index"> <!-- declared here -->
<div class="col-md-6" [formGroupName]="outterI">
<div class="col-12">
<div class="card card-primary bg-white">
<div class="card-header h4">
Perfil
</div>
<ul class="list-group list-group-flush register h5 scroll-list">
<!-- and here -->
<li *ngFor="let profileType of profileTypes; index as innerI"
class="list-group-item rounded-0 pointer"
[ngClass]="{'selected bg-primary text-white font-weight-bold': this.selectedProfile === profileType.profileId}"
(click)="selectProfile(profileType.profileId, outterI)">
{{profileType.profileDescription}}
</li>
</ul>
<input formControlName="perfil" type="text" />
</div>
</div>
</div>
</div>
</div>
you don't need to declare an index variable when using a ngFor though if you're not using it, as you don't seem to be using innerI in this example.
I'm trying to display some json data I got from a get request. So far I've been able to display other arrays with no problem, but I'm having trouble displaying this particular array for some reason, and I'm not getting any errors.
the one that won't display correctly is the showtimes array.
<div *ngIf="show">
<div *ngFor="let shows of show"class="showtime">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{shows.title}}</h3>
</div>
<div class="panel-body" >
<div class="row">
<div class="col-md-7 col-sm-5 col-xs-12 ">
<img class="thumbnail movie_pic" src="http://developer.tmsimg.com/{{shows.preferredImage.uri}}?api_key={{api}}"><br>
</div>
<div class="col-md-5 col-sm-7 col-xs-12">
<ul class="list-group">
<li class="list-group-item">Genres: {{shows.genres}}</li>
<li class="list-group-rel">Release Date: {{shows.releaseDate}}</li>
<li class="list-group-rel">{{shows.longDescription}}</li>
<li *ngFor="let shows of show.showtimes" class="list-group-rel">shows.theatre.name</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
You are missing {{}} expression
<li *ngFor="let shows of show.showtimes" class="list-group-rel">{{shows.theatre.name}}</li>
EDIT
Change it to some other variable name, since you are already using shows,
<li *ngFor="let showdetail of show.showtimes" class="list-group-rel">{{showdetail.theatre.name}}</li>
As it looks from your snapshot, the array of objects is stored in variable showtimes, if that is the case, try the below code:
<li *ngFor="let detail of showtimes" class="list-group-rel">
{{detail.theatre.name}}
</li>
A nested ngFor loop solved the problem..
<div *ngFor="let shows of show">
<div *ngFor="let detail of shows.showtimes">
<p>{{detail.theatre.name}}</p>
</div>
</div>
works perfectly, thanks for the help
I'm trying to display all the .name and .info elements for actor and character objects shown below, using *ngFor.
I create the object here in my resultpage.component:
this.actorInfo = [{actor: res.actorInfo, character: this.characters}];
and here is my html for running through the elements in resultpage.html:
<div class="castMembers">
<div class="member" *ngFor="let item of actorInfo">
<span class="character">{{item.name}}</span>
<span class="actor">{{item.info}}</span>
</div>
</div>
The above code does not output any errors, but it also does not output any information. I believe this is due to the first element on the object being 0:. But trying *ngFor="let item[0] of actorInfo" causes major problems in Angular.
How can I display all the name and info items in my object?
You need to have two nested ngFor,
<div class="castMembers">
<div class="member" *ngFor="let item of actorInfo">
<div class="member" *ngFor="let actor of item.actorInfo ">
<span class="character">{{actor.name}}</span>
</div>
<div class="member" *ngFor="let character of item.character">
<span class="actor">{{character.info}}</span>
</div>
</div>
</div>
Try this:
<div class="castMembers">
<div class="member" *ngFor="let item of actorInfo">
<div style="width: 100%">
<div style="width: 50%; display: inline-block">
<span class="character" *ngFor="let actor of item?.actor">
{{actor.name}}
</span>
</div>
<div style="width: 50%; display: inline-block">
<span class="actor" *ngFor="let character of item?.character">
{{character.info}}
</span>
</div>
</div>
</div>
</div>
I have an angular ng-repeat like bellow,
<div class="row" >
<div class="col-md-6" ng-repeat="(index,data) in mydata">
<-- my content -->
</div>
</div>
This will create output like below,
<div class="row" >
<div class="col-md-6" ng-repeat="(index,data) in mydata">
<-- my content -->
</div>
<div class="col-md-6" ng-repeat="(index,data) in mydata">
<-- my content -->
</div>
<div class="col-md-6" ng-repeat="(index,data) in mydata">
<-- my content -->
</div>
-----------------------
----------------- Etc.
</div>
But i need to repeat <div class="row" > also which contain two <div class="col-md-6" in each row.
This output needs like
<div class="row" >
<div class="col-md-6" ng-repeat="(index,data) in mydata">
<-- my content -->
</div>
<div class="col-md-6" ng-repeat="(index,data) in mydata">
<-- my content -->
</div>
</div>
<div class="row" >
<div class="col-md-6" ng-repeat="(index,data) in mydata">
<-- my content -->
</div>
<div class="col-md-6" ng-repeat="(index,data) in mydata">
<-- my content -->
</div>
</div>
<div class="row" >
<div class="col-md-6" ng-repeat="(index,data) in mydata">
<-- my content -->
</div>
<div class="col-md-6" ng-repeat="(index,data) in mydata">
<-- my content -->
</div>
</div>
------- Etc
Is it possible to do with this usingng-repeat?
As mentioned in comment, if you want your row to repeat with each element in mydata, you would need to put ng-repeat on the <div> that contains row.
Its upto you to decide if you want to hardcode the inner <div> like this:
<div class="row" ng-repeat="data in mydata">
<div class="col-xs-6"> {{data.index}} </div>
<div class="col-xs-6"> {{data.value}} </div>
</div>
or use another ng-repeat on it.
<div class="row" ng-repeat="(index,data) in mydata">
<div class="col-xs-6" ng-repeat="i in data"> {{i}} </div>
</div>
Where mydata is an array of json with following structure:
$scope.mydata = [{index:0,value:'hello'},
{index:1,value:'hello1'},
{index:2,value:'hello2'}
]
here's the plunkr
UPDATE:
If you have data like following,
$scope.mydata = [{value:'hello0'},
{value:'hello1'},
{value:'hello2'},
{value:'hello3'}];
and you want to display it like
hello0 hello1
hello2 hello3
in the view, then you would need to make a check for elements occurring at even iterations and print elements at $index and $index+1. I used $even for the same. but you can also create a custom filter.
<div class="row" ng-repeat="data in mydata">
<div ng-if="$even">
<div class="col-xs-6" > {{mydata[$index].value}} </div>
<div class="col-xs-6" > {{mydata[$index + 1].value}} </div>
</div>
</div>
Heres the updated plunker
Create a directive:
angular.module(....)
.directive('myRows',function(){
return {
restrict : 'A',
template : '<div class="row"><div class="col-md-6" ng-repeat="(index,data) in mydata"><-- my content --></div></div>'
};
});
assumption here is that mydata is defined on the scope of the surrounding controller.
Use it in your html:
<div data-my-rows><div>
Yes ng-repeat is possible to do that.
Here is example code from my project
<tbody>
<tr ng-repeat="proj in projects>
<td>
{{proj.projectID}}
</td>
<td>
{{proj.projectName}}
</td>
<td>
{{proj.project.start date | date:'mediumDate'}}
</td>
<td>
{{proj.projectStatus}}
</td>
</tr>
</tbody>
I hope this would help
So, you now have two answers that correctly address your question and will produce the results you requested, but I wanted to give you some advice (albeit unsolicited) about your Bootstrap structure.
In Bootstrap, it is perfectly acceptable to not repeat your rows for every 12 grid units. In fact, if you want to use multiple responsive column classes together, such as using col-lg-4 and col-md-6 to produce 3 columns on large viewports and 2 on medium viewports, you cannot insert rows between your repeated content.
Since col classes use float and percentages, you don't have to worry about "terminating" the row. Elements will simply float next to one another up to 12 grid units and then start a new natural horizontal "row." That is how Bootstrap's responsive grid works.
You should use rows for two purposes:
To nest columns inside of columns. See my answer here for a
visual explanation of this topic. or
To create horizontal groups of columns. This has the purpose of grouping elements together and clearing column floats for subsequent columns.
What I'm saying is that your initial ng-repeat structure was better and will provide you with a more flexible outcome.