0

I'm trying to figure out what is happening in my program and I really don't understand. Before I jump to the problem, for the purpose of this question I simplified the code and I conducted some tests, to make sure that I localized the problem. So, in my component.html file I have this piece of code:

<div *ngFor="let item of entityDetails | keyvalue">
   <div *ngIf="hasConflict(item.key)">text</div>
</div>

As you can see I'm using *ngFor together with Angular KeyVauePipe and inside I check a condition using *ngIf. Entity Details is a json that I get through http request using Promise and it looks like this:

{rcn: "1912330", name: "Barcelona supercomputing Center", vatNumber: "ESS090000099D", category: "Research Organisation", categoryCode: "REC"}

In component.ts, the declaration:

public entityDetails: string[] = new Array();

and the retrieving the data:

this.service.getEntityDetails().then(data => {
   this.entityDetails = data;
});

In hasConflict method, all I do is printout:

hasConflict(item): Boolean {
    let check: boolean = false;
    console.log("test");
    return check;
}

When I run it and open the console, I can already see a bunch of printouts:

printouts

but then once I click anywhere on the website or I use scroll, they intensify, after one mouse click:

click

after quick scroll:

scroll

Any help will be appreciated.

miselking
  • 3,043
  • 4
  • 28
  • 39
MWPodgorni
  • 83
  • 2
  • 9
  • 1
    A probable cause is that you're calling the method hasConflict inside the ngif statement rather than having an pre-evaluated flag – crystalfrost Oct 11 '19 at 12:31
  • What is your question exactly? – robert Oct 11 '19 at 12:34
  • 1
    The issue is not due to keyvalue pipe. It's because of method call in template. Using method in template gets called every time change detection in that component happens. – Chellappan வ Oct 11 '19 at 12:36
  • My question is why is this happening and is there any way to fix it? – MWPodgorni Oct 11 '19 at 12:39
  • As me and Chellappan hinted with comments, you should not call a method inside the ngif at your template. This is what triggers this behaviour – crystalfrost Oct 11 '19 at 12:42
  • As @Chellappan mentioned, angular run change detection cycle and on every change detection it'll call the hasConflict() method. You should avoid using methods binded directly to HTML elements unless they serve a proper purpose. One use case could be to enable/disable submit button based on the input values validation. – Hemendra Oct 11 '19 at 12:46
  • Use pipe or directive based on your need to handle in such a scenario instead of using function. – Chellappan வ Oct 11 '19 at 12:46
  • @MichałPodgórni if an answer solves your issue, please mark it as accepted – crystalfrost Oct 16 '19 at 20:33

2 Answers2

1

Why is this happening.

This is happening beacuse you are calling a function from template. So every time angular's change detection runs, it updates the UI and calls the function again.

How to fix

Instead of calling a function from template, Prepare a map inside subscribe of getEntryDetails which you could use in template directly to check for the condition. For example:

conflictMap = {};

if(condition){
  conflictMap[key] = value;
}

and then directly in html:

<div *ngFor="let item of entityDetails | keyvalue">
   <div *ngIf="conflictMap[item.key]">text</div>
</div>

Further reading - using a function in *ngIf runs several times instead of once

Community
  • 1
  • 1
Archit Garg
  • 2,908
  • 2
  • 14
  • 22
  • [@Michael](https://stackoverflow.com/users/12094093/micha%c5%82-podg%c3%b3rni) Please share the actual implementation of `hasConflict` method so that I could provide you a better answer. – Archit Garg Oct 11 '19 at 12:53
  • `hasConflict(item): Boolean { let check: boolean = false; this.conflictedProperties.forEach(element => { if (item.key == element.property) { check = true; this.setConflict(element.conflictType); this.setSeverity(element.severity); } }); return check; }` This is the actual implementation. This function is supposed to take current item, check if the value is in an array with conflicted items and return true if it is, which displays fix button in html – MWPodgorni Oct 11 '19 at 12:58
0

As suggested in the comments [@Chellappan] you should use a pipe. Function calling inside ngif is what causes the problem due to angular's change detection triggering.

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({ name: 'hasConflict' })
export class HasConflictPipe implements PipeTransform {
 transform (value: any, type: string, ): boolean {

   console.log('test');
   return value ? true : false;
 }
}

and then inside your template

<div *ngFor="let item of entityDetails | keyvalue">
   <div *ngIf="item.key | hasConflict">text</div>
</div>

Obviously you should place your logic inside hasConflict that is specific to your case.

crystalfrost
  • 261
  • 1
  • 3
  • 14