Search code examples
angularforms

Angular Form with Checkboxes that Display Sections


Apologies on what might be an easy question but as a Product Manager trying to write Angular I'm hitting my head on the wall.

What I'm trying to create is an Angular form that has conditional sections. There's an initial checkbox: Should entry information be collected?

Based on this being checked it should open up the next section: Entry Categories

Within Entry Categories there are four options: Apple, Banana, Orange, Kiwi

If ANY of the four options in Entry Categories are selected it should open up the next section: Entry Details AND the entry details tied to that category should show the options.

All Entry Details are the same they have a checkbox for: Organic, GMO, Lego and an input with placeholder: Fruit Name

So if Apple is selected in Entry Categories it should open up Entry Details and display the following checkboxes: Organic, GMO, Lego; and a Fruit Name input.

I've been able to put a conditional from the first checkbox to the Entry Categories from here is where it gets dicey. My original attempt was a compilation of using formControlNames and every checkbox correlating with ngModel. It was also hyper complex. So then I made some massive changes to pull from an array in the ts file.

I'm running into the following issues:

  1. With all the options in the ts file how can I grab the last option and make it into an input?
  2. I have Contact as default being checked, but it's not actually being marked as checked, is there a way?
  3. Is there a way to keep the Entry Details at the top section and push the results into a separate section?

My original entry html and ts files: entry.component.html

 <div fxlayout="row" fxLayoutalign="start center" class="padded-bottom">
  <mat-checkbox id="requireEntryInformation" formControlName="requireEntryInformation" [(ngModel)]="showEntryProduct">Should entry information be collected?
  </mat-checkbox>
 </div>
 <div fxLayout="column" fxLayoutalign="start center" id="entryProductCategorySection" *ngIf="showEntryProduct">
  <h2>Entry Categories</h2>
   <div fxLayout="row" fxLayoutAlign="stretch">
     <div fxFlex="50" fxFlexOffset="10" fxLayout="column" fxLayoutAlign="baseline">
      <mat-checkbox id="apple_category" [(ngModel)]="showApple">Apple</mat-checkbox>
      <mat-checkbox id="orange_category">Orange</mat-checkbox>
     </div>
     <div fxFlex="50" fxFlexOffset="10" fxLayout="column" fxLayoutAlign="baseline">
      <mat-checkbox id="kiwi_category">Kiwi</mat-checkbox>
      <mat-checkbox id="banana_category">Banana</mat-checkbox>
     </div>
    </div>
  </div>
  <div fxLayout="column" fxLayoutAlign="start center" id="entryDetailSection" *ngIf="showEntryDetailSection">
   <h2>Entry Details</h2>
    <div fxLayout="row" *ngIf="showApple">
     <h3>Apple</h3>
     <mat-checkbox>Organic</mat-checkbox>
     <mat-checkbox>GMO</mat-checkbox>
     <mat-checkbox>Lego</mat-checkbox>
    </div>
  </div>

entry.component.ts

showEntryProduct: boolean;
showApple: boolean;
defineFormGroup = () => {
 this.initializeDefaultFormValues();
 this.formGroup = this.formBuilder.group({
  requireEntryInformation: [this.defaultFormValues['requireEntryInformation'], Validators.required]
 });
}

That failed a lot so I built out: StackBlitz Example

I don't know how to handle an input field in the entry array.

  entries = [
    {
      name: 'Apple',
      options: ['Organic', 'GMO', 'Lego'],
      checked: false,
      field_name: 'String'
    },
    {
      name: 'Banana',
      options: ['Organic', 'GMO', 'Lego'],
      checked: false,
    },
    {
      name: 'Kiwi',
      options: ['Organic', 'GMO', 'Lego'],
      checked: false,
    },
    {
      name: 'Contact',
      options: ['Email Required?', 'Address Required?'],
      checked: true,
    },
  ];

In my results as soon as I click on the Apple it immediately loads the Entry Details when I want that section to be completely after Kiwi. Actual Results


Solution

  • You should start with the "data" you want to "store", not with the .html

    Some like

    <div fxlayout="row" fxLayoutalign="start center">
      <mat-checkbox [(ngModel)]="showEntryProduct"
        >Should entry information be collected?
      </mat-checkbox>
    </div>
    <div *ngIf="showEntryProduct">
      <div>
        <h2>Entry Category</h2>
        <div *ngFor="let entry of entries">
          <div>
            <mat-checkbox
              [checked]="entry.checked"
              (change)="unselectRest(entry)"
              >{{ entry.name }}</mat-checkbox
            >
          </div>
        </div>
        <div *ngFor="let entry of entries">
          <div *ngIf="entry.checked">
            <div>
              <h2>Preferred {{ entry.name }}</h2>
              <mat-checkbox *ngFor="let option of entry.options">{{
                option
              }}</mat-checkbox>
            </div>
            <input mat-input [(ngModel)]="fruitName"/>
          </div>
        </div>
      </div>
    </div>
    

    using in .ts

      //declare also a variable fruitName
      fruitName:string="";
    
      unselectRest(entry:any){
          this.entries.forEach(x=>{
            x.checked=x==entry
          })
      }
    

    Show the data (I make "exclusive" the checkbox), but you can not know what are the option selected

    if the checked are "exclusive" we can change a bit the "entries" array adding a property: selected

    entries = [
        {
          ...
          selected:-1,
        },
        {
          ...
          selected:-1,
        },
        ....
    ]
    

    then we can change also the mat-check related to options

          <mat-checkbox *ngFor="let option of entry.options;let i=index" 
             [checked]="entry.selected==i"
             (change)="entry.selected=$event.checked?i:-1">
             {{option}}
          </mat-checkbox>
    

    Now we have all we need in "entriesArray. e.g. in submit you can use some like

    submit()
    {
        //find the entry checked
        const entrie=this.entries.find(x=>x.checked)
    
        //create an object "on fly" with the values
        const result=entrie?{name:entrie.name,
                             option:entrie.selected>=0?entrie.options[entrie.selected]:
                                                       '',
                             fruitName:this.fruitName}:null
        alert(JSON.stringify(result))
          
    }
    

    a stackblitz

    NOTE: There'r anothers ways to do, I only show a way using the event (change) of the mat-checkbox and [checked] property. If you're not using material, you can use some similar using ngModel. It's possible alse do some similar using reactive forms (but the philosophy change a bit)

    NOTE2: remember: always think first in the data we can get, after think about the .html