Search code examples
htmlangularcheckboxevent-handling

HTML checkbox input with (click) event listener stops checkbox tick from showing when clicked


I have recenty taken on an Angular17 project and have been going through code left to me by previous developers. One issue that has come up has to do with checkbox inputs in a table.

Whenever any checkbox is clicked, it is expected to call a function. It does so successfully, however the checkboxes don't get 'ticked'.

Here is the HTML I am working with which generates the checkboxes (some variable names have been changed):

<tbody *ngFor="let header of headerArray; let headerIndex = index;">
    <tr>
        <td *ngFor="let row of tsidRows; let rowIndex = index;">
            <!-- If all variables have a value -->
            <span *ngIf="header.headVarA && header.headVarB && row.rowVarA">
                <!-- Normal Checkbox -->
                <div *ngIf="!(!this.repositoryArray.includes(header.headVarA) && row.rowVarB)">
                    <input #prodCheckbox type="checkbox" class="form-check-input" 
(click)="createProductAllocation($event, header.headVarA.substring(0,6).trim(), header.headVarB.substring(0, 8).trim(), row.rowVarA, row.rowVarB, rowIndex)">
                </div>

                <!-- Error Checkbox -->
                <div *ngIf="!this.repositoryArray.includes(header.headVarA) && row.rowVarB">
                    <input #prodCheckbox type="checkbox" class="form-check-input" disabled>
                </div>
            </span>
            <!-- If any variables are missing - Error Checkbox -->
            <span *ngIf="!header.headVarA || !header.headVarB || !row.rowVarA">
                <div>
                    <input #prodCheckbox type="checkbox" class="form-check-input" disabled>
                </div>
            </span>
        </td>
    </tr>
</tbody>

The *ngIf statements are probably irrelevant, as the checkboxes are still being generated normally. The class form-check-input is a Bootstrap class.

The function createProductAllocation(...) is also not relevant, as calling any function, including:

testFunction() {
    console.log("Hello World");
}

stops the checkbox from ticking. The checkbox.checked attribute returns true after clicking, but since the checkbox is not ticked, I cannot 'untick' it to return it to false. The checked attribute also does not get added directly into the element when inspecting the HTML in developer tools.

I also have this function in the .ts file that unchecks all the checkboxes when an update is made earlier in the component, but after adding in a console.log, I determined that this is never called when a checkbox is clicked.

@ViewChildren("prodCheckbox") pcheckboxes: QueryList<ElementRef>;
uncheckAll() {
  this.pcheckboxes.forEach((element) => {
    element.nativeElement.checked = false;
  });
}

I won't lie, I asked ChatGPT to see if I could get a quick solution; none of the suggestions there worked. Below is what I've tried so far:

  1. Having the (click) event call a different function, which then calls my actual function.
  2. Changing (click) to (change)
  3. Changing ChangeDetectionStrategy from OnPush to Default
  4. Manually forcing event.target.checked to true
  5. Using changeDetectorRef.detectChanges()
  6. Delegating the (click) event to a parent element
  7. Using Angular Renderer2 to force the checked property to 'checked'
  8. Making an entire Angular checkbox component to handle the events

After trying everything listed above, the function still called successfully, but the checkboxes still didn't tick. Removing the (click) event listener allows the checkbox to tick/untick like normal, but obviously doesn't call the function anymore.

All I'm wanting, is to have the checkbox element call the function when it's state changes, while having it tick/untick, which I assume is normal behaviour?

I've been trying to fix this for about 3 days now, and I thought this would have been a simple fix. Any help is really appreciated.


Solution

  • I just want to answer this now as I have a fix thanks to a suggestion from @Eliseo

    They suggested using trackBy in my *ngFor loops, to which I implemented it like this:

    <tbody *ngFor="let header of headerArray; let headerIndex = index; trackBy: trackByHeaderIndex">
        <tr>
            <td *ngFor="let row of tsidRows; let rowIndex = index; trackBy: trackByRowIndex">
                <!-- If all variables have a value -->
                <span *ngIf="header.headVarA && header.headVarB && row.rowVarA">
                    <!-- Normal Checkbox -->
                    <div *ngIf="!(!this.repositoryArray.includes(header.headVarA) && row.rowVarB)">
                        <input #prodCheckbox type="checkbox" class="form-check-input" 
    (click)="createProductAllocation($event, header.headVarA.substring(0,6).trim(), header.headVarB.substring(0, 8).trim(), row.rowVarA, row.rowVarB, rowIndex)">
                    </div>
    
                    <!-- Error Checkbox -->
                    <div *ngIf="!this.repositoryArray.includes(header.headVarA) && row.rowVarB">
                        <input #prodCheckbox type="checkbox" class="form-check-input" disabled>
                    </div>
                </span>
                <!-- If any variables are missing - Error Checkbox -->
                <span *ngIf="!header.headVarA || !header.headVarB || !row.rowVarA">
                    <div>
                        <input #prodCheckbox type="checkbox" class="form-check-input" disabled>
                    </div>
                </span>
            </td>
        </tr>
    </tbody>
    
    trackByHeaderIndex(index: number, item: any) {
      return index;
    }
    
    trackByRowIndex(index: number, item: any) {
      return index;
    }
    

    This worked and now the function gets called, and the checkboxes are ticked/unticked as normal.