I developed a gallery of images separated by several categories.
I just want to display categories that contain images.
I have three categories: "new", "old", "try".
Of these three categories, only new and old have images. My problem is that all categories are appearing, even those that have no image (as is the case with try).
Is there a way to present only the categories that contain images?
How can I do this?
DEMO
code
<div *ngFor="let cat of Cats">
<div class="row ">
<span class="">{{cat}}</span>
</div>
<ul class="mdc-image-list my-image-list" style="padding-left: 10px;padding-right: 10px;">
<ng-container *ngFor="let product of data; let j = index;">
<li class="mdc-image-list__item" *ngIf="product.Cat == cat">
<div class="mdc-image-list__image-aspect-container">
<ng-container *ngIf="product.image == null; else productImage">
<img src="./assets/image-not-found.svg" class="mdc-image-list__image imagenotfound">
</ng-container>
<ng-template #productImage>
<img [src]="product.image" class="mdc-image-list__image">
</ng-template>
</div>
</li>
</ng-container>
</ul>
</div>
The try should not be presented :(
You can wrap your category div in an <ng-container>, and then use *ngIf to check if your data array contains a product in a given category. The best way to achieve that is to have an array with product counts per given category.
Add this to your AppComponent class:
get counts() {
return this.data.reduce((obj, value) => {
if (value.Cat in obj) {
obj[value.Cat]++;
} else {
obj[value.Cat] = 1;
}
return obj;
}, {});
}
And then use this as your template:
<ng-container *ngFor="let cat of Cats">
<div *ngIf="counts[cat]">
<div class="row ">
<span class="">{{cat}}</span>
</div>
<ul class="mdc-image-list my-image-list" style="padding-left: 10px;padding-right: 10px;">
<ng-container *ngFor="let product of data; let j = index;">
<li class="mdc-image-list__item" *ngIf="product.Cat == cat">
<div class="mdc-image-list__image-aspect-container">
<ng-container *ngIf="product.image == null; else productImage">
<img src="./assets/image-not-found.svg" class="mdc-image-list__image imagenotfound">
</ng-container>
<ng-template #productImage>
<img [src]="product.image" class="mdc-image-list__image">
</ng-template>
</div>
</li>
</ng-container>
</ul>
</div>
</ng-container>
You can write format function, write data as an object where the key is category, value is array of images and use keyvalue pipe.
For example:
component property: formattedData: {[key: string]: string[]} = {}
format function:
formatData(data: {image: string; cat: string}[]): void {
data.forEach((item: {image: string; cat: string}) => {
if (this.formattedData[item.cat]) {
this.formattedData[item.cat].push(item.image)
} else {
this.formattedData[item.cat] = [item.image]
}
});
}
call the format function in constructor:
constructor() {
this.formatData(this.data);
}
template:
<div *ngFor="let item of formattedData | keyvalue">
<div class="row">
<span class="">{{item.key}}</span>
</div>
<ul class="mdc-image-list my-image-list" style="padding-left: 10px;padding-right: 10px;">
<ng-container *ngFor="let image of item.value;">
<li class="mdc-image-list__item">
<div class="mdc-image-list__image-aspect-container">
<img [src]="image ? image : './assets/image-not-found.svg'" class="mdc-image-list__image" [ngClass]="{'imagenotfound': !image}">
</div>
</li>
</ng-container>
</ul>
</div>
I think it looks better because we have a little less logic in the template. You have only listed categories that have images.
Related
Need to slice the inner ngfor loop into 3 parts
<div class="row" *ngFor="let row of matrix; index as i">
<div class="col" *ngFor="let col of row; index as j">
<div *ngFor="let placeholder of placeholders | slice:i:j">
<ng-container [ngComponentOutlet]="placeholder.component" >
</ng-container>
</div>
</div>
</div>
There are a couple of ways around this:
$any
<p *ngFor="let item of data | slice:2:4">
{{ $any(item).parentName }}
</p>
Bracket notation
<p *ngFor="let item of data | slice:2:4">
{{ item['parentName'] }}
</p>
A function
slicedData(data : any[]) : any[] {
return data.slice(2,4)
}
<p *ngFor="let item of slicedData(data)">
{{ item['parentName'] }}
</p>
I have a list of items i am getting from backend.
I do not want to show two items in UI.
I do not want to display Conference and reception in UI.
HTML Code:
<ng-container *ngFor="let space of space_name">
<div *ngIf="space">
<a class="dropdown-item text-light" [routerLink]="['/spaces']"
routerLinkActive="current"
[queryParams]="{space_name:space}" data-toggle="collapse"
data-target=".navbar-collapse.show"
style="background-color: #8d0528;">{{ space | uppercase }}</a>
</div>
</ng-container>
TS Code:
this.sharedService.getDropdownspace().subscribe(data => {
this.spaceDropdown = data;
this.api_data = Object.values(this.spaceDropdown);
this.space_name = this.api_data[0];
})
Please suggest me a approach to do this.
You can use the array filter method to do the same. Create a function in your component and use it on the *ngFor directive like below.
In your component create a function to filter the result.
filter(itemList: space_name[]): space_name[] {
let result: space_name[] = [];
result = itemList.filter(item => {
return (item != "Conference" && item != "Reception")
});
return result;
}
In your template file
<ng-container *ngFor="let space of filter(space_name)">
<div *ngIf="space">
<a class="dropdown-item text-light" [routerLink]="['/spaces']" routerLinkActive="current" [queryParams]="{space_name:space}" data-toggle="collapse" data-target=".navbar-collapse.show" style="background-color: #8d0528;">{{ space | uppercase }}</a>
</div>
</ng-container>
Code and Demo: https://jsfiddle.net/ybdmrL2z/
Here I am sharing my Angular Code:
1. app-component.ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-all-trades',
templateUrl: './all-trades.component.html',
styleUrls: ['./all-trades.component.css']
})
export class AllTradesComponent implements OnInit {
// Arrays of Object
crops = [
{
name:'Rice',
checked: false
},
{
name:'Wheat',
checked : false
},
{
name:'Barley',
checked : false
},
{
name:'Soyabean',
checked : false
},
];
constructor() { }
ngOnInit(): void {
}
// Here I tried the logic but its not working
onChange(event, index, item) {
item.checked = ! item.checked
console.log(index, event, item);
}
}
2. app-component.html
<app-header></app-header>
<div
fxLayout="row"
fxLayout.lt-md="column"
fxLayoutAlign="space-between start"
fxLayoutAlign.lt-md="start stretch"
>
<div class="container-outer" fxFlex="20">
<div class="filters">
<section class="example-section">
<span class="example-list-section">
<h1>Select Crop</h1>
</span>
<span class="example-list-section">
<ul>
<li *ngFor="let crop of crops; let i = index">
<mat-checkbox
[checked]="item.checked"
(change)="onChange($event, i, item)"
>
{{ crop.name }}
</mat-checkbox>
</li>
</ul>
</span>
</section>
<section class="example-section">
<span class="example-list-section">
<h1>Select District</h1>
</span>
<span class="example-list-section">
<ul>
<li *ngFor="let district of districts">
<mat-checkbox>
{{ district.name }}
</mat-checkbox>
</li>
</ul>
</span>
</section>
</div>
</div>
<div class="content container-outer" fxFlex="80">
<mat-card
class="crop-card"
style="min-width: 17%"
*ngFor="let crop of crops"
>
<mat-card-header>
<img
mat-card-avatar
class="example-header-image"
src="/assets/icons/crops/{{ crop.name }}.PNG"
alt="crop-image"
/>
<mat-card-title>{{ crop.name }}</mat-card-title>
<mat-card-subtitle>100 Kgs</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>PRICE</p>
</mat-card-content>
</mat-card>
</div>
</div>
<app-footer></app-footer>
Here I shared my output window, as you guys can see on left hand side I have created mat-checkbox, all I need to do is that when I check the "RICE" box its should only show the RICE CARD and when I check Wheat then only Wheat Card.
the first thing to know is that you
Can't have multiple template bindings on one element
as you saw when trying to do so and here is the official document for that if you are interested
so here are two possible solutions for you
hide unchecked cards by [hidden] directive as below:
<mat-card [hidden]="!crop.checked">
wrap your card in another element like a div and do as below:
<div *ngFor="let crop of crops">
<mat-card
class="crop-card"
style="min-width: 17%"
*ngIf="crop.checked">
.
.
</mat-card>
</div>
I'd like to add row on each 2 elements of ngFor loop.But I couldnt handle it.
I have studentNames array like below
studentNames=[
{
name:"Jonas",
age:22,
number:"1234"
},
{
name:"Mathilda",
age:25,
number:"5432"
},
{
name:"Jacob",
age:20,
number:"4321"
},
{
name:"Ivan",
age:23,
number:"6984"
},
{
name:"Kate",
age:21,
number:"3432"
},
{
name:"James",
age:20,
number:"4312"
},
{
name:"Sean",
age:23,
number:"4321"
},
{
name:"Julia",
age:23,
number:"4321"
},
]
Here what I tried
<ng-container *ngFor="let item of studentNames;let i=index">
<div class="row" *ngIf="i%2==0">
<div class="col md-12">
{{item.name}}
</div>
</div>
</ng-container>
This only skipped when index is not even.
Here how I want to see them below(order doesnt matter only should be 2 by 2 per row).
Stackblitz example: https://stackblitz.com/edit/bootstrap-wpfukz?file=app%2Fapp.component.html
This is a bit more "hacky" approach but it's HTML-only:
<ng-container *ngFor="let item of studentNames;let i=index">
<div *ngIf="i%2==0">
<div class="row">
<div class="col md-12">
{{studentNames[i].name}}
</div>
<div class="col md-12">
{{studentNames[i + 1].name}}
</div>
</div>
</div>
</ng-container>
You can try like below. Use *ngFor exported value index and use half length of the array to continue the loop
<ng-container *ngFor="let item of studentNames; let j = index;">
<ng-container *ngIf="j < (studentNames.length / 2)">
<div class="row">
<div class="col md-6">
{{item.name}}
</div>
<div class="col md-6">
{{ studentNames[ j + (studentNames.length / 2) ].name }}
</div>
</div>
</ng-container>
</ng-container>
Working stackblitz
Note : Array length should be an even number ;)
I'm using this AngularJS paginator, https://github.com/michaelbromley/angularUtils/tree/master/src/directives/pagination.
And I'm also using the example in this video to add checkbox filters to my ng-repeat. I want to filter based on whether the specie property of every animal object (animal.specie) is a cat or a dog. I start setting both to true but my ng-repeat doesn't populate when I do that. And when I check a box to true or false nothing happens.
The only thing in the controller is $scope.species = {dogs: true, cats: true};
What is wrong with my implementation? Any help will be much appreciated.
Thanks :)
<div class="row adopt-title-wrap">
<div class="medium-6 large-6 column">
<h3>Find Your Next Best Friend</h3>
</div>
<div class="medium-2 large-2 end column">
<input type="checkbox" ng-model="species.cats">
<label>Cats Only</label>
</div>
<div class="medium-2 large-2 end column">
<input type="checkbox" ng-model="species.dogs">
<label>Dogs Only</label>
</div>
</div><br>
<i class="fa fa-spinner fa-pulse fa-3x"></i>
<div class="row">
<!-- Start Single Animal Record -->
<div class="medium-6 large-6 end column" dir-paginate="animal in animals
| filter: {specie: 'Dog'} : species.dogs
| filter: {specie: 'Cat'} : species.cats
| itemsPerPage: 6">
<div class="panel">
<img class="pet-img" ng-src="{{ animal.profile_photo }}" alt="pet for adoption">
<ul class="pet-details">
<li class="pet-field-title">Name</li>
<li class="pet-field-value">{{ animal.name }}</li>
<li class="pet-field-title">Age</li>
<li class="pet-field-value">{{ animal.dob }}</li>
<li class="pet-field-title">Breed</li>
<li class="pet-field-value">{{ animal.breed }}</li>
<li class="pet-field-title">Code</li>
<li class="pet-field-value">{{ animal.shelter_code }}</li>
</ul>
<p class="pet-description">{{ animal.description }}</p>
</div>
</div> <!-- End Single Animal Record -->
</div>
<dir-pagination-controls></dir-pagination-controls>
I would recommend stripping down your code to the base, maybe just get everything displayed and then migrate to pagination. I can spot your first issue is you are applying two filters on specie.
dir-paginate="animal in animals | filter: {specie: 'dog'} | filter: {specie: 'cat'}"
is basically saying filter down to animals that are both a dog and a cat. The first filter will return an array of only dogs, then the second filter will run on that finding only cats, which is none because obviously you can't be a dog and a cat :)
Original Array: [{name: 'bob', specie: 'cat'}, {name: 'jeff', specie: 'dog'}] | filter dogs | filter cats
After First Filter (only dogs): [{name: 'jeff', specie: 'dog'}] | filter cats
After Second Filter (only animals that are both dogs and cats): []
Try defining your own angular filter to simplify your work as so:
.filter('isCatOrDog', function() {
return function(inputAnimals) {
return inputAnimals.filter(function(animal) {
return animal.value === 'cat' || animal.value === 'dog';
});
}
});
And you call the filter as so:
dir-paginate="animal in animals | isCatOrDog"
Basic Plunkr
Just learned that Angular filters don't have (at least not yet) the functionality I was looking for. So I worked around the issue by changing the check boxes to radio buttons and make a different backend API call based on the radio selected.
template:
<div class="row adopt-title-wrap">
<div class="medium-5 large-5 column">
<h4>Find Your Next Best Friend!</h4>
</div>
<div class="medium-1 large-1 column">
<i class="fa fa-spinner fa-pulse fa-2x"></i>
</div>
<div class="medium-2 large-2 column">
<input type="radio" ng-model="species" value="all">
<label>Show All</label>
</div>
<div class="medium-2 large-2 column">
<input type="radio" ng-model="species" value="cats">
<label>Cats Only</label>
</div>
<div class="medium-2 large-2 column end">
<input type="radio" ng-model="species" value="dogs">
<label>Dogs Only</label>
</div>
</div>
<div class="row">
<!-- Start Single Animal Record -->
<div class="single-record medium-6 large-6 end column" dir-paginate="animal in animals | itemsPerPage: 6">
<div class="panel">
<img class="pet-img" ng-src="{{ animal.profile_photo }}" alt="pet for adoption">
<ul class="pet-details">
<li class="pet-field-title">Name</li>
<li class="pet-field-value">{{ animal.name }} ({{animal.specie }})</li>
<li class="pet-field-title">Age</li>
<li class="pet-field-value">{{ animal.dob }}</li>
<li class="pet-field-title">Breed</li>
<li class="pet-field-value">{{ animal.breed }}</li>
<li class="pet-field-title">Code</li>
<li class="pet-field-value">{{ animal.shelter_code }}</li>
</ul>
<p class="pet-description">{{ animal.description }}</p>
</div>
</div> <!-- End Single Animal Record -->
</div>
<dir-pagination-controls></dir-pagination-controls>
Controller:
angular.module('animalShelterApp')
.controller('AdoptCtrl', function ($scope, SharedServices) {
var spinner = angular.element('.fa-spinner'); // Holds spinner element.
/*
* Binds 3 radio buttons (all, cats, dogs).
* Load 'all' records by default.
*/
$scope.species = 'all';
/*
* When user selects a specie, display appropriate data.
*/
$scope.$watch('species', function() {
spinner.show();
if ($scope.species === 'all') {
/*
* Get all available animals.
*/
SharedServices.getAllAnimals().success(function(data) {
spinner.hide();
$scope.animals = data;
});
} else if ($scope.species === 'cats') {
/*
* Get all available Cats.
*/
SharedServices.getAllCats().success(function(data) {
spinner.hide();
$scope.animals = data;
});
} else if ($scope.species === 'dogs') {
/*
* Get all available Dogs.
*/
SharedServices.getAllDogs().success(function(data) {
spinner.hide();
$scope.animals = data;
});
}
}); // end $scope.$watch('species').
}); // end AdoptCtrl.