Looping through Angular QueryList - javascript

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

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 !

apply function from methods to reverse string in paragraph in vue.js

Dears, I have tried to apply function to reverse string in paragraph text in vue.js,
I have created function to reverse words in methods called (reverseword) and added it card using :rule="reverseword()",but it does not work. your support is highly appreciated
Code:
<div class="post-box">
<span class="post-viwes">{{viwes}}</span>
<h3 class="post-title">{{title}}</h3>
<span class="post-date">{{date}}</span>
<p class="post-content">{{content}}</p>
<div class="row">
<div class = "col-sm-6 text-right">
<span class="post-author">{{author}} </span>
</div>
<div class = "col-sm-6 text-right" :rules="reverseword()">
<span class="post-category" >{{category.toUpperCase()}}</span>
</div>
</div>
)
</div>
</template>
<script>
export default {
props:["viwes","title","date","content","author","category"],
name:"posts",
methods: {
reverseWord: function () {
this.category = this.category.split('').reverse().join('')
}
}};
</script>```
Your reverseWord method attempts to mutate a prop (category).
You can't mutate a prop, because the mutation would be overwritten by the first update from the parent.
If/when you do want/need to change a prop value, you have do it in the parent component which will then pass the change down to the child component, through the props binding.
Updating the parent from child can be done by
either using $emit
or by using a store, external to both the child and the parent.
If, in fact, you don't want to mutate category, you just need to be able to use its reverse inside the template, create a computed property:
computed: {
reversedCategory() {
return this.category.split('').reverse().join('');
}
}
Use it in template as you would use a normal property:
<div class = "col-sm-6 text-right" :rules="reversedCategory">
<span class="post-category" >{{category.toUpperCase()}}</span>
</div>
The computed is reactive. Meaning every time category changes, reverseCategory will update accordingly.

Render images from Entries in Contenful + Angular app

The issues:
How to I access the includes array so I can get the image url to render the image? (or how to I get the images to render in general)
Why is are my description fields not rendering?
So I have a contentful service that gets all the entries as such:
//get all exhibits
getExhibits(query?: object): Promise<Entry<any>[]> {
return this.cdaClient.getEntries(Object.assign({
include : 2,
content_type: CONFIG.contentTypeIds.exhibit
}, query))
.then(res => res.items);
}
The above function is called in my Gallery component as such:
ngOnInit() {
this.contentfulService.getExhibits()
.then(exhibits => this.exhibits = exhibits);
}
And the data is rendered in the corresponding HTML as such:
<section class="gallery__section" [class.menu--open]="menu.isMenuClosed" *ngFor="let exhibit of exhibits">
<figure>
<img src="{{ exhibit.fields.file.url }}" alt="">
<!-- HOW DO I GET THE IMG URL -->
</figure>
<div class="gallery__text">
<h2 class="gallery__heading">Gallery</h2>
<p class="gallery__title">{{ exhibit.fields.title }}</p>
<h3 class="gallery__artist">{{ exhibit.fields.artist }}</h3>
<p class="gallery_onview">On View</p>
<p class="gallery__dates">{{ exhibit.fields.date }}</p>
<p class="gallery__description" *ngIf="exhibit.fields.description.content[0].content[0].value">{{ exhibit.fields.description.content[0].content[0].value }}</p>
</div>
</section>
Bellow is the JSON of one item the items array:
And here the image that is supposed be part of the above item, but is placed in a different array called includes:
Below is the console.log output of one exhibit:
You don't need to access the includes array. The Contentful SDK will automatically resolve the fields that contain a reference.
For your image issue, I think you're missing the field name for your file field: exhibit.fields.YOUR_FIELD_NAME.fields.file.url
For your description field, I would recommend using ngx-contentful-rich-text to parse the rich text into HTML.

Vue - dynamically create divs that use e.g v-on:click

I have something like that:
<div v-if="dataIsLoaded" v-for="(line, index) in json.data" v-on:dblclick="edit(index)" v-html="test(index)">
</div>
and test(index) returns html-ish string:
<div id=${index} v-on:click="remove(index)"></div>
How can I make it work?
My goal is:
If dataIsLoaded == true, then foreach (line, index) in json.data perform
test(index) and return its output to that container and display it as html.
Meanwhile output from test(index) can have different functions/events associated with them defined via string (as I showed above).
Why are you using v-html here? v-html should really only be needed in very specific and limited situations; I don't think it applies here. The string <div id=${index} v-on:click="remove(index)"></div> is not plain HTML, it's a Vue template string, so it won't work. v-html is also susceptible to XSS attacks; all in all I say avoid it like the plague.
Do something like this instead:
<template v-if="dataIsLoaded">
<div v-for="(line, index) in json.data" #dblclick="edit(index)">
<!-- Type1 -->
<div v-if="line.type === 'Type1'" :id="index" #click="remove(index)"></div>
<!-- Type2 -->
<div v-else-if="line.type === 'Type2'">...</div>
</div>
</template>
I've added the type property on each line object as a discriminator to determine which template should be used for the line.
Also I've hoisted the v-if="dataIsLoaded" into a <template> above the div because v-if is evaluated for each div generated by the v-for and the condition doesn't depend on the children so it needn't be repeated for each child (a minor optimization).
If you don't like the idea of having lots of v-if and v-else-if (a sort of "switch" statement in the template) then you can use <component :is="..."> instead:
<div v-for="(line, index) in json.data" #dblclick="edit(index)">
<component :is="line.type" :id="index"/>
</div>
import Type1 from './type1.vue'
import Type2 from './type2.vue'
export default {
components: {
Type1,
Type2
}
}
It's not possible with v-html, as the doc states
Updates the element’s innerHTML. Note that the contents are inserted
as plain HTML - they will not be compiled as Vue templates
A more component-thinking solution is to create a component(s) with needed behaviour and paste them as a results of v-for:
// componentWithOnClick.js
<script>
export default {
name: 'ComponentWithOnClick',
methods: {
remove() {
//
}
}
</script>
<template>
<div id=${index} v-on:click="remove(index)"></div>
</template>
and then use it in the parent file:
//app.js
import ComponentWithOnClick from './ComponentWithOnClick.vue'
{
components: {
ComponentWithOnClick
}
//
<div v-if="dataIsLoaded" v-for="(line, index) in json.data" v-on:dblclick="edit(index)">
<ComponentWithOnClick></ComponentWithOnClick>
</div>

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