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/
Related
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.
I am looping through a list of products and then checking if the product Id is already present in an array of products(objects) , then printing the quantity else if the product is not in the object then trying to print 0. Below is the code I have tried till now.
<ion-item class="added" *ngFor="let item of fetchProducts();let key=index;">
<ng-container *ngFor="let cartitem of cart" >
<span class="count" *ngIf="cartitem.p_id==item.p_id;">
{{cartitem.qty}}
</span>
</ng-container>
</ion-item>
How to print 0 if item is not in the cartitem in same span.
You can simply do this using ternary operator like below.
<ng-container *ngFor="let cartitem of cart" >
<span class="count">
{{cartitem.p_id==item.p_id ? cartitem.qty : 0 }}
</span>
</ng-container>
Can use *ngIf else condition inside your for loop -
<ion-item class="added" *ngFor="let item of fetchProducts();let key=index;">
<ng-container *ngFor="let cartitem of cart">
<span class="count" *ngIf="cartitem.p_id==item.p_id; then content else other_content">
</span>
<ng-template #content>{{cartitem.qty}}</ng-template>
<ng-template #other_content>0</ng-template>
</ng-container>
</ion-item>
Instead of using template logic I would move the logic to the class.
cart and products are obviously available in the class.
Therefore adjust the fetchProducts function in the class to return the products list as needed (with the quantity information) and use a single ngFor loop in the template.
Or add a new function getProductsWithQuantity...
In your class
public getProductsWithQuantity() {
return this.fetchProducts().map(product => {
...product,
quantity: this.getQuantity(product);
});
}
private getQuantity(product) {
const foundCartItem = this.cart.find(cartItem => product.id === cartItem.id);
if (foundCartItem) {
return foundCartItem.qty;
}
return 0;
}
In your template:
<ion-item class="added" *ngFor="let item of getProductsWithQuantity();let key=index;">
<span class="count">
{{item.qty}}
</span>
...
I am new to Angular and trying to perform certain task which includes retrieving value from div from front end in ts file. The scenario is as follows :-
I have two drop down on front end and one dropdownToggle button. After inserting and opting for desired operation, the content is showed in a span/div. As part of unit test case, I need to check whether value appeared in div or not. Also, to perform other operation, I am saving that value in an array at *.component.ts file. I have tried several ways to call that array in *.component.spec.ts (test case file) but unable to achieve so.
It would be great if someone can suggest the possible solution to get this resolve.
Code Snippet
Front End
<div class="col">
<div class="btn-group" dropdown >
<button type="button" class="btn" (click)="peformSearch(searchOp)" [disabled]="loading">Search </button>
<button type="button" dropdownToggle class="btn dropdown-toggle " aria-controls="dropdown-split">
</button>
<ul id="dropdown-split" *dropdownMenu class="dropdown-menu" aria-labelledby="button-split">
<li role="menuitem"><a class="dropdown-item" >Add</a></li>
<li role="menuitem"><a class="dropdown-item" >Sub</a></li>
</ul>
</div>
</div>
</div>
<div class="form-group row">
<span class="btn" *ngFor="let e of searchTerms" >{{ e.key }}: {{ e.value }}</span>
</div>
2 . Test Case File (***.component.spec.ts)
const component = fixture.componentInstance;
component.ngOnInit();
fixture.detectChanges();
const one = component.searchForm.controls['one'];
const two = component.searchForm.controls['two'];
// print div or selected value here
you can access the elements like
// print div or selected value here
const div = fixture.nativeElement.querySelector('div.form-group');
console.log(div.innerHTML);
it might be helpful to give more specific css-classes to the elements e.g. search-term-list
import { ComponentFixture } from '#angular/core/testing';
let fixture: ComponentFixture<ComponentName>;
beforeEach(() => {
fixture = TestBed.createComponent(ComponentName);
})
de = fixture.debugElement.query(By.css('.classname-of-the-tag-whose-value-is-to-be-fetched'));
console.log(de);
de.textContent on innerHtml should return the required value.
inside test-suite various options are available By.css('.class'), By.id('#id'), By.tagName('div') etc
When I type in text to search something, displaying one character in text is very slow.
What is the problem ?
I have display 50 products with ngFor as below , if I display more than 50 products 100 or 150 typing in text is more slow.
what should I do to fix this problem ?
<div class="width_products products-animation " *ngFor="let product of productsService.products ; trackBy: $index" [ngClass]="{ 'width_products_open_menu':productsService.status_menu }" >
<span class="each_width_product" >
<div class="title_products more_detail_product" (click)="set_router({ path:product['company'].company_name+'/'+product.product_title , data:product.product_id , relative:true })">
{{product.product_title }}
<span class="glyphicon glyphicon-chevron-down"></span><br>
<div class=' glyphicon glyphicon-time'></div> {{product.product_date}}
</div>
<div class="image_product_primary " (click)="set_router({ path:product['company'].company_name+'/'+product.product_title , data:product.product_id , relative:true })">
<img class="image_product" src="../../assets/images/products_image/{{product.product_image}}">
</div>
<button (click)="product.product_in_wishList='true'; productsService.add_wish_list( product )" mat-button class="wish_list notCloseDropdawnFavorite notCloseDropdawnCard">
<span class="write_add_wish">{{dataservices.language.add_wishlist}}</span>
<mat-icon *ngIf="product.product_in_wishList == 'false' " class="notCloseDropdawnFavorite notCloseDropdawnCard">favorite_border</mat-icon>
<mat-icon *ngIf="product.product_in_wishList == 'true' " class="hearts_div_hover notCloseDropdawnFavorite notCloseDropdawnCard">favorite</mat-icon>
</button>
<div class="footer_products">
<span matTooltip="Views!">
<div class="button_footer_products">
<span class="glyphicon glyphicon-eye-open icon_eye"></span>
<div class="both_write ">
12889
</div>
</div>
</span>
<span matTooltip="Add to your card" class="notCloseDropdawnCard notCloseDropdawnFavorite " (click)="product.product_in_cartList='true'; productsService.add_cart_list( product )">
<div class="button_footer_products">
<span *ngIf="product.product_in_cartList=='false'" class="glyphicon glyphicon-plus icon_eye notCloseDropdawnCard notCloseDropdawnFavorite" ></span>
<span *ngIf="product.product_in_cartList=='true'" class="glyphicon glyphicon-ok icon_eye notCloseDropdawnCard notCloseDropdawnFavorite" ></span>
<div class="both_write ">
Cart
</div>
</div>
</span>
<span matTooltip="See Details!">
<div (click)="set_router({ path:product['company'].company_name+'/'+product.product_title , data:product.product_id , relative:true })" class="button_footer_products" >
<span class=" glyphicon glyphicon-option-horizontal icon_eye"></span>
<div class="both_write ">
More
</div>
</div>
</span>
</div>
<div class="prise_products">
Price:<del>$2500</del> $3500
</div>
<div class="plus_height"></div>
</span>
</div>
In header component I have a input type text as below :
<input type="text" class="kerkim" name="search" [(ngModel)]="typing_search" placeholder="
{{dataservices.language.searchproducts}}">
Debouce effect, e.g. do not run search immediately.
class Coponent {
private _timeoutId: number;
//to be called on search text changed
search(){
clearTimeout(this._timeoutId);
this._timeoutId = setTimeout(() => {
//do search stuff
}, 500) //play with delay
}
}
Cache prev results using search keyword.
When kyeword changes like so ["k","ke","key"] you do not need to refilter whole array.
class Search {
private _keywordChanges:string[] = [];
private _prevFilterResults: any[] = [];
private _allData: any[] = [];
search(keyword:string){
let prevKeyword = this.getPrevKeyword(),
toBeFiltered: any[];
if(keyword.match(keyword)){ //if it was "ke" and now it is "key"
//filter prev results only
toBeFiltered = this._prevFilterResults;
} else {
//filter prev results or even make cache for keyword
toBeFiltered = this._allData;
}
let results = toBeFiltered.filter(() => {});
this._prevFilterResults = results;
}
private getPrevKeyword(){
return this._keywordChanges[this._keywordChanges.length - 1];
}
Use for with break instead of Array.filter(), in some cases it may be helpfull. For example you have sorted array ["a","apple","b","banana"] and keyword "a".
function search(array:any[], keyword:string) {
//so
let results = [];
for(let i = 0; i < array.length; i++){
let item = array[i];
if(item.toString().startsWith(keyword)){
results.push(item);
} else {
break; //as b and banana left
}
}
return results;
}
Take a look at binary search. How to implement binary search in JavaScript
and hash table Hash table runtime complexity (insert, search and delete)
From my issue: every input field is slow due to many data. so i add "changeDetection: ChangeDetectionStrategy.OnPush" at where data reloaded, then everything work normal.
#Component({
selector: 'app-app-item',
templateUrl: './app-item.component.html',
styleUrls: ['./app-item.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
I have two tabs and have to implement filtering on both of them.
In second tab everything works fine, but in first one not.
I'm filling that problem relates to this part(ng-model):
<ul search-list=".letter" model="search.district">
<li class="letter" ng-repeat="letter in alphabet.letter">{{ letter }}</li>
</ul>
and to this:
<input id="q" type="text" ng-model="search.district " />
So second tab works fine:
<div ng-repeat="district in districts | filter:search:startsWith" class="district">
<h4 class="district-name">{{ district.district }}</h4>
Some info
<ul class="district-cities">
<li ng-repeat="city in district.cities ">
<a href="{{ city.url }}">
{{ city.name }}
</a>
</li>
</ul>
</div>
But how to make works first part I don't know...
Here is DEMO
Do this changes in HTML
<div class="tab-content active" data-id="first">
<li ng-repeat='city in cities | filter:{name :search.district}:startsWith'>
<a href="{{ city.link }}">
{{ city.name }}
</a>
</li>
</div>
Here is the updatd Plunker
In your startsWith method you are specifically referencing the district property of the object. This property doesn't exist on city objects:
$scope.startsWith = function (actual, expected) {
var lowerStr = (actual + "").toLowerCase();
var e = angular.isString(expected) ? expected.toLowerCase() :
// district is not present in City object
expected.district.toLowerCase();
return lowerStr.indexOf(e) === 0;
}
Since you're using search.district, it tries to access the .district value for the cities.
You can use search.name instead, here...
<ul search-list=".letter" model="search.name">
...and here...
<input id="q" type="text" ng-model="search.name" />
and then rename district to name on each district of the district array.