Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using array.prototype.some inside ngIf

I am developing an angular app using the 8th version. Inside the ngIf expression, I want to check something existanse inside array. So I have written the expression below:

*ngIf="questionniare.factors.some(item => item.intensities.length > 0)"

But now I get this error inside the console window:

Parser Error: Bindings cannot contain assignments at column 34 in [questionniare.factors.some(item => item.intensities.length > 0)]

But as see, I don't have any assignment in my condition. So what's the problem and how can I fix it?

(I know that I can define a method and do this job inside that method, but I want to know if this is a limitation on ngIf that I should consider next times?)

like image 459
ConductedClever Avatar asked Oct 18 '19 18:10

ConductedClever


3 Answers

The error message mentions an "assignment" but the problem is that you are creating an arrow function inside of the component template, which is not allowed. A feature request has been posted on GitHub, asking to support the creation of these functions in Angular templates.

In order to use Array.prototype.some in the template, you would have to define the predicate in the component code:

// You can define the predicate as a method
public itemHasIntensities(item): boolean {
  return item => item.intensities.length > 0;
}

// You can also define the predicate as an arrow function
public itemHasIntensities = (item): boolean => item.intensities.length > 0;

and pass that function as an argument to Array.prototype.some in the template:

*ngIf="questionniare.factors.some(itemHasIntensities)"

This stackblitz is similar to your original code and gives the same error. This other stackblitz shows the same example with the callback function defined in the component code.


That being said, and as mentioned in the question, the simplest solution is to evaluate the whole condition in a component method:

public showElement(): boolean {
  return this.questionniare.factors.some(item => item.intensities.length > 0);
}
*ngIf="showElement()"

Note: memoizing the function would be recommended to avoid performance issues.

like image 91
ConnorsFan Avatar answered Oct 17 '22 21:10

ConnorsFan


The problem is that your binding is iterating over the list and tracking a value that is being assigned in the background.

There are a couple solutions to this, first is putting your logic inside a public method on the component class that does this, which is the slower of the two solutions because every time change detection runs it will check your array value.

A better solution is to update a value on the component whenever the array changes

You can do that like this:

@Component({
  selector: 'my-component',
  templateUrl: 'my-component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyComponent {
  @Input()
  set questionaire(value: Questionaire) {
    this.questionaire$.next(value);
  }

  readonly questionaire$ = new ReplaySubject<Questionaire>(1);
  readonly hasIntensities$ = this.questionaire$.pipe(
    map(questionaire => questionniare.factors.some(item => item.intensities.length > 0))
  );
};

Then in the template you can do something like this: *ngIf="hasIntensities$ | async"

You could also accomplish it via change detector ref and ngOnChanges, but this should be the most efficient method

like image 27
Elliot Mendiola Avatar answered Oct 17 '22 20:10

Elliot Mendiola


This is not written in Angular Documentation but looks like you can't use Array.some in structural directives and you should move this logic into specific function inside the compnent.

like image 38
Yochai Akoka Avatar answered Oct 17 '22 22:10

Yochai Akoka