When the page reloads, the checkboxes are already checked but do not contain any initial values

The current configuration functions properly when I toggle checkboxes and submit to the server.

However, upon reloading the page or entering edit mode, everything loads correctly and the checkboxes are checked with the corresponding entries displayed like this...

https://i.sstatic.net/9z7PB.png

The issue arises when I attempt to submit without making any changes, resulting in an empty array being submitted permission_ids: []. To trigger the OnChange() event, each checkbox needs to be clicked again; however, as a newcomer to Angular, I'm unsure how to automate this process upon page load.

My understanding of the problem is that while the checkboxes appear checked, the form values are not being updated accordingly.

Below is the code:

Template

<h4>Permissions</h4>
            <div class="form-group row p-t-20">
              <div class="col-sm-4">
                <div class="custom-control custom-checkbox" *ngFor="let perm of permissions; let i=index">                
                  <input
                    type="checkbox"
                    class="custom-control-input"
                    id="permission_{{perm.id}}"
                    [value]="perm.id"
                    (change)="onPermissionCheckboxChange($event)"
                    [checked]="permissionExists(perm.id)">
                  <label
                    class="custom-control-label"
                    for="permission_{{perm.id}}">
                    {{ perm.name }}
                  </label>
                </div>
              </div>
            </div>

Typescript

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormArray, FormControl, Validators  } from "@angular/forms";
import { NotificationService } from "../../notification/notification.service";
import { PermissionsService } from "../../permissions/permissions.service";
import { HttpClient } from "@angular/common/http";
import { environment } from "../../../../environments/environment";
import { Router, ActivatedRoute } from "@angular/router";
import { RolesService } from "../roles.service";

@Component({
  selector: 'app-edit-role',
  templateUrl: './edit-role.component.html',
  styleUrls: ['./edit-role.component.css']
})
export class EditRoleComponent implements OnInit {
  isLoading: boolean = false;
  roleId: number = +this.route.snapshot.params['id']
  roleObj: any = {};
  haveRole: any = true;
  roles: any;
  role: any;

  permissions: any;

  form: FormGroup;

  constructor(
    private http: HttpClient,
    private router: Router,
    private route: ActivatedRoute,
    private fb: FormBuilder,
    private notificationService: NotificationService,
    private rolesService: RolesService,
    private permissionsService: PermissionsService
  ) {
    this.form = this.fb.group({
      role: new FormGroup({
        name: new FormControl('', [Validators.required]),
        permission_ids: this.fb.array([])
      })
    })
  }

  permissionExists(id: number) {
    return this.role.permissions.find(function(el: any){ return el.id === id }) !== undefined;
  }

  ngOnInit(): void {
    this.rolesService.getRoles().subscribe(response => {

    this.roles = response.body;
    this.role = this.roles.find((el: any) =>  el.id === this.roleId)

    //console.log(this.role);
    this.isLoading = false;
    this.form.patchValue({
      'role': {
        'name': this.role.name
      }
    });
    },
    error => {
      this.isLoading = false;
      this.notificationService.showNotification(
        error.error.title,
        error.error.details,
        'error'
      );
    })

    
    this.permissionsService.getPermissions().subscribe(response => {

      this.permissions = response.body;
      this.isLoading = false;
    },
    error => {
      this.isLoading = false;
      // console.log(error.error);
      this.notificationService.showNotification(
        error.error.title,
        error.error.details,
        'error'
      );
    })
  }


  onPermissionCheckboxChange(e: any) {
    const checkArray: FormArray = this.form.get('role.permission_ids') as FormArray;
if (e.target.checked) {
      checkArray.push(new FormControl(e.target.value));
    } else {
      let i: number = 0;
      checkArray.controls.forEach((item: any) => {
        if (item.value == e.target.value) {
          checkArray.removeAt(i);
          return;
        }
        i++;
      });
    }
  }

  onSubmit() {
    this.roleObj.role = this.form.value.role
    console.log(this.roleObj)
  }
}

Apologies for the less than stellar code quality, still adapting to Angular coming from a primarily Ruby on Rails background.

Answer №1

Amir is slightly confused by your code. Additionally, you are mixing mixins FormBuilder and the constructor of FormControl.

First, think in terms of objects before creating the Form. I envision your "role" looking something like this:

{
  name: 'name',
  permissions: [1, 3]
}

Your permissions would be an array like this:

[{id: 1, name: "Can create category"}, {id: 2, name: "Can read category"}...]

In Angular, an input type "checkbox" only has two values: true or false. Therefore, if you use a FormArray, its value will be something like [true, false, true, false, false].

One approach could involve creating a FormGroup like this:

form = new FormGroup({
    name: new FormControl(this.role.name, [Validators.required]),
    permissions: new FormArray(
      this.permissions.map(
        x => new FormControl(this.role.permissions.find(p => p == x.id) != null)
      )
    )
  });

To manage this, create a getter called rolePermission that returns the FormArray:

get rolePermision()
{
    return this.form.get('permissions') as FormArray
}

You can then utilize HTML like this:

<form [formGroup]="form">
   <input formControlName="name">
   <div formArrayName="permissions">
     <div *ngFor="let control of rolePermission.controls; let i=index">
      <input 
            [formControlName]="i" type="checkbox">{{permissions[i].name}}
            </div>
   </div>
</form>

After submitting the form, you can do the following:

submit(form: FormGroup) {
    if (form.valid) {
      const data = {
        name: this.form.value.name,
        permisions: this.permissions
          .filter((x, index) => form.value.permissions[index])
          .map(x => x.id)
      };
      ...use data to send to your service..
    }
  }

Another approach involves having a formGroup with two formControls, one of which stores an array:

form = new FormGroup({
    name: new FormControl(this.role.name, [Validators.required]),
    permissions: new FormControl(this.role.permissions)
  })

This method utilizes [ngModel] and (ngModelChange). Here's how you can implement it:

<form [formGroup]="form">
  <input formControlName="name">
  <div *ngFor="let permission of permissions">
    <input type="checkbox" [ngModel]="form.get('permissions')?.value && form.get('permissions').value.indexOf(permission.id) >= 0"
    [ngModelOptions]="{standalone:true}"
    (ngModelChange)="change(permission.id, $event)"
    >{{permission.name}}
    </div>
  
</form>

The change function would look like this:

change(id: any, checked: boolean) {
    const oldValue = this.form.value.permissions;
    const permissions = checked
      ? this.permissions.filter(
          x => (oldValue && oldValue.indexOf(x.id) >= 0) || x.id == id
        )
      : this.permissions.filter(
          x => oldValue && oldValue.indexOf(x.id) >= 0 && x.id != id
        );

    this.form
      .get('permissions')
      .setValue(permissions.length > 0 ? permissions.map(x => x.id) : null);
  }

You can explore both approaches in detail on this stackblitz link.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Testing Angular Service Calls API in constructor using Jasmine Test

My service is designed as a singleton, and its constructor initiates an API call function. This simplifies the process during service initialization, reducing the complexity and dependencies on various components like AppComponent to import and execute API ...

Segment the progress bar into sections

Struggling with an Angular app, and still new to HTML and CSS, I am attempting to create a unique progress bar. I aim to split each section of the progress bar into two subsections with distinct background colors shown in this image: https://i.sstatic.net/ ...

Upon clicking a button, I aim to retrieve the data from the rows that the user has checked. I am currently unsure of how to iterate through the rows in my mat-table

My goal is to iterate through the column of my mat-table to identify the rows that have been checked, and then store the data of those rows in an array. <td mat-cell *matCellDef="let row"> <mat-checkbox (click)="$event.stopPropagation()" (c ...

There are no route parameters defined

Within my user template file, I have a tab control implemented as shown below: <nav md-tab-nav-bar> <a class="tab-label" md-tab-link [routerLink]="'following'" routerLinkActive #following="routerLinkActive" [acti ...

Redirect all traffic in Node.js/Express to an Angular 2 page

Currently working on a project using Angular 2, Node.js, and Express. Encountering an issue with my routes file when using a wildcard. Whenever I try to access a page other than / (such as /test), it throws the error message: ReferenceError: path is no ...

What could be the reason behind my Heroku app suddenly crashing without any visible errors?

After successfully starting the Nest application, about 50 seconds later it transitions from 'starting' to 'crashed'. The last log entry before the crash is a console log indicating the port number. View Heroku logs after successful bui ...

Issue with Angular2: Trouble transmitting data to another component. The backend service is being invoked twice

My current project requires me to extract query parameters from a URL in the ngOnInit function, create a specific URL for calling an API on the backend like http://localhost:8080/myapp/video0=5&video1=6, and then send the received data to another compo ...

Upgrade to Angular 12: TypeScript is now an essential requirement for the Angular Compiler

Recently, I made sure to update my project to the latest Angular version. After running "ng update", I received a confirmation that everything was already up to date, indicating that all required packages had been successfully updated in the last step of t ...

Is there a method to accurately pinpoint the specific type?

Is there a way to optimize the validateField function to narrow down the type more effectively? type TStringValidator = (v: string) => void; type TNumberValidator = (v: number) => void; type TFields = 'inn' | 'amount'; interface ...

Confirm the contents of a form retrieved through ajax

Let me explain the issue I am facing. I have created an HTML page with a simple form and some jQuery code for validation. This is how the page looks: <html> <head> <title></title> <script src="jquery-1.7.min.js" type="text/jav ...

How to verify that the user is using the most up-to-date version of the application in React Native

Currently, I am focused on the application and have implemented API endpoints that return the latest version along with information on whether the update is mandatory. Within the application flow, a GET request is sent to these API endpoints to check the ...

Using Angular2 animations (specifically transform animations) on an iPhone 6s may result in unexpected behavior

I am interested in utilizing Angular2's Animation to create an animation similar to the following: transition('out => arise', [ style({ 'transform': 'translateY(-50px)', '-webkit-transform&a ...

Verifying the accuracy of a React Component in interpreting and displaying a path parameter

I have the following React/Typescript component that is functioning correctly. However, I am struggling to write a test using testing-library. My goal is to verify that it properly receives the level parameter and displays it on the page: import React from ...

Navigational module and wildcard for routes not located

I have my routing configuration structured as follows: app-routing const routes: Routes = [ { path: 'login', loadChildren: 'app/modules/auth/auth.module#AuthModule' }, { path: '', redirectTo: 'dash ...

Exploring cutting-edge Angular 2 UI controls?

Imagine this scenario: An extensive organization is in need of developing a large web application with advanced UI components, such as hierarchical grid/tree and charts, alongside the standard UI elements. All these controls should ideally be sourced fro ...

Utilize string values as identifiers in type declarations

My Props type declaration currently looks like this: type Props<FormData> = { formData: FormData, sectionNme: keyof FormData, name: string } However, I am trying to modify it to look more like the following: type Props<FormData> = ...

Tips for integrating icons with React's slick slider functionality

There's a piece of code that showcases various icons. switch (name) { case Header.Arrows.name: return <ArrowsComponent key={name} color={color}/>; case Header.Zoom.name: return <ZoomTool key={name} color={color}/>; ...

Storing Buffer data in Postgres bytea using TypeORM is limited to only 10 bytes

I am encountering an issue while trying to store images in a postgres database, as only 10 bytes of data are being saved. Here is the sequence of events: Initially, I receive a base64 encoded string on my server. I then convert it into a Buffer, assign i ...

Uncovering the Technique to Retrieve Posts from Identically Named Textboxes Using PHP

Is there a way to read all values of multiple textboxes with the same name in a dynamically created form using the $_POST method during form processing? If so, how can this be accomplished? ...

Converting a string[] to an EventEmitter string[] in Angular 4 with TypeScript: Step-by-step guide

Programming Language: Typescript – written as .ts files Development Framework: Angular 4 I'm currently working on an application that is supposed to add chips (similar to tags in Angular 1) whenever a user types something into the input box and hi ...