Angular - Rendering filtered array issue - arrays

I'm following this tutorial and I'm stuck in this feature where I have to display a filtered array (object coming from firebase) when clicking an anchor. I followed all the implementation steps but I'm missing something...The code:
HTML:
<div class="row">
<div class="col-3">
<div class="list-group">
<a
*ngFor="let c of categories$ | async"
routerLink="/"
[queryParams]="{category: c.$key}"
class="list-group-item list-group-item-action"
[class.active]="category===c.$key">
{{c.name}}
</a>
</div>
</div>
<div class="col">
<div class="row">
<ng-container *ngFor="let p of filteredProducts; let i =index">
<div class="col">
<div class="card" style="width: 15rem;">
<img src="{{p.imageURL}}">
<div class="card-body">
<h5 class="card-title">{{p.title}}</h5>
<p class="card-text">{{p.price | currency: 'EUR': true}}</p>
Add to cart
</div>
</div>
</div>
<div></div>
</ng-container>
</div>
</div>
</div>
TS class:
products:Product[]=[];
filteredProducts: Product[]=[];
category: string;
categories$;
constructor(
private productService: ProductService,
private categoryService: CategoryService,
private route: ActivatedRoute) {
this.categories$=this.categoryService.getCategories();
this.productService.getAll().subscribe(products =>this.products=products)
this.route.queryParamMap.subscribe(params =>{
this.category = params.get('category');
this.filteredProducts = (this.category) ?
this.products.filter(p=>p.category===this.category) :
this.products;
});
}
The service retrives data correcly, but when I log the filtered array, I get nothing...Can someone give me a hand?
EDIT:

your attempting to set your array before you retrieve the products. subscribe is an asynchronous operation so it doesn't execute in the order it's started, but rather in the order the results arrive, and queryParams is a type of observable that emits immediately, so that subscribe is executing BEFORE the products arrive, instead, use observable combineLatest and the map operator and the async pipe:
filteredProducts$: Observable<Product[]>;
....
const category$ = this.route.queryParamMap.pipe(map(params => params.get('category')));
this.filteredProducts$ = combineLatest(this.productService.getAll(), // combine the streams
category$)
.pipe(
tap(([products, category])=> console.log(products, category)), // this is how you log in a stream
map(([products, category]) => // you'll have the latest value from both
(category) ? // run your filter
products.filter(p => p.category.toLowerCase() === category.toLowerCase()) :
products),
tap(filteredProducts => console.log(filteredProducts)) // this is how you log in a stream
);
then in your template just use the async pipe like you did with categories$
<ng-container *ngFor="let p of filteredProducts$ | async; let i =index">
an Observable is JUST a definition of how to handle a stream of data. logging things outside of that stream will not show you the values inside of that stream. the tap operator exists to allow you to log things inside of the stream

Related

Vue js for each uninterrupted set of recieved messages place an icon at the last element

I got this cosmetic problem where there is this v-for loop for message fetching and i want every last message of recieved message streak to bear an icon of the user you're chatting with. Just like in picture related.
Now... I know how to style it... But the v-for loop is preventing me from from using something like this...
<div class="messagegroup recieved">
<div class="iconcontainer">
<div class="iconcontainerinner">
<img class='usericoncontacts' src="../assets/icons8-male-user-100.png" alt="">
</div>
</div>
<div class="textmessages recieved">
<div class="message">
<span>Hello</span>
</div>
<div class="message">
<span>How are you?</span>
</div>
</div>
</div>
which looks something like this
but instead, because I'm unable to somehow group recieved text messages i must go on with code that goes something like this...
<div v-if='selectedContact' class="textmessages">
<div
v-for="message in messages"
v-if="message.messageTo == selectedContact.userid || message.messageFrom == selectedContact.userid"
:key="message.messageFrom"
:class="[message.messageFrom==authUser.uid?'message':'message recieved']"
>
<span>{{ message.message }}</span>
</div>
</div>
which unfortunately only gives this basic chat structure that looks something like this...
The messages array is an array of objects which have data in them like this...
author: "Matěj Pospíšil"
createdAt: July 13, 2020 at 2:43:17 PM UTC+2
message: "Hello"
messageFrom: "exl21qLb1yW3qNUFgpcZv3BRnyI2"
messageTo: "xTw8bWpVTwaj05TBTudBNLV2XEE3"
Please if you somehow understand my broken english be so kind and teach me. I bet this thing is trivial, but I just couldn't find any answers on web so please be so kind...
Thank you in advance
I'm taking some big guesses about the structure of your data but what I would do is create a computed property that does two things...
Filters the list of messages to just ones from or to the selected user
Chunk the messages into groups of sent and received
computed: {
conversation () {
if (!this.selectedContact) {
return []
}
// filter for the selected user
const messages = this.messages.filter(({ messageTo, messageFrom }) =>
messageTo === selectedContact.userid || messageFrom === selectedContact.userid)
// create a new array chunked by sender
return messages.reduce((chunks, { messageFrom, message }) => {
let last = chunks[chunks.length - 1]
if (last?.uid !== messageFrom) {
chunks.push(last = {
uid: messageFrom,
messages: [],
received: messageFrom !== this.authUser.uid // a handy flag for CSS classes
})
}
last.messages.push(message)
return chunks
}, [])
}
}
Now you can use this to drive your template
<div
v-for="messageGroup in conversation"
class="messagegroup"
:class="{ received: messageGroup.received }"
>
<div v-if="messageGroup.received" class="iconcontainer">
<div class="iconcontainerinner">
<img class='usericoncontacts' src="../assets/icons8-male-user-100.png" alt="">
</div>
</div>
<div class="textmessages" :class="{ received: messageGroup.received }">
<div class="message" v-for="message in messageGroup.messages">
<span>{{ message }}</span>
</div>
</div>
</div>

Problem with an angular filter of objects without using routes (Angular TS)

I need some help about the code: I have to make a filter for a list of objects and I have to use the observables (the exercise only includes the front end part) and the objects are in a database.
with the code written as soon as I insert a letter in the search bar, the array is emptied and only the last letter remains (for example insert C and after I insert E, in the filter only the E remains)
in TS:
Search(name:any):void{
this.arraycopy=this.mylist
})
this.arraycopy=this.mylist.filter(res =>{
return res.description.includes(name.key) ;
})
}
IN HTML:
<div class="row">
<div class="col-2" *ngFor="let object of arraycopy">
<div class="card">
<div class="card-block">
<p class="card-text">
<a class="breadcrumbLabelStyle" href="{{list.listCode}}" title="access to {{list.description}}">{{list.description}}
</a>
</p>
</div>
</div>
</div>
</div>
Are you sure you're updating your Observable with the full search-bar input and not just the last key pressed?
You are probably updating it based on the keypress event which returns the last key pressed.
Update on Answer:
Try this:
Search-bar:
<input type="text" [ngModel]="searchInput" (keyup)="Search()">
In TS:
public searchTerm = new BehaviorSubject('');
public searchInput: string;
constructor() {
this.searchTerm.subscribe((text: string) => {
this.arraycopy = this.mylist.filter(res => {
return res.description.includes(text);
});
});
}
public Search(): void {
this.searchTerm.next(this.searchInput);
}
This should work, but with this you're not using Observables so good.

Displaying all but [0] element of map key arrays

The premise of this question is that in the following TS block, I am creating an array that is made from the given map's keys and console logging to ensure that the arrays are created as needed.
public certKeys: String[];
public certMap: Map<String, DataObject[]> = new Map();
public allData: DataObject[];
#Input()
set data(data: DataObject[]) {
if (!data) { return; }
new Set(data.map(i => i.certTypeDescription)).forEach(i => {
this.certMap.set(i, []);
});
data.forEach(i => {
this.certMap.get(i.certTypeDescription).push(i);
});
this.certKeys = Array.from(this.certMap.keys());
this.allData = data;
console.log(this.certMap);
}
Now when this translates to the HTML portion of this, I am wanting to display the most recent record (or the [0] element) of each key array. This is already being accomplished. However, the other portion is that in the accordion drop down, I need to retrieve the rest of the elements save for the [0] element. below you will see what I have so far:
<app-basic-card myTitle="Data">
<i cardIcon class="uxd uxd-mode-edit uxd-lg uxd-pointer text-primary" (click)="openEditDialog()"></i>
<div cardBody class="accordion" *ngIf="allData; else loading">
<p *ngIf="allData?.length === 0">
No allData found...
</p>
<mat-accordion *ngIf="allData?.length>0">
<mat-expansion-panel *ngFor="let cert of certKeys">
<mat-expansion-panel-header>
<mat-panel-title class="list-group list-group-flush">
<ng-container>
<div>
<div class="w-50 float-left">{{cert}}</div>
<div class="w-50 float-right">
<i class="uxd uxd-lg" [ngClass]="getCertIcon(certMap.get(cert)[0]?.certificationResult)"></i>
{{getDateTaken(certMap.get(cert)[0].certificationDate)}}
</div>
</div>
</ng-container>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-container>
<div *ngFor = "let certKeys of allData">
<div class="w-50 float-left">{{cert}}</div>
<div class="w-50 float-right">
<i class="uxd uxd-lg" [ngClass]="getCertIcon(certMap.get(cert).certificationResult)"></i>
{{getDateTaken(certMap.get(cert).certDate)}}
</div>
</div>
</ng-container>
</mat-expansion-panel>
</mat-accordion>
</div>
<ng-template cardLoading #loading class="text-center">
<mat-spinner class="loading-spinner" style="margin:0 auto;" diameter="50"></mat-spinner>
</ng-template>
</app-basic-card>
My question is how do I accomplish retrieving every element but the [0] element of each key array? There is something that I very obviously am missing. I would appreciate any answers that are given and resources that may point me in the right direction. I thank you all for your time.
I don't know Angular tbh, but if you can modify the array you could use slice method, which returns a shallow copy of the original array, so the original will stay untouched.
Can you change this line:
<div *ngFor = "let certKeys of allData">
Into this:
<div *ngFor = "let certKeys of allData.slice(1)">
?
Working snippet of the slice() function.
const items = [1, 2, 3, 4];
const itemsWithoutTheFirstItem = items.slice(1);
// Target array contains all but first item
console.log(itemsWithoutTheFirstItem);
// Original array stays the same
console.log(items);
I think of two options right now. The first one is using *ngIf or a simple Pipe.
First option:
<mat-expansion-panel *ngFor="let cert of certKeys; let index = index">
<mat-expansion-panel-header *ngIf="index > 0">
...
</mat-expansion-panel>
Second option:
Create a Angular Pipe, which returns a new array except the first entry:
#Pipe({name: 'ignoreFirst'})
export class IgnoreFirstEntryPipe implements PipeTransform {
transform(arr: any[]) {
// Returns a new array without the original first entry
return arr.slice(1);
}
}
And in your html:
<mat-expansion-panel *ngFor="let cert of certKeys | ignoreFirst">
<mat-expansion-panel-header>
...
</mat-expansion-panel>

Angular 2: Push into array upon clicking an object

When I click a row of object, I want it to be pushed into the selectedProducts array.
I have this in my typescript
selectedProducts: Product[] = [];
select(prod) {
this.selectedProducts.push(prod);
console.log(this.selectedProducts);
}
but it only gets the first object I click in the user's side
Below is my HTML
<div class="list-content fluid">
<div class="products-cards" *ngFor="let product of dataSource['docs']">
<app-product-card [product]="product" (click)="select(product)"></app-product-card>
</div>
</div>
I suggest to try this :
<div class="list-content fluid">
<div class="products-cards" *ngFor="let product of dataSource['docs']" (click)="select(product)">
<app-product-card [product]="product"></app-product-card>
</div>
</div>

AngularJS filter is requiring two clicks before it filters results

I am a beginner at angular. I am pretty certain I am doing this the completely incorrect way but because I finally have it "somewhat working" as it works on the second click I am stuck going in this direction and can't seem to figure out another way to do it.
The filter sorts on the second click because it is initialing as "undefined" before the first click and sets it based on that I believe.
In my html:
<div class="col-xs-12 col-sm-4 location-list" ng-repeat="key in careerlist.location">
<div class="locations" ng-click="careerlist.criteriaMatch()">{{key}}
</div>
</div>
<div class="col-xs-12">
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-4 job-container" ng-repeat="job in careerlist.career | filter : searchText | filter: selectExperience | filter: careerlist.criteria.name">
<h2>
{{job.title}}
</h2>
<h3>
{{job.location}}
</h3>
<div class="job-description" ng-bind-html="job.description | limitHtml : 200">
</div>
<br><br>
<button>Read More</button>
</div>
<br><br>
</div>
</div>
In my controller:
cl.criteriaMatch = function( criteria ) {
jQuery(document).on('click', '.locations', function(){
cl.criteria = {};
console.log("just the element text " + jQuery(this).text());
cl.criteria.name = jQuery(this).text();
return function( criteria ) {
return criteria.name === criteria.name;
};
});
};
Use ng-click instead of jQuery#on('click'). Angular does not know that the filter should be updated.
Update
#Makoto points out that the click is bound twice. It very much looks like you should just remove the jQuery binding altogether. I would even go so far as suggesting removing jQuery from you project.

Resources