execute a function evertime ngFor is finished displaying data - javascript

I am trying to call a function everytime my ngFor is done loading data from my API.
but the callback is only triggering on first load of the ngFor.
how can I execute the callback everytime my ngFor is changed;
I used this answer: https://stackoverflow.com/a/38214091/6647448
here is what I have so far...
HTML
<button class="btn" (click)="changeDate()"></button>
<div *ngFor="item of items; let last = last">
<div>{{item}}{{last ? ngForAfterInit() : ''}}</div>
</div>
TS
this.ngForIsFinished = true;
ngForAfterInit() {
if (this.ngForIsFinished) {
this.ngForIsFinished = false;
}
}
changeDate() {
// this is where i trigger my ngFor to change its content
}
The person who answered said that you just need to set the ngForIsFinished back to true but I am having a hard time where to set it on my code.

Try to use ChangeDetectionStrategy.OnPush and last variable of ngFor. Because ChangeDetectionStrategy.Default always checks methods, so fooMethod(...) will be called multiple items:
<div *ngFor = "let title of iterated; let i=index; let last=last">
{{last ? fooMethod(last) : '' }}
</div>
yourComponent.ts:
import { Component, ChangeDetectionStrategy } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
name = 'Angular 4';
iteratedData = [
{"title":"test1","description":"foo","name":"name1"},
{"title":"test2","description":"foo","name":"name2"},
{"title":"test3","description":"foo","name":"name3"},
{"title":"test4","description":"foo","name":"name4"}
];
fooMethod(v) {
if (v)
console.log(`This is from the method ${v}`);
}
}
UPDATE:
After you've loaded data you should call slice method as Angular 2 change detection strategy doesn't check the contents of arrays or object. So try to create a copy of the array after mutation:
this.iteratedData.push(newItem);
this.iteratedData = this.iteratedData.slice();
OR:
constructor(private changeDetectorRef: ChangeDetectorRef)
And you can call a method markForCheck to trigger a changeDetection:
this.iteratedData.push(newItem);
this.changeDetectorRef.markForCheck();

Here is the example:
Component:
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular 5';
data:any = [];
ngAfterViewInit(){
this.changeDate();
}
ngForAfterInit(valuue) {
alert('FOR LOOP COMPLETED : '+ valuue);
}
ChangeDate(){
let i = 0;
setInterval(()=>{
this.data=[];
for(var j=0; j<10; j++){
this.data.push("TEST DATA "+ j+" SET AT : "+i);
};
i+=1;
},7000);
}
}
html:
<div *ngFor="let item of data; let last=last;">
{{item}} {{last? ngForAfterInit(item) :''}}
</div>
SetInterval is like your API which will assign data.This is working for me
https://stackblitz.com/edit/pokemon-app-4n2shk

Related

ngOnChange is not called when the parent updates the child

i want to know how ngOnChanges callback works. so i have added it to observe changes in a prpoperty annotated with Input decorator as follows:
#Input() postsToAddToList: Post[] = [];
now, when I compile the code i add some values that causes change in the property annotated with #Input, but that does not cause the the ngOnChanges callback to be called and executed. please see logs shown in the screen-shot posted below.
i want to see the logs in the ngOnChanges displayed in the browser.
please let me know what prevents the ngOnChanges to be invoked and called correctly
app.component.ts:
import { Component } from '#angular/core';
import { Post } from './post-create/post-create.component';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'binding2';
postsArray: Post[] = [];
onReceiveSubmittedEvtEmitter(post: Post) {
this.postsArray.push(post);
console.log("onReceiveSubmittedEvtEmitter->: post.title: " + post.title);
console.log("onReceiveSubmittedEvtEmitter->: post.content:" + post.content);
}
}
app.component.html:
<app-post-create (onPostSubmittedEvtEmitter)="onReceiveSubmittedEvtEmitter($event)"></app-post-create>
<app-post-list [postsToAddToList]="postsArray"></app-post-list>
post-list.component.ts:
import { Component, Input,OnInit, OnChanges, SimpleChanges,Output, EventEmitter } from '#angular/core';
import { Post } from '../post-create/post-create.component';
#Component({
selector: 'app-post-list',
templateUrl: './post-list.component.html',
styleUrls: ['./post-list.component.css']
})
export class PostListComponent implements OnInit {
constructor() {}
#Input() postsToAddToList: Post[] = [];
ngOnInit(): void {}
ngOnChanges(changes: SimpleChanges) {
for (let changedProperty in changes) {
if (changes.hasOwnProperty(changedProperty)) {
console.log("ngOnChanges->: changedProperty: " + changedProperty);
console.log("ngOnChanges->: changedProperty:" + changedProperty);
switch(changedProperty) {
case 'postsToAddToList':
console.log("ngOnChanges->: changes[changedProperty].previousValue: " + changes[changedProperty].previousValue);
console.log("ngOnChanges->: changes[changedProperty].currentValue):" + changes[changedProperty].currentValue);
break;
}
}
}
}
}
post-list.component.html:
<!-- post-list.component.html -->
<h3>List</h3>
<ng-container *ngIf="postsToAddToList.length; else elseTemplate">
<ul class="list-group">
<li class="list-group-item" *ngFor="let post of postsToAddToList; let i = index">
<h5>{{i+1}}) {{post.title}}</h5>
<p>
{{post.content}}
</p>
</li>
</ul>
</ng-container>
<ng-template #elseTemplate>
<div class="alert alert-danger" role="alert">
No Post Found!
</div>
</ng-template>
error screen-shot:
As u are using push() in your parent component, ngOnChanges will not be invoked in the child component. Instead of using push() you can reassign value to postsToAddToList every time there is a change in it.

await fetch Angular between components via Input

I'm making a request with fetch to the reqres api users in app.component, then i share data to his child component (hello.component) via Input. I get the correct user names in child template, I'm trying to print the users in console but i get an empty array. It's there a way to 'await' the response of another component? i guess this is an asynchronous issue. Here is the link: https://stackblitz.com/edit/angular-ivy-b3m1kp?file=src%2Fapp%2Fhello.component.ts
Thanks in advance.
app.component:
import { Component, VERSION, OnInit } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
public usuarios;
constructor(){
this.usuarios = [];
}
ngOnInit(){
this.getUsers();
}
getUsers(){
fetch('https://reqres.in/api/users')
.then(data => data.json())
.then(users => {
this.usuarios = users.data;
console.log(this.usuarios);
});
}
}
hello.component:
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'hello',
template: `<h1 *ngFor="let user of usuarios">Hello {{user.first_name}} </h1>`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent{
#Input() usuarios;
ngOnInit(){
console.log(this.usuarios);
}
}
app.component.html:
<hello [usuarios]="usuarios"></hello>
As the fetch operation is asynchronous, usuarios array would be empty upon initialization for the child. To detect the value changes move logic which will use the fetched results to ngOnChanges.
Like this:
ngOnChanges(changes: SimpleChanges) {
const { previousValue, currentValue } = changes.usuarios;
if (previousValue !== currentValue) {
console.log(this.usuarios);
}
}
Having a condition to check if the value has changed inside ngOnChanges is essential, otherwise the logic will be constantly triggered.
<hello *ngIf="usuarios.length>0" [usuarios]="usuarios"></hello>
<p>
Start editing to see some magic happen :)
</p>

Why Angular 2+ innerHTML is calling method multiple times in a single statement, How to solve this

I have the template view like this.
<p [innerHTML]="myfunction()"></p>
and, ts file be like
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
myfunction(){
alert(name);
return '<div>abcd</div>';
}
}
Its a simple method calling from html to render the html content through innerhtml, here the myfunction() is calling multiple times, and i'm getting multiple alerts, can anyone help me to solve this.
the stackblitz for this is link
thanks in advance
You can use pure pipe to only rerun it if inputs to the pipe have changed:
#Pipe({name: 'functionCaller'})
export class FunctionCallerPipe {
transform(func: any, ...args: any[]): any {
return func(...args);
}
}
And use it like that:
<p [innerHTML]="myfunction| functionCaller : name"></p>
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
myfunction(name: string){
alert(name);
return '<div>' + name + '</div>';
}
}

Two way binding on Array elements using <form>

I have an array of objects in a component. which I will iterate in the template.
app.component.ts
import {Component, OnInit} from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'sample-app';
classesData = [];
constructor() {
}
ngOnInit() {
this.classesData = [
{title: 'Hello0'}, {title: 'Hello1'}, {title: 'Hello2'}
];
}
duplicate() {
const newData = JSON.parse(JSON.stringify(this.classesData[1]));
newData.title += 'Copy';
this.classesData.splice(1, 0, newData);
}
}
app.template.html
<form #testingFrom="ngForm">
<p>{{classesData | json}}</p>
<div *ngFor="let classData of classesData; let i=index">
<input [(ngModel)]="classData.title" name="{{'title-' + i}}" type="text">
</div>
<button (click)="duplicate()">Duplicate</button>
</form>
My aim is when a user clicks on the duplicate button I simply add a new element at index 1 in an array. My initial state looks like (before user clicks)
And my state after a user clicks duplicate button
In the image above at 3rd input field, we are getting Hello1Copy instead of Hello1.
I completely suspect that this behavior is happening because of conflict in the name attribute value. For this case only, if you splice the newItem at first location, it only adds that's variable and other DOM's doesn't re-render. For cross verification you can try replacing input element with simple binding like {{classData.title}} and everything works fine.
This behavior can easily be solved by not conflicting name attribute value for all time. What that means is to assign a unique id variable with each collection item and use it.
this.classesData = [
{ id: 1, title: 'Hello0' },
{ id: 2, title: 'Hello1' },
{ id: 3, title: 'Hello2' }
];
duplicate() {
const newData = JSON.parse(JSON.stringify(this.classesData[1]));
newData.title += 'Copy';
newData.id = Date.now()
this.classesData.splice(1, 0, newData);
}
Template
<div *ngFor="let classData of classesData;let i=index">
<input [(ngModel)]="classData.title" [name]="'title_'+classData.id" type="text">
</div>
Stackblitz
You can also verify the same by removing name attribute from each input field. But that would not suffice, it would throw
ERROR Error: If ngModel is used within a form tag, either the name
attribute must be set or the form control must be defined as
'standalone' in ngModelOptions.
So add [ngModelOptions]="{standalone: true}" on each input field to make input working without name attribute. As suggested in another answer by #briosheje, you can also re-enforce rendering using trackBy.
PS: I'm investigating why this works differently when there is a combination of name and input, I suspect about form API wiring with input element. I'll update the answer as soon as I get something.
The problem is that you are using a form. Because you're using a form, you need to specify how angular should track the changes for your form items, if you're planning to alter the existing source. You can do such using the trackBy pipe:
<form #testingFrom="ngForm">
<p>{{classesData | json}}</p>
<div *ngFor="let classData of classesData; let i=index; trackBy: trackByFn">
<input [(ngModel)]="classData.title" [name]="'title-' + i" type="text">
</div>
<button (click)="duplicate()">Duplicate</button>
</form>
Typescript relevant part:
trackByFn(index: any) {
return index;
}
Please note that adding elements to the collection will work in your original example.
Working stackblitz: https://stackblitz.com/edit/angular-uabuya
Make another variable and iterate that variable to crate inputs
import { Component,OnInit } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
title = 'sample-app';
originalData=[];
classesData = [];
constructor() {
}
ngOnInit() {
this.classesData = [
{title: 'Hello0'}, {title: 'Hello1'}, {title: 'Hello2'}
];
this.originalData=[...this.classesData]; // changed here
}
duplicate() {
const newData = JSON.parse(JSON.stringify(this.classesData[1]));
newData.title += 'Copy';
this.classesData.splice(1, 0, newData);
}
}
Working Demo
You can solve your issue using "trackBy" feature. Please see below code sample.
app.component.ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
title = 'sample-app';
classesData = [];
constructor() {}
ngOnInit() {
this.classesData = [
{ title: 'Hello0' },
{ title: 'Hello1' },
{ title: 'Hello2' }
];
}
duplicate() {
const newData = JSON.parse(JSON.stringify(this.classesData[1]));
newData.title += 'Copy';
this.classesData.splice(1, 0, newData);
}
trackByIndex(index: number, obj: any): any {
return index;
}
}
app.component.html
<form>
<p>{{classesData | json}}</p>
<div *ngFor="let classData of classesData; let i=index;trackBy:trackByIndex;">
<input [(ngModel)]="classesData[i].title" name="{{'title-' + i}}" type="text" />
</div>
<button (click)="duplicate()">Duplicate</button>
</form>
Please let me know if this solution works for you!

How to push object into an object array in Angular 6?

I have an empty object array like this groupList:any = {} and now I want to push an object into it. I am trying to name and description as an object.
It is not an array, array is represented like [] , probably you need
groupList:any = [];
and then,
this.groupList.push({name:'you',description:'what is array'});
app.component.ts
declare var require: any;
import { Component } from '#angular/core';
import { OnInit } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent{
groupList:any = [];
arrayVal:any;
currentVal : any;
title = 'projectchart';
public array = [{"id":1},{"id":3},{"id":5}];
getPrev(){
this.array.forEach((item, index) => {
this.groupList.push(item.id);
});
console.log(this.groupList);
}
}
app.component.html
<button (click) ="getVal()">Previous Value</button>

Categories

Resources