Displaying all but [0] element of map key arrays - 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>

Related

Angular 9 - Add removed li to another ul - Eventbinding

Hello everyone =) I have started to practice Angualar 9 and I really like the component approach. In order to practice property and eventbinding, i started a project of deckbuilding component with two lists. English is not my main language but I will try to explain clearly.
IMPORTANT : I really want to practice this tool, so could you, in the answer, explain your reasoning ? If you have solution tracks, i can search by myself too in order to learn.
GOAL :
I have two lists (CardlistComponent and DecklistComponent) and each time i click on one LI in CardListComponent (or DecklistComponent), this same element is removed but added to the other list.
COMPONENTS :
DeckbuilderComponent which contains my two other components (the two lists)
CardlistComponent (the first list)
DecklistComponent (the second list)
ISSUE :
I can remove the element in CardlistComponent, but I can't add the same element to the DecklistComponent. I guess the problem is the way I use arrays in Javascript.
DeckbuilderComponent (HTML) (Parent component)
<app-cardlist></app-cardlist>
<app-decklist
(cardAdded)="onCardAdded($event)">
</app-decklist>
*DeckbuilderComponent (TS) * (Parent component) I guess one of the problems is here because I duplicated the logic of add an item in the parent AND in DecklistComponent
#Output() cardAdded = new EventEmitter<{name: string}>();
decklist: Card[] = [
new Card('CardC'),
new Card('CardD')
]
onCardAdded(element : HTMLLIElement){
this.decklist.push({
name: element.textContent
})
}
CardlistComponent
#Output() cardRemoved = new EventEmitter<{cardName: string}>();
cardlist: Card[] = [
new Card('CardA'),
new Card('CardB')
]
removeCard(indexCard: number, element: HTMLLIElement){
this.cardlist.splice(indexCard, 1)
this.cardRemoved.emit({
cardName: element.textContent
});
DecklistComponent this list has to recieve the element removed in the first list
#Input() cardName: {name: string};
decklist: Card[] = [
new Card('CardC'),
new Card('CardD')
]
onCardAdded(element : HTMLLIElement){
this.decklist.push({
name: element.textContent
})
}
Here is the HTML of my two components just in case.
DecklistComponent (HTML)
<div class="container">
<div class="col-xs-12">
<ul class="list-group">
<li
*ngFor="let card of decklist; let indexCard=index"
class="list-group-item"
#cardName
>
<a href="#" class="list-group-item list-group-item-action" >{{ card.name }}</a>
</li>
</ul>
</div>
CardlistComponent (HTML)
<div class="container">
<div class="col-xs-12">
<ul class="list-group">
<li *ngFor="let card of cardlist; let indexCard=index" class="list-group-item">
<a href="#"
(click)="removeCard(indexCard, card)"
class="list-group-item list-group-item-action">{{ card.name }}</a>
</li>
</ul>
</div>
If you want something else I didn't mention, don't hesitate to tell me and have a nice day full of code =D
You can design DeckbuilderComponent as Container Component and CardlistComponent, DecklistComponent as Presentational Components
source
Container Components: These components know how to retrieve data from
the service layer. Note that the top-level component of a route is
usually a Container Component, and that is why this type of components
where originally named like that
Presentational Components - these components simply take data as input
and know how to display it on the screen. They also can emit custom
events
<deck-list (remove)="removeAndMoveTo($event, deckList, cardList)" [items]="deckList"></deck-list>
<card-list (remove)="removeAndMoveTo($event, cardList, deckList)" [items]="cardList"></card-list>
<div class="container">
<div class="col-xs-12">
<ul class="list-group">
<li *ngFor="let card of cardlist; let indexCard=index" class="list-group-item">
<a href="#"
(click)="removeCard(card)"
class="list-group-item list-group-item-action">{{ card.name }}</a>
</li>
</ul>
</div>
class CardlistComponent {
#Output("remove") removeCardEmitter: EventEmitter<Card>;
constructor() {
this.removeCardEmitter = new EventEmitter();
}
removeCard(card: Card){
this.removeCardEmitter.emit(card);
};
}
class DeckbuilderComponent {
cardList: Card[];
decklist: Card[];
constructor() {
this.cardList = [
new Card('CardA'),
new Card('CardB')
];
this.decklist = [
new Card('CardC'),
new Card('CardD')
];
}
removeAndMoveTo(card: Card, sourceList: Card[], targetList: Card[]) {
this.sourceList = this.sourceList.filter(pr => pr.id === card.id);
this.targetList.push(card);
}
}
Of course they are many solution to move element from one list to another. You can as well have one list and each card has some information to which group it belongs. Then value input binder would look like [items]="list.filter(pr => pr.isInCardList).
Another single-list approach might use rxjs. One observable which gets split with partition operator

Angular - Rendering filtered array issue

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

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.

ngIf array contains index

I have the following code:
<li *ngFor="let item of serviceList; let i = index;">
<button *ngIf="currentTools contains i" (click)="processService(item)">
Run Service</button>
</li>
Is there any way in Angular 2 to check if an array contains the index of my item?
I basically only want to show the items when their index number is in an array (currentTools), which is dynamically modified in a different process.
<li *ngFor="let item of serviceList; let i = index;">
<button *ngIf="currentTools.indexOf(i)>-1" (click)="processService(item)">
Run Service
</button>
</li>
Try this
You need to avoid function call inside a data binding like *ngIf. Taking this in to account, you should probably do this
<li *ngFor="let item of serviceList; let i = index;">
<ng-container *ngFor=let currentTool of currentTools>
<button *ngIf="currentTool === i" (click)="processService(item)">
Run Service</button>
</ng-container>
</li>
You can get more info about why not tu call function inside data binding here Don't Use Functions Inside Angular Templates and What to Use Instead
Also, you can check a similar answer here

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>

Resources