How to access multiple elements generated from ngFor directive in Angular? - javascript

I want to add CSS class to div elements generated with *ngFor Angular directive. Firstly, I tried with #ViewChild directive and Renderer2, but it seems it does not work with multiple elements. Secondly, I tried to manipulate div elements with #ViewChildren directive, but could not find a suitable syntax example that would work for me. How can I access those elements and work with their classList?
The error message that I get is: Error: Uncaught (in promise): TypeError: el.classList is undefined addClass
HTML
<div
class="row" #rowDiv
*ngFor="let courtReservation of currentIntervalReservations"
>
<div class="court">
<h1 class="court-name">{{ courtReservation.courtName }}</h1>
</div>
<lp-player-diagram
*ngFor="let res of courtReservation.reservations; let i = index"
[playerName]="res.playerName"
[index]="i"
></lp-player-diagram>
</div>
TS
export class MainGantogramComponent implements OnInit, AfterViewInit {
#ViewChildren('rowDiv') rowElements?: QueryList<HTMLDivElement>;
constructor(private renderer: Renderer2) {}
/* ... */
setGridTemplateClass(currentTime: Time) {
if (currentTime.minutes == 0) {
this.rowElements?.forEach((div) =>
this.renderer.addClass(div, 'short-grid-template')
);
} else {
this.rowElements?.forEach((div) =>
this.renderer.addClass(div, 'standard-grid-template')
);
}
}
}

If I understood you correctly you can just use a field to store the css class you need:
<div
class="row"
[ngClass]="gridClass"
*ngFor="let courtReservation of currentIntervalReservations"
>
<div class="court">
<h1 class="court-name">{{ courtReservation.courtName }}</h1>
</div>
<lp-player-diagram
*ngFor="let res of courtReservation.reservations; let i = index"
[playerName]="res.playerName"
[index]="i"
></lp-player-diagram>
</div>
----
setGridTemplateClass(currentTime: Time) {
this.gridClass = currentTime.minutes === 0 ? 'short-grid-template' : 'standard-grid-template'
}
this is how it works in angular, Renderer is very seldom used

Related

$.emit is not a function on ng-select change/ngModelChange event

I'm having an issue understanding what is happening in my code (Angular 13).
When I use the (change) or the (ngModelChange) of the ng-select component to call the function selectPlan(), I get this error : ERROR TypeError: this.planSelected.emit is not a function. Also, whatever value I put in the emit function, it does not work so I don't think the issue is coming from the value I emit.
When I call selectPlan() in every other way possible, the EventEmitter is emitting correctly (with (click) for example) and I'm receiving the Plan in the parent component. I'm using Output() and EventEmitter everywhere on my project, and it's the first time something like this happens.
Here is my component.html :
<div class="dropdown-with-add-button">
<ng-select
class="custom-ng-select"
notFoundText="{{ 'labelNoResultAfterResearch' | translate }}"
id="dropdown-list-groups"
[clearable]="false"
[(ngModel)]="planSelected"
(change)="selectPlan()"
>
<ng-container *ngIf="plansSidebar">
<ng-option *ngFor="let plan of plansSidebar.plans">
<div class="plan-container">
<div class="image-container">
<img class="plan-item-image" src="{{ plan.imageChaine }}" />
</div>
<div class="text-container">
<p class="plan-item-libelle">{{ plan.libelle }}</p>
</div>
</div>
</ng-option>
</ng-container>
</ng-select>
</div>
And here is my component.ts :
export class PlanChildComponent implements OnInit {
_plansSidebar: PlansSidebar;
get plansSidebar(): PlansSidebar {
return this._plansSidebar;
}
#Input() set plansSidebar(value: PlansSidebar) {
if (value) {
this._plansSidebar = value;
this._plansSidebar.plans.sort(
(x, y) => x.ordreAffichage - y.ordreAffichage
);
if (this._plansSidebar.plans.length > 0) {
this.selectedPlan = this._plansSidebar.plans[0];
}
}
}
#Input() idSelectedPlan: number;
#Input() loading: boolean;
#Output() planSelected: EventEmitter<PlanWithImage> =
new EventEmitter<PlanWithImage>();
selectedPlan: PlanWithImage;
constructor() {}
ngOnInit(): void {}
public selectPlan() {
this.planSelected.emit(this.selectedPlan);
}
}
If anyone has an idea of what's happening, thank you for letting me know !

Angular2+ whole DOM flicker on component load ngFor

When a user clicks an item it adds it to a list. To render the list i'm using an ngFor. After the user adds the first item to the selected list the whole screen/DOM flickers (everything disappears and reappears). This does not happen when the user then adds a second element to the selected array
Here is my ngFor loop:
<div
*ngFor="let sel of selected"
class="cw-selected-list-item"
>
<div class="cw-selected-name t4">{{ sel.id }}</div>
<app-checkbox class="cw-selected-r-tick"></app-checkbox>
<app-checkbox class="cw-selected-rw-tick"></app-checkbox>
</div>
When I comment out my app-checkbox components the flicker does not appear. Below is my app-checkbox component
TS
import { Component, OnInit, Input, Output, EventEmitter } from "#angular/core";
#Component({
selector: "app-checkbox",
templateUrl: "./checkbox.component.html",
styleUrls: ["./checkbox.component.scss"],
})
export class CheckboxComponent implements OnInit {
#Input() checked = false;
#Output() checkChanged = new EventEmitter();
constructor() {}
ngOnInit(): void {}
toggleChecked() {
this.checked = !this.checked;
this.checkChanged.emit(this.checked);
}
}
HTML
<div
class="checkbox clickable"
[ngClass]="{ 'checkbox-active': this.checked }"
(click)="toggleChecked()"
>
<img
class="checkbox-image"
[ngStyle]="{ opacity: !this.checked ? 0 : 1 }"
src="assets/buttons/tick.png"
/>
Any help would be much appreciated
EDIT
When the user clicks it simply call this function
selected = [];
public addToSelected(item: Document) {
this.selected.push(item);
}
HTML
<div
*ngFor="let hit of hits"
class="aisd-hit t4"
[ngClass]="{ 'hit-disabled': this.isAlreadySelected(hit) }"
(click)="
this.isAlreadySelected(hit) ? undefined : this.addToSelected(hit)
"
>
isAlreadySelected function
isAlreadySelected(doc: Document) {
return this.selected.includes(doc);
}
I found it!
It was importing my fonts locally through .woff2 files which was creating a full DOM refresh when a new component was created after view init.
Hope this helps someone
Example import:
url(/assets/fonts/opensans/mem8YaGs126MiZpBA-UFUZ0bbck.woff2)
format("woff2");

Facing issue while rendering index value of *ngFor directive in Angular 5

I am facing an issue while rendering index of *ngFor directive for a particular use case as follows.
Lets say we have an array of objects as
this.temp = [
{name:'John',age:24,visibility:'visible',
{name:'Kane',age:26,visibility:'hidden',
{name:'Ross',age:28,visibility:'visible',
{name:'Lui',age:21,visibility:'visible'
]
For rendering this in my app.component.html file I have html as follows
<div *ngFor="let user of temp; let i = index">
<div *ngIf="user.visibility === 'visible' ">
<div>{{i+1}}</div>
<div>{{user.name}}</div>
</div>
</div>
So as per the above array example, it renders users
1.John
2.Ross
3.Lui
Now there is a button name 'Change visibility' against each user in my UI, where in it will toggle the visibility state of user from 'hidden' to 'visible' and viceversa.
So clicking on button mentioned against John, it will set its visibility as
hidden but the UI rendered is
2.Ross
3.Lui
My expected output is
1.Ross
2.Lui
How to make the index render properly ?
The use case here is that I cannot modify/alter my this.temp array in terms of length.Basically I need the entire array with only visiblity property changed in it as per user actions.
Please help.
you can filter array first:
<div *ngFor="let user of temp.filter(us => us.visibility === 'visible'); let i = index">
<div>
<div>{{i+1}}</div>
<div>{{user.name}}</div>
</div>
</div>
like this way, you dont analize all array items too, more efficient and desired output.
Cheers!
You can also achieve your required result by using Pipe like this
HTML component
<div *ngFor="let user of temp | visiblefilter ; let i=index">
<span>{{i+1}} {{user.name}}</span> <button name={{user.name}} (click)="onHide($event)">Hide</button>
</div>
PIPE
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'visiblefilter',
pure: false
})
export class VisibleFilterPipe implements PipeTransform {
transform(items: any[]): any {
return items.filter(({visibility}) =>visibility=='visible');
}
}
You can check here with working example stackblitz
use a custom trackby function :
*ngFor="let user of temp; let i = index; trackBy: customTB"
customTB(index, item) {
return index + ' - ' item.name;
}

How to display just one element with same id

This is my first question at stack overflow
i just wanted to know a simple solution for the following case
<div *ngFor="let d of w.event">
<div class="date" id="d.date" >
<p>
<span style="font-size:1.75em">{{d.date | date:'dd'}}</span>
<br>
<strong> {{d.date | date:'EEE'}}</strong>
</p>
</div>
the looped div can have the same id
I just want to display the first div with a particular date and ignore the rest
can this be achieved with CSS or JavaScript
You can't use the same id on two elements. It's one of the few restrictions on ids.
You can use a class:
<div class="show">Yes</div> <div class="show">No</div>
...and then show either the first or second by using index 0 or index 1 after getting a list of matching elements:
var list = document.querySelectorAll(".show");
list[0].style.display = "none"; // Hides the first one
// or
list[1].style.display = "none"; // Hides the second one
Some other thoughts:
1. Rather than using style.display as I did above, you might add a class that hides the element.
2. You might use separate ids (or classes) for the elements so you don't need to index, e.g.:
<div id="show-yes">Yes</div> <div id="show-no">No</div>
then
document.getElementById("show-yes").style.display = "none";
// or
document.getElementById("show-no").style.display = "none";
On all browsers in my experience, you can do the first thing above (with querySelectorAll) with your invalid HTML with a selector like "[id=show], but don't. Fix the HTML instead.
In your question update, you show:
<div *ngFor="let d of w.event">
<div class="date" id="d.date" >
...
You've said you're aware of the fact you can't have multiple elements with the same id, so why code that? You can easily give them unique ids:
<div *ngFor="let d of w.event; let i = index">
<div class="date" id="d.date{{i}}" >
...
First of all, in HTML ID is a unique selector so one ID can be associate with only one element. if you want to achieve your desired functionality you have to assign different id for both DIV. and use javascript to hide and show DIV
<div id="showYes">Yes</div> <div id="showNo">No</div>
If you want to show one at a time you can go with *ngIf , as it will show only one at a time
<div id="show" *ngIf='your_status'>Yes</div>
<div id="show" *ngIf='!your_status'>No</div>
After your question update , you can create custom filter that will only return unique date , so only first unique date will be shown
// CREATE A PIPE FILTER :
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({name: 'checkUniqueDate'})
export class UniqueDatePipe implements PipeTransform {
transform(dataArray) {
let dates = [];
return dataArray.filter(data => {
return if(dates.indexOf(data.date) === -1) {
dates.push(data.date);
return true;
} else {
return false;
}
});
}
}
// TEMPLATE SIDE :
<div *ngFor="let d of (w.event | checkUniqueDate )">
Add the date in class also, then you can try below code
.YOUR_DATE{
display:none
}
.YOUR_DATE:first-child{
displany:inline
}

Angular 4 ngIf not toggled after variable being updated in ngOnInit

I am using Angular v4 and have a *ngIf in my template:
<div class="product-list row" *ngIf="products.length > 0">
<div *ngFor="let product of products" class="product-container">
...
</div>
</div>
and in my component file I have:
public products = [];
....
public ngOnInit(): void {
this.productsService.all().toPromise().then( (data: Product[]) => {
this.products = data;
});
}
However the ngIf will not be toggled after products is set. When I add a button and set the variables manually the ngIf will be toggled!
I tried changing the if statement to products?.length > 0 but it doesn't work as well.
Found my answer from this post:
Triggering change detection manually in Angular
According to Angular's documents https://angular.io/api/core/ChangeDetectorRef
detectChanges(): Checks the change detector and its children.
So by applying detectChanges Angular will manually check and update the node.

Categories