2

Hi I am building a dynamic form in Angular 8. So far everything is working fine, but I have trouble implementing a checkbox control. The checkbox control shows up, and I try to see if it is checked or not.

If it is set to true, in meta.json file, and I click it on the webpage, then the value becomes "true". I would expect false. If the value is false in meta.json, then there is no change on webpage when I click.

meta.json

[ 
  [
    {
      "questionType": "dropdown",
      "key": "brave",
      "label": "Bravery Rating",
      "options": [
        {"key": "solid", "value": "Solid"},
        {"key": "great", "value": "Great", "selected": "selected"},
        {"key": "good", "value": "Good"},
        {"key": "unproven", "value": "Unproven"}
      ],
      "order": 3,
      "visibility": true
    },
    {
      "questionType": "checkbox",
      "key": "mycheck01",
      "label": "Mycheck01",
      "value": true,
      "type": "checkbox",
      "validators": [],
      "order": 2,
      "visibility": true
    },
    {
      "questionType": "textbox",
      "key": "firstName",
      "label": "First name",
      "value": "abcd",
      "type": "text",
      "validators": ["required"],
      "order": 2,
      "visibility": true
    },
    {
      "questionType": "textbox",
      "key": "emailAddress",
      "label": "Email",
      "value": "[email protected]",
      "type": "email",
      "order": 1,
      "validators": ["required", "email"],
      "visibility": "this.form.get('firstName').value === 'abc'"
    }
  ],
  [
    {
      "questionType": "textbox",
      "key": "emailAddress",
      "label": "Email",
      "value": "",
      "type": "email",
      "order": 2,
      "visibility": true
    }
  ]
]

main.componennt.html

<div *ngIf="loaded">
  <h2>POC</h2>
  <form (ngSubmit)="onSubmit()" [formGroup]="form">
    <ng-container *ngFor="let eachGroup of objectKeys(globalForm); let index = index">
      <button (click)="openGroupHadler(index)">click me</button> <br>
      <div 
        [formGroupName]="eachGroup" 
        class="form-group"
        [ngClass]="{'active' : index === activeGroup}">
        <div *ngFor="let question of globalForm[eachGroup]" class="form-row">
          <app-question [question]="question" [form]="form.controls[eachGroup]"></app-question>
        </div>
      </div>
    </ng-container>
    <div class="form-row">
      <button type="submit" [disabled]="!form.valid">Save</button>
    </div>
  </form>

  <pre>
    {{form.value | json}}
  </pre>
  <div *ngIf="payLoad" class="form-row">
    <strong>Saved the following values</strong><br>{{payLoad}}
  </div>
</div>

main.component.ts

import { Component, OnInit } from '@angular/core';
import { HeaderService } from '@myHeaderService';
import { QuestionService } from '../../question/services/question.service';
import { QuestionControlService } from '../../question/services/question-control.service';
import { FormGroup } from '@angular/forms';
import { FControlTextbox } from '../../question/model/question-textbox';
import { FControlDropdown } from '../../question/model/question-dropdown';
import { FControlCheckbox } from '../../question/model/question-checkbox';

@Component({
  selector: 'app-irv',
  templateUrl: './irv.component.html',
  styleUrls: ['./irv.component.scss']
})
export class IrvComponent implements OnInit {
  labels;
  objectKeys = Object.keys;
  form: FormGroup;
  payLoad = '';
  loaded = false;
  globalForm: any = {};
  activeGroup;

  constructor(
    private _headerService: HeaderService,
    private _questionService: QuestionService,
    private _questionControlService: QuestionControlService
    ) {}

  ngOnInit() {
    this.labels = JSON.parse(localStorage.getItem('labels'));

    this._headerService.updateHeader({
      title: 'HC - Irv',
      back: true
    });

    this._questionService.getQuestions().subscribe((response: any[]) => {
      response.forEach((group, index) => { // loop thrugh groups
        const formControls = [];

        group.forEach(formControl => { // loop through form controls in groups
          switch (formControl.questionType) {
            case 'textbox':
              formControls.push(new FControlTextbox(formControl)); break;
            case 'checkbox':
              formControls.push(new FControlCheckbox(formControl)); break;
            case 'dropdown':
              formControls.push(new FControlDropdown(formControl)); break;
            default:
              break;
          }
        });
        console.log(this.globalForm);
        this.globalForm['group' + (index + 1)] = formControls.sort((a, b) => a.order - b.order);
      });

      this.form = this._questionControlService.toFormGroup(this.globalForm);
      this.loaded = true;
    });
  }

  onSubmit() {
    this.payLoad = JSON.stringify(this.form.value);
  }

  openGroupHadler(index) {
    this.activeGroup === index ? this.activeGroup = null : this.activeGroup = index; // show or hide a formgroup
  }

}

question.component.ts

import { Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';

import { QuestionBase } from './model/question-base';

@Component({
  selector: 'app-question',
  templateUrl: './question.component.html',
  styleUrls: ['./question.component.scss']
})
export class QuestionComponent {
  @Input() question: QuestionBase<any>;
  @Input() form: FormGroup;
  get isValid() { return (
    (
      this.form.controls[this.question.key].valid &&
      this.form.controls[this.question.key].dirty
    ) ||
    (
      this.form.controls[this.question.key].untouched
    ) ||
    (
      !this.form.controls[this.question.key].invalid
    )
    );
  }

  isVisible(value) {
    return eval(value);
  }
}

question.component.html

<div [formGroup]="form" 
  [ngClass]="{'isa_error':isValid ? false : true}" 
  *ngIf="isVisible(question.visibility)"
  >
    <label [attr.for]="question.key">{{question.label}}</label>

  <div [ngSwitch]="question.controlType">
    <input
      *ngSwitchCase="'textbox'"
      [formControlName]="question.key"
      [id]="question.key"
      [type]="question['type']">

    <input
      *ngSwitchCase="'checkbox'"
      [formControlName]="question.key"
      [checked]="question['checked']"
      [id]="question.key"
      [type]="question['type']">

    <select [id]="question.key" *ngSwitchCase="'dropdown'" [formControlName]="question.key">
      <option 
        *ngFor="let opt of question['options']" 
        [value]="opt.key"
        >{{opt.value}}</option>
    </select>
  </div> 

  <div class="errorMessage" *ngIf="!isValid">{{question.label}} is required</div>
</div>

question.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class QuestionService {
  constructor(
    private _http: HttpClient
  ) {}
  getQuestions() {
    return this._http.get('./assets/meta.json').pipe(res => res);
  }
}

question.control.service.ts

import { Injectable } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';

import { QuestionBase } from '../model/question-base';

@Injectable({
  providedIn: 'root'
})
export class QuestionControlService {
  constructor() { }

  toFormGroup(questions: QuestionBase<any>[] ) {
    let group: any = {};
    const sections: {} = {};
    Object.keys(questions).forEach((eachgroup: string) => {
      group = {};
      questions[eachgroup].forEach(question => {
        const validators = [];
        if (question.validators) {
          question.validators.forEach(element => {
            switch (element) {
              case 'required': validators.push(Validators.required); break;
              case 'email': validators.push(Validators.email); break;
            }
          });
          group[question.key] = new FormControl(question.value || '', validators);
        } else {
          group[question.key] = new FormControl(question.value || '');
        }
      });
      sections[eachgroup] = new FormGroup(group);
    });

    return new FormGroup(sections);
  }
}

question.textbox.ts

import { QuestionBase } from './question-base';

export class FControlTextbox extends QuestionBase<string> {
  controlType = 'textbox';
  type: string;

  constructor(options) {
    super(options);
    this.type = options.type || '';
  }
}

question.dropdown.ts

import { QuestionBase } from './question-base';

export class FControlDropdown extends QuestionBase<string> {
  controlType = 'dropdown';
  options: {key: string, value: string}[] = [];

  constructor(options) {
    super(options);
    this.options = options.options || [];
  }
}

question.base.ts

export class QuestionBase<T> {
  value: T;
  key: string;
  label: string;
  required: boolean;
  order: number;
  controlType: string;
  visibility: string;
  validators: string;

  constructor(options: {
      value?: T,
      key?: string,
      label?: string,
      required?: boolean,
      order?: number,
      controlType?: string,
      visibility?: string
      validators?: string
    } = {}) {
    this.value = options.value;
    this.key = options.key || '';
    this.label = options.label || '';
    this.required = !!options.required;
    this.order = options.order === undefined ? 1 : options.order;
    this.controlType = options.controlType || '';
    this.visibility = options.visibility;
    this.validators = options.validators;
  }
}

question.checkbox.ts

import { QuestionBase } from './question-base';

export class FControlCheckbox extends QuestionBase<string> {
  controlType = 'checkbox';
  type: string;
  checked: boolean;

  constructor(options) {
    super(options);
    this.type = options.type || '';
    this.checked = options.checked || false;
  }
}

asd

1 Answer 1

2

turns out it was actually quite simple. I just added checked attribude and change attribute

question.component.html

<div [formGroup]="form" 
  [ngClass]="{'isa_error':isValid ? false : true}" 
  *ngIf="isVisible(question.visibility)"
  >
    <label [attr.for]="question.key">{{question.label}}</label>

  <div [ngSwitch]="question.controlType">
    <input
      *ngSwitchCase="'textbox'"
      [formControlName]="question.key"
      [id]="question.key"
      [type]="question['type']">

    <input
      *ngSwitchCase="'checkbox'"
      [formControlName]="question.key"
      [checked]="question['value']"
      [id]="question.key"
      (change)="form.controls[question.key].setValue($event.target.checked)"
      [type]="'checkbox'">

    <select [id]="question.key" *ngSwitchCase="'dropdown'" [formControlName]="question.key">
      <option 
        *ngFor="let opt of question['options']" 
        [value]="opt.key"
        >{{opt.value}}</option>
    </select>
  </div> 

  <div class="errorMessage" *ngIf="!isValid">{{question.label}} is required</div>
</div>
Sign up to request clarification or add additional context in comments.

1 Comment

I have used something similar but I wonder, is there a better solution? I looks more like a hack!

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.