如何创建自定义表单验证器以仅接受Angular中的有效JSON

时间:2019-04-18 14:31:01

标签: json angular typescript angular-reactive-forms angular-validation

在我的Angular应用中,我有一个反应形式,为简单起见,我假设只有一个控件称为configJson,该控件在DOM中由<textarea>表示。

我需要验证此表单控件以仅接受来自用户输入的有效JSON文本,否则显示错误消息。

这是我组件的类和模板:

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

@Component({
  selector: 'app-configuration',
  templateUrl: './configuration.component.html',
  styleUrls: ['./configuration.component.scss']
})
export class ConfigurationComponent implements OnInit {

  form: FormGroup;

  constructor() {}

  ngOnInit() {
    this.form = new FormGroup({
      'configJson': new FormControl(),
    });

    // TODO: someone add JSON validation
  }

  loadJsonConfiguration() {
    const config = JSON.parse(this.form.get('configJson').value);

    // some logic here using the parsed "config" object...
  }
}
<form [formGroup]="form">
  <div class="form-group">
    <label for="json-config-textarea">Parse from JSON:</label>
    <textarea
      class="form-control"
      id="json-config-textarea"
      rows="10"
      [formControlName]="'configJson'"
    ></textarea>
  </div>
  <div [hidden]="form.get('configJson').pristine || form.get('configJson').valid">
    Please insert a valid JSON.
  </div>
  <div class="form-group text-right">
    <button
      class="btn btn-primary"
      (click)="loadJsonConfiguration()"
      [disabled]="form.get('configJson').pristine || form.get('configJson').invalid"
    >Load JSON Configuration</button>
  </div>
</form>

2 个答案:

答案 0 :(得分:2)

一种解决方案是创建一个custom form validator并将其附加到表单控件。验证器的工作是仅接受有效的JSON

这是我的验证器的外观:

import {AbstractControl, ValidationErrors, ValidatorFn} from '@angular/forms';

export function jsonValidator(): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const error: ValidationErrors = { jsonInvalid: true };

    try {
      JSON.parse(control.value);
    } catch (e) {
      control.setErrors(error);
      return error;
    }

    control.setErrors(null);
    return null;
  };
}

可以使用以下内容轻松进行单元测试:

import { FormControl, ValidationErrors, ValidatorFn } from '@angular/forms';
import Spy = jasmine.Spy;

import { jsonValidator } from './json.validator';

describe('JSON Validator', () => {
  let control: FormControl;
  let spySetErrors: Spy;
  let validator: ValidatorFn;

  const errorName = 'jsonInvalid';

  beforeEach(() => {
    control = new FormControl(null);
    validator = jsonValidator();
    spySetErrors = spyOn(control, 'setErrors').and.callThrough();
  });


  for (const { testId, valid, value } of [

    { testId: 1, valid: true, value: '{}' },
    { testId: 2, valid: true, value: '{"myKey": "myValue"}' },
    { testId: 3, valid: true, value: '{"myKey1": "myValue1", "myKey2": "myValue2"}' },
    // more valid cases can be added...

    { testId: 4, valid: false, value: 'this is not a valid json' },
    { testId: 5, valid: false, value: '{"theJsonFormat": "doesntLikePendingCommas",}' },
    { testId: 6, valid: false, value: '{"theJsonFormat": doesntLikeMissingQuotes }' },
    // more invalid cases ca be added...

  ]) {
    it(`should only trigger the error when the control's value is not a valid JSON [${testId}]`, () => {
      const error: ValidationErrors = { [errorName]: true };
      control.setValue(value);

      if (valid) {
        expect(validator(control)).toBeNull();
        expect(control.getError(errorName)).toBeFalsy();
      } else {
        expect(validator(control)).toEqual(error);
        expect(control.getError(errorName)).toBe(true);
      }
    });
  }
});

在组件的ngOnInit中,应添加新的验证器:

    this.form.get('configJson').setValidators([
      Validators.required, // this makes the field mandatory
      jsonValidator(), // this forces the user to insert valid json
    ]);

所以组件的类现在看起来像这样:

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

import { jsonValidator } from './json.validator';

@Component({
  selector: 'app-configuration',
  templateUrl: './configuration.component.html',
  styleUrls: ['./configuration.component.scss']
})
export class ConfigurationComponent implements OnInit {

  form: FormGroup;

  constructor() {}

  ngOnInit() {
    this.form = new FormGroup({
      'configJson': new FormControl(),
    });

    this.form.get('configJson').setValidators([
      Validators.required,
      jsonValidator(),
    ]);
  }

  loadJsonConfiguration() {
    const config = JSON.parse(this.form.get('configJson').value);

    // some logic here using the parsed "config" object...
  }
}

答案 1 :(得分:0)

我最初尝试由OP编辑答案,但由于以下原因,它被同行评审拒绝:

  

此编辑旨在解决帖子的作者,但不对   感觉就像是编辑。它应该被写为评论或   答案。

所以,这是我的修改版本:

import {AbstractControl, ValidationErrors, ValidatorFn} from '@angular/forms';

export function jsonValidator(control: AbstractControl): ValidationErrors | null {
  try {
    JSON.parse(control.value);
  } catch (e) {
    return { jsonInvalid: true };
  }

  return null;
};
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';

import { jsonValidator } from './json.validator';

@Component({
  selector: 'app-configuration',
  templateUrl: './configuration.component.html',
  styleUrls: ['./configuration.component.scss']
})
export class ConfigurationComponent implements OnInit {

  form: FormGroup;

  ngOnInit() {
    this.form = new FormGroup({
      configJson: new FormControl(Validators.compose(Validators.required, jsonValidator))
    });
  }

  loadJsonConfiguration() {
    ...
  }
}