Home > Software engineering >  function call in Angular using typescript creates infinite loop
function call in Angular using typescript creates infinite loop

Time:03-30

I have a usecase as below: My objective is to update the array in if and else case.

The main array(cis) looks like below

enter image description here

.html

<sample-container  [cis]="filterCi(currentBaseline.cis, currentBaseline)"> </sample-container>

.ts This function runs a map over the array provided above and returns the same structure with all the objects execpt those with property checked:false (removed from the structure) by doing a recursive call on children everytime.

//retrieveChildren

retreiveChildren(children: Structure[]) : Structure[] {
  let filteredCis =  children && children.filter((ci) =>{
      if(ci.children && ci.children.length > 0){
         retreiveChildren(ci.children);
      }
      return ci.checked === true;
    }) ;
  return filteredCis;
}

//filterCi function

filterCi(cis: Structure[], baseline: Baseline): Structure[] {
    if(baseline.isHideElementsActivated){
      let newArray = [...cis]
      let result =  newArray.filter(ci => ci.checked === true).map((item,index) => {
        return {
          ...item,
          children : retreiveChildren(item.children)
        }
      })
      console.log("Result array...." , result)
      return result;
    }else{
      return cis;
    }
}

The issue I am facing here is, the filterCi function is calling infinitely. Initially, I thought it is because of the recursive call, but even after removing recursion it results in infinite loop.

Finally, I could conclude that it is because of map function used inside filterCi. Why is this behavior and how can I resolve it?

CodePudding user response:

As you are calling a method from an input binding in a template it will be evaluating on every change detection cycle, as angular has no way of knowing that this is a pure function (see: https://medium.com/showpad-engineering/why-you-should-never-use-function-calls-in-angular-template-expressions-e1a50f9c0496).

I think this may be the problem you are seeing. To resolve you can use an angular pipe: https://angular.io/guide/pipes

Edit: You should be able to treat the transform method of the pipe as the filterCi method creating a pipe like:

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

@Pipe({
  name: 'filterCis'
})
export class FilterCisPipe implements PipeTransform {

  public transform(cis: Structure[], baseline: Baseline): Structure[] {
    if(baseline.isHideElementsActivated){
      let newArray = [...cis]
      let result =  newArray.filter(ci => ci.checked === true).map((item,index) => {
        return {
          ...item,
          children : retreiveChildren(item.children)
        }
      })
      console.log("Result array...." , result)
      return result;
    }else{
      return cis;
    }
  }

  private retreiveChildren(children: Structure[]) : Structure[] {
    let filteredCis =  children && children.filter((ci) =>{
        if(ci.children && ci.children.length > 0){
           retreiveChildren(ci.children);
        }
        return ci.checked === true;
      }) ;
    return filteredCis;
  }

}

Then after registering the pipe in the app module. Use it in the template like:

<sample-container [cis]="currentBaseline.cis | filterCi:currentBaseline"> </sample-container>

CodePudding user response:

I tried your solution for retreiveChildren and it worked for me with the data you provided in the beginning of your question.

I agree @Jonathan with the statement that you shouldn't call the filter function from the HTML directly. You should use a Pipe as he mentioned.

But I found a bug in your implementation. Your function filters the parent nodes accordingly but it doesn't have any effect on the children. You should assign the return value of your recursion to the children to remove all children with are not checked.

That could look like following:

retrieveChildren(children: Structure[]): Structure[]{
  const filteredCis = children && children.filter((ci) => {
    if (!ci.checked) {
      return false;
    }
    
    ci.children = retrievedChildren(ci.children);
    return true;
  });

  return filteredCis;
}

I refactored your function a bit to be more readable in my opinion.

  • Related