Angular2+ whole DOM flicker on component load ngFor - javascript

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");

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 !

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

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

Can I search filter through a child component? (Angular)

I've got a parent component that I'm passing data down to a child component with *ngFor / #input. That child component is created x number of times depending on how many objects are in the pciData array
pciData is an array of about 700 data objects, all with a "hostname" property/value. when passed down to app-system, the hostname of that object shows on a button. I want the end user to be able to filter through those buttons by that hostname, only showing the app-system components that match.
On the parent component template, how would I create a search bar that can filter the app-system components by their hostname property values? In the code below, I've tried piping this like:
<ng-container *ngFor="let system of pciData | searchFilter: system.hostname | paginate: { itemsPerPage: 180, currentPage: p }; let i = index ">
but "system" comes back undefined. However, when I just type in {{system.hostname}} in the template under the loop, it does loop through every single object and display the hostname. Any assistance would be appreciated.
Here's the parent component. I've removed the implimentation that I tried to do with the filter to avoid confusion:
import { Observable } from 'rxjs';
import { Ipcidata } from '../Shared/Ipcidata';
import { map, filter, switchMap } from 'rxjs/operators';
import { Ng2SearchPipeModule } from 'ng2-search-filter';
#Component({
selector: 'app-system-status',
templateUrl: './system-status.component.html',
styleUrls: ['./system-status.component.css'],
})
export class SystemStatusComponent implements OnInit {
#Input() pciData: any;
constructor() {}
searchText;
p: number;
filteredValues : any;
ngOnInit() {
}
}
Here's the parent template, "app-system" is what I'm trying to search through the values of :
<ngx-spinner></ngx-spinner>
<section class="app-container">
<div class ="card-container">
<ng-container *ngFor="let system of pciData | paginate: { itemsPerPage: 180,
currentPage: p }; let i = index ">
<div class='cardboi'>
<app-system [systemInput]="system"></app-system>
</div>
</ng-container>
</div>
<div class="pagination-container">
<pagination-controls class='paginator' (pageChange)="p = $event"></pagination-controls>
</div>
</section>
lastly, here is the child template, to which the search bar filter should only show what the user inputs as the hostname. This is essentially just a button that pops up more data about that host when clicked. Again, the point of this is only to show the buttons with a certain hostname.
<button (click)="openDialog()" [ngClass]="{
'btn buttonGood':isValid(),
'btn buttonDateAlert':isValid()=='datewarning',
'btn buttonAlert':isValid()==false
}">{{systemInput.hostname.slice(0,13) | uppercase}}</button>
</div>
Thanks again for anyone who can help me with this.
In your *ngFor loop, the array you are looping though is being piped. The pipe has to be processed before the loop begins. So pciData is piped through the two pipes and then the resulting array is looped through. Which is why system doesn't exist at the time that the pipes are being processed.
My recommendation would be to not use a pipe for the search filters. Instead, create another pciData variable. You can call it something like filteredPciData. Bind to the onChange event of the search box in the parent component. When the search value changes, filter pciData (which should have all values) and use filteredPciData to store the results. Then in your HTML loop through filteredPciData instead of pciData

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 use ngFor index variable in child component

I have a created a list out of an array using ngFor, and I am importing in element's of that list via other components.
In my list component, I am using ngFor to get the index, I would like to use this index within the child components (see code) to create dynamic variables but I can't seem to get it to work.
//main list html template
<li *ngFor="#task of taskService.tasks; #i = index" class="list-group-item">
<task-item [task]="task"></task-item>
</li>
//task item component html template
<div class="read-only">
<label [contentEditable]="taskService.editable[i] == true" ng-model="task.title">{{task.title}}</label>
<p [contentEditable]="taskService.editable[i] == true" ng-model="task.description">{{task.description}}</p>
<task-actions [task]="task"></task-actions>
</div>
//task actions component html template
<button type="button" (click)="taskService.deleteTask(task.title)" class="btn btn-danger btn-xs">delete</button>
<button type="button" (click)="taskService.editTask(i)" class="btn btn-info btn-xs">Edit</button>
<button type="button" (click)="completeTask()" class="btn btn-success btn-xs">complete</button>
Within the 'taskService', I have a method on click called - editTask(i) - I want to be able to pass the index of the array item
my class looks something like this:
export taskService {
editable = [];
editTask(i){
this.editable[i] == true;
}
}
I hope I have explained that well enough, let me know if you need more info!
You could provide it as an input:
<task-item [task]="task" [index]="i"></task-item>
And in your TaskItemComponent class:
#Component({
selector: 'task-item',
(...)
})
export class TaskItemComponent {
#Input()
index: number;
(...)
}
Edit
Since you want to use the index into the actions component, you also need to define it as input:
<task-actions [task]="task" [index]="index"></task-item>
And in your TaskItemComponent class:
#Component({
selector: 'task-actions',
(...)
})
export class TaskItemActionsComponent {
#Input()
index: number;
(...)
}

Categories