4

NOTE: Original post edited to remove implemention details/complexity.

I have an ngFor loop that calls a method on a service. The method returns an array that is iterated over by the for loop.

The click event binding does not work if the returned array is an array of objects. It does work if the array is of a primitive type.

***contextmenu/right-click does work in either case, so this is affecting click and not contextmenu

***click events immediately before and after this for work as expected

***the behavior is the same even if I move the hardcoded method from the service to the component

Here is the for loop in context:

<div><span (click)="addVariable()"></span></div> <!-- works -->

<div *ngFor="let variable of designerService.getVariables();" (click)="onLeftClick()" (contextmenu)="onRightClick()">just a simple div</div> <!-- doesnt work -->

<div><span (click)="addVariable()"></span></div> <!-- works -->
public addVariable() {
  alert("add variable");
}

public onLeftClick() {
  alert("left click");
}

public onRightClick() {
  alert("right click");
}

With this implementation (or any other object type array) click will not work/fire:

public getVariables(): {name: string}[] {
  return [{"name": "dog"},{"name": "cat"},{"name": "bird"},{"name":"bear"}];
}

With this implementation click will work:

public getVariables(): number[] {
  return [1,2,3,4,5];
}

Super simple, doesn't work...

Any ideas on what might be the problem? I'm at the end of my rope with Angular at this point.

7
  • Can you also include the implementation of the getVariables method? Commented Feb 6, 2022 at 20:50
  • Does your getVariables() method return an observable? If so, you need to add async pipe to get it working. Commented Feb 6, 2022 at 20:53
  • It returns an array Commented Feb 6, 2022 at 21:00
  • Could you output the result of your method in the console to be sure you're getting an array? Commented Feb 6, 2022 at 21:31
  • Can you provide stackblitz? Commented Feb 6, 2022 at 21:55

1 Answer 1

2

Try this:

// YourComponent ts file
variables: {name: string}[];

constructor(public designerService: DesignerService) { }

ngOnInit() {
  this.variables = this.designerService.getVariables();
}

// Other methods
<!-- HTML template file -->
<!-- Replace the designerService.getVariables() method call with component property 'variables' -->
<div *ngFor="let variable of variables" (click)="onLeftClick()" 
(contextmenu)="onRightClick()">works</div> <!-- works -->

There might be some code within the application that is triggering Change Detection which is causing re-rendering even before the associated click handler executes and hence the issue.

Just for testing purpose, before making the above suggested change you can also try to define an array property within the service class and then return that array from getVariables(), and observe if it resolves the issue.

// DesignerService ts file
someArray = [{"name": "dog"},{"name": "cat"},{"name": "bird"},{"name":"bear"}];

public getVariables(): {name: string}[] {
  return this.someArray;
}
Sign up to request clarification or add additional context in comments.

5 Comments

thanks, yes I think that would likely work. I have narrowed it down to, if the for loop targets a hardcoded array, it will work, if it targets a method returning a hard coded array it will not bind click. The problem I have with just populating the array in onInit is that the result set returned by the method is being changed by dropdown and filters in different parts of the application. So I need results that consider the latest state.
also the reason that I got into this situation, I was try to solve for the notorious ExpressionChangedAfterItHasBeenCheckedError because that array count/contents was changing due to the category filter finally completing it load of category values from the database and then selecting a default, which filters the results. I still don't understand how to deal with it. It seems to be a very very common situation but very hard to not hit that error.
@gsn1074 Based on your application logic, you have to accordingly set the value of this.variables. If there are different parts of the application modifying the value, you can always define a Subject/BehaviorSubject and then subscribe to it for getting the latest state. Again the approach you take, depends upon the application architecture.
Yes, thanks. I have used observables before and in many other parts of this application. I guess I'll have rewrite this part. I really think I'm going to quit angular programming. It is way to difficult and slow to develop in. Like running in chest deep water. I'll never get this project done.
For the record, I think it is ridiculous that angular requires us to consider/understand their framework implementation details so closely to accomplish simple tasks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.