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 !
Related
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
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");
So I want to get all the images in my component in an array, so I am using Angular's #ViewChildren which returns a QueryList of ElementRef:
#ViewChildren('img', { read: ElementRef }) images: QueryList<ElementRef>;
However, when ever I try to loop through the list am can't ever seem to get hold of the nativeElement. I am calling this in ngAfterViewInit()
this.images.forEach((img, index) => console.log("Image", img.nativeElement));
I don't get anything in the console.log, no error nothing.
This in turn does work:
console.log("this.images", this.images);
Below is an image of the log of console.log("this.images", this.images):
Here the html:
<div class="swiper-slide" *ngFor="let exhibit of exhibits; let i = index">
<app-exhibit-images exhibitImagesId="{{ exhibit.fields.eventImages[0].sys.id }}" [eventPicturesStart]="eventPicturesStart" (sendEndEventPictures)="getEndEventImages($event)"></app-exhibit-images>
<div id="exhibit-{{i}}"class="gallery__content" [ngClass]="{'show-event-pictures' : eventPicturesStart}">
<div class="gallery__poster" [ngClass]="{'gallery-expand' : isGalleryExpanded }">
<figure class="poster__image">
<picture>
<source srcset="{{ exhibit.fields.photo.fields.file.url }}" type="img/png">
<img #img src="{{ exhibit.fields.photo.fields.file.url }}" alt="{{ exhibit.fields.photo.fields.description }}">
</picture>
</figure>
</div>
<div class="gallery__text" [ngClass]="{'gallery-expand' : isGalleryExpanded }">
<button class="gallery-expand-button" (click)="expandGallery()"></button>
<h2 class="gallery__heading">{{ exhibit.fields.title }}</h2>
<h3 class="gallery__subheading">{{ exhibit.fields.subTitle }}</h3>
<h4 class="gallery__artist">{{ exhibit.fields.artist }}</h4>
<p class="gallery__description" *ngIf="exhibit.fields.description">{{ exhibit.fields.description }}</p>
</div>
</div>
</div>
What am I missing?
I ran into a similar issue with #ContentChildren. The solution was to subscribe to the changes observable of the QueryList.
Essentially, this will trigger whenever any of the state of the children changes. Normally this wouldn't be necessary but if your list of children is loaded in via a GET, this ensures the QueryList is data is loaded before executing on it. Its important to note the subscription will fire again whenever the any of the children's content changes.
#ContentChildren(forwardRef(() => ChildComponent), { descendants: true })
private children: QueryList<ChildComponent>;
ngAfterContentInit() {
// subscribe to changes
this.children.changes.subscribe(children => {
children.forEach(child => {
// do something with each child...
});
});
}
As #Jakopo Sciampi already mentioned a possibility is also to use the .toArray() function on the QueryList.
this.images.toArray().forEach((img, index) => {
console.log('Image', img.nativeElement);
});
Just make sure that you don't call the command to early => there will be no elements => the loop is not excecuted.
Don't call your loop in ngAfterViewInit, use ngAfterViewChecked instead. This will be triggered AFTER your child components are checked.
See lifecycle-hooks
We are trying to pass data from one component to another and below is the approach we are taking. When there is no data we want to show the error message
<div *ngIf="showGlobalError">
<h6>The reporting project doesn't have any Shippable Items</h6>
</div>
and the component.ts is like
showGlobalError = true;
constructor(private psService: ProjectShipmentService, private pdComp: ProjectDetailsComponent) {
this.psService.tDate.subscribe(x => this.cachedResults = x);
}
ngOnInit() { }
ngDoCheck() {
if (this.cachedResults.length > 0 && this.count <= 1) {
this.showGlobalError = false;
this.populateArrays();
this.count++;
}
}
populateArrays() {
this.reportingProject = [this.pdComp.rProjectNumber];
this.projectSalesOrder = this.pdComp.rSalesOrder;
this.clearFilter();
........
The issue is Even though there is data in the this.cachedResults that is this.cachedResults.length not equal to '0' for few seconds 'The reporting project doesn't have any Shippable Items' is shown in the page and then shows the data I am not sure if this something with the ngDoCheck() is causing this. Any help is greatly appreciated
Since, the default value of showGlobalError is true, the page load shows the error message.
Please make it by default false and make it true when this.cachedResults.length is 0 or this.cachedResults is undefined or this.cachedResults is null.
Hope this solves your problem.
Rather than subscribing in the code you can use the async pipe in your template
items$ = this.psService.tDate;
showGlobalError$ = this.items$.pipe(map(results => !results || !results.length));
constructor(private psService: ProjectShipmentService, private pdComp: ProjectDetailsComponent) { }
and in your template
<div *ngIf="showGlobalError$ | async">
<h6>The reporting project doesn't have any Shippable Items</h6>
</div>
<ng-template *ngFor="let item of items$ | async">
Do stuff with {{item | json}}
</ng-template>
This manages your subscription for you and fixes the memory leak you have in your code with the subscription you don't unsubscribe from.
Take a look at alibrary I wrote for this sort of thing, make caching data a lot easier. https://medium.com/#adrianbrand/angular-state-management-with-rxcache-468a865fc3fb
I've got a function that checks the value of an observable and based on that value performs some logic to change the value of a variable that I've defined in a service. Everything is working as it should except that the changed value is not rendering (updating) in the web component through string interpolation when it gets changed. It is being changed correctly in the service (when I console.log it is coming back correctly) but just not getting it to update the component for some reason. I've read a lot about ngZone, ChangeDetectorRef etc. and have implemented those strategies on other areas where I've had update issues in the past, but for some reason they are not working here. Code below, any guidance would be appreciated as I've banged my head against this one for a while.
//From the component where I'm performing the checks on the observable. The component where I'm doing the string interpolation on the data service value is a different component
ngOnInit(){
this.myObservable$ = this.scanitservice.decodedString$.subscribe(data => {
if (
data ==
this.adventuredata.exhibit[this.adventuredata.adventure.currentAmount]
.target
) {
this.adventuredata.sharedVariables.guideText =
'You found the right clue! Great job! Answer the question correctly to claim your prize.';
console.log(this.adventuredata.sharedVariables.guideText);
this.showQuiz = true;
this.changeDetector.detectChanges();
console.log('Evaluated as matched');
}
});
}
//From the data service
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class AdventuredataService {
constructor() {}
sharedVariables = {
guideText: '',
quizText: ''
};
}
<div class="card-body float-right m-0 p-0">
<img
src="{{ this.adventuredata.adventure.guideImage }}"
alt=""
class="card-img-top w-25 float-left"
/>
<span class="card-text p-0">
{{ this.adventuredata.sharedVariables.guideText }}
</span>
</div>