How to display custom validation error message - json

For my form it has just one input and that's a text area, where the user enters in JSON Code and if the Text is not Valid JSON it should display an error message but for some reason it wont work.
Here's my custom validator:
import { AbstractControl, ValidationErrors, ValidatorFn } from '#angular/forms';
export function jsonValidator(control: AbstractControl): ValidationErrors | null {
try {
JSON.parse(control.value);
} catch (e) {
console.log("Not Valid JSON");
return { jsonInvalid: true };
}
return null;
};
Here is the .ts File
import { Component, OnInit } from '#angular/core';
import { FormBuilder, Validators, FormGroup } from '#angular/forms';
import { jsonValidator } from 'src/app/Validators/jsonValid';
#Component({
selector: 'app-email-body-editor',
templateUrl: './email-body-editor.component.html',
styleUrls: ['./email-body-editor.component.scss']
})
export class EmailBodyEditorComponent implements OnInit {
errorMsg : string = "Not VALID JSON";
form = this.fb.group({
jsonField: [null, [Validators.required , jsonValidator]]
});
constructor(private fb: FormBuilder) {
}
submit(): void {
console.log(this.form);
}
ngOnInit(): void {
}
}
And Finally the HTML File
<form [formGroup]="form" (submit)="submit()">
<mat-form-field appearance="fill">
<mat-label>Textarea</mat-label>
<textarea matInput
formControlName="jsonField"
cols="1000"
placeholder="my custom json here"
cdkTextareaAutosize
cdkAutosizeMinRows="10"
cdkAutosizeMaxRows="50">
</textarea>
</mat-form-field>
<br>
<div *ngIf="form.controls.jsonField.hasError('jsonValidator')">
{{errorMsg}}
</div>
</form>

Your validation error name is jsonInvalid, not jsonValidator.
The name is defined by the return statement in your validator function.
return { jsonInvalid: true };
You should use this in your HTML:
<div *ngIf="form.controls.jsonField.hasError('jsonInvalid')">
{{errorMsg}}
</div>
DEMO: https://stackblitz.com/edit/angular-h89jju

Related

Angular: ERROR TypeError: Cannot read property 'choice_set' of null, while data displayed correctly

i hope you're doing well.
I am trying to implement a FormsBuilder in Angular by accessing the data from an API. The data is pushed down to its child-component via #Input().
However the data gets pushed down, are provided and shown successfully, but still I get this Error, when the first attempt from ngOnChangess tries to receive the data.
ERROR TypeError: Cannot read property 'choice_set' of null
at StyleTypeQuestionComponent.setFormValues (style-type-question.component.ts:34)
at StyleTypeQuestionComponent.ngOnChanges (style-type-question.component.ts:26)
at StyleTypeQuestionComponent.rememberChangeHistoryAndInvokeOnChangesHook (core.js:1471)
at callHook (core.js:2490)
at callHooks (core.js:2457)
at executeInitAndCheckHooks (core.js:2408)
at refreshView (core.js:9207)
at refreshEmbeddedViews (core.js:10312)
at refreshView (core.js:9216)
at refreshComponent (core.js:10358)
The data is provided through an data-service and are subscribed through an async pipe from its parent-component and as mentioned above pushed down via property binding.
I tried to use the ? operator in my template and tried to set an Timeout on the childcomponent. Also i tried to initialize the data via default values. Still thats making no sense for me right know, because the data is already available through his parent component and getting checked via an *ngIf directive.
I hope i could provided as much as information as needed.
I guess there is an initializing problem in the first seconds of ngChanges.
Parent-Component
import { Component, Input, OnChanges, OnInit } from '#angular/core';
import { Question } from '../shared/models/question';
import { QuestionStoreService } from '../shared/question-store.service';
import { Observable } from 'rxjs';
#Component({
selector: 'pc-style-type-detection',
templateUrl: './style-type-detection.component.html',
styleUrls: ['./style-type-detection.component.css'],
})
export class StyleTypeDetectionComponent implements OnInit, OnChanges {
question$: Observable<Question>;
#Input() question_Input: Question;
question_index: number = 1;
constructor(private qs: QuestionStoreService) {}
ngOnInit(): void {
this.question$ = this.qs.getSingle(1);
}
ngOnChanges(): void {}
updateBook(question: Question): void {
console.log(question);
}
}
Parent-Template
<pc-style-type-question
*ngIf="question$"
(submitQuestion)="updateBook($event)"
[question]="question$ | async"
></pc-style-type-question>
Child-Component
import {
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
} from '#angular/core';
import { FormArray, FormBuilder, FormGroup } from '#angular/forms';
import { Choice, Question } from '../shared/models/question';
#Component({
selector: 'pc-style-type-question',
templateUrl: './style-type-question.component.html',
styleUrls: ['./style-type-question.component.css']
})
export class StyleTypeQuestionComponent implements OnInit, OnChanges {
questionForm: FormGroup;
#Input() question: Question;
#Output() submitQuestion = new EventEmitter<Question>();
constructor(private fb: FormBuilder) {}
ngOnChanges(): void {
this.initForm();
this.setFormValues(this.question);
}
ngOnInit(): void {
this.initForm();
}
private setFormValues = (question: Question) => {
this.questionForm.patchValue(question.choice_set);
this.questionForm.setControl(
'choice_set',
this.buildChoiceSetArray(question.choice_set)
);
};
initForm = () => {
if (this.questionForm) {
return;
}
this.questionForm = this.fb.group({
choice_set: this.buildChoiceSetArray([
{
choice_text: '',
choice_value: false,
},
]),
});
};
get choiceSet(): FormArray {
return this.questionForm.get('choice_set') as FormArray;
}
private buildChoiceSetArray = (values: Choice[]): FormArray => {
if (values) {
return this.fb.array(
values.map((choice) => {
return this.fb.control(choice.choice_value);
})
);
}
return this.fb.array(
this.question.choice_set.map((choices) =>
this.fb.control(choices.choice_value)
)
);
};
submitForm() {}
}
Child-Template
<form class="ui form" [formGroup]="questionForm" (ngSubmit)="submitForm()">
<div
formArrayName="choice_set"
*ngFor="let choiceset of choiceSet?.controls; index as i"
>
<div>
<input type="checkbox" [formControl]="choiceset" />
<label>
{{ question.choice_set[i].choice_text }}
</label>
</div>
</div>
</form>
Thank you in advance and wish you a nice weekend.
You are not using ngOnChanges the right way, its going to be triggered everytime your input change no matter what the value is which means you need to check if that value is what you expect it to be with SimpleChanges.
ngOnChanges(changes: SimpleChanges) {
if(changes.question.currentValue) {
this.initForm();
this.setFormValues(this.question);
}
}

Angular reactive form submit programmatically without button

I would like to submit a form automatically when all the fields in the form are valid,.
How can I trigger a form submission with codes but no buttons?
Here is an example form:
<form [formGroup]="dummyForm">
<label>
<input type="text" formControlName="dummyText" ng-change="autoSubmit()"/>
</label>
</form>
component.ts:
import { Component } from '#angular/core';
import { FormGroup } from '#angular/forms';
import { RxFormBuilder, RxwebValidators } from '#rxweb/reactive-form-validators';
#Component({
selector: 'dummy-page',
templateUrl: './dummy.component.html',
styleUrls: ['./dummy.component.sass']
})
export class DummyComponent {
dummyForm: FormGroup;
constructor(
private readonly _formBuilder: RxFormBuilder
) {
// Do something here
}
ngOnInit(): void {
this.dummyForm = this._formBuilder.group({
dummyText: ['', [
RxwebValidators.pattern({
pattern: {
validPattern: /^1234$/
}
})
]]
});
}
autoSubmit(): void {
if (!this.dummyForm.valid) {
return;
} else {
// Trigger form submission here, how?
}
}
}
You can subscribe to statusChanges observable of your FormGroup like this:
this.dummyForm.statusChanges.subscribe(status => {
if (status === 'VALID') {
// form submission here
}
});
You can subscribe on form valueChanges
this.dummyForm.valueChanges.subscribe({
next: data => {
if (this.dummyForm.valid) {
// send data
}
},
});

Angular 6 input range validation

I have to validate one of the input element in my ngForm
How to validate range while submit that form
For example user should enter the price between $1000 to $2000
Please give the suggestions to proceed
Thanks
Try this
<form [formGroup]="myForm">
<label for="priceRange">Price Range: </label>
<input type="number" id="priceRange" formControlName="priceRange">
<div *ngIf="f.priceRange.errors">
Invalid Price Range
</div>
in component.ts
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '#angular/forms';
myForm = new FormGroup({});
get f() {
return this.myForm.controls;
}
constructor(private formBuilder: FormBuilder){
this.myForm = formBuilder.group({
priceRange: ['', [Validators.min(1000), Validators.max(2000)]]
});
}
Try this working Example
link
<h3>Reactive Forms Validator Example</h3>
<br>
<label>We have created two validators using slightly different approaches that validate if the input is equal to "A" or not.</label>
<br><br>
<label>Doesn't work because the method is not executed which will return the custom validator function.</label><br>
<input type="text" [formControl]="wrongOnlyA">
<br><br>
<label>Works because the method is executed.</label><br>
<input type="text" [formControl]="correctOnlyA">
<br><br>
<label>Works because the validator function is directly exported.</label><br>
<input type="text" [formControl]="onlyB">
.ts
import { Component, OnInit } from '#angular/core';
import { FormGroup } from "#angular/forms"
import { RxFormBuilder } from '#rxweb/reactive-form-validators';
import { FormBuilderConfiguration } from '#rxweb/reactive-form-validators';
import { EmployeeInfo } from '../employee-info.model';
#Component({
selector: 'app-employee-info-add',
templateUrl: './employee-info-add.component.html'
})
export class EmployeeInfoAddComponent implements OnInit {
employeeInfoFormGroup: FormGroup
constructor(
private formBuilder: RxFormBuilder
) { }
ngOnInit() {
let employeeInfo = new EmployeeInfo();
let formBuilderConfiguration = new FormBuilderConfiguration();
formBuilderConfiguration.validations = {
age : {
range : {minimumNumber:18,maximumNumber:60,}
},
experience : {
range : {minimumNumber:2,maximumNumber:20,conditionalExpression:'x => x.age >=25',}
},
salary : {
range : {minimumNumber:1000,maximumNumber:200000,message:'Your Salary should be between 10000 to 200000.',}
},
};
this.employeeInfoFormGroup = this.formBuilder.formGroup(employeeInfo,formBuilderConfiguration);
}
}

How to create a custom form validator to accept only valid JSON in Angular

In my Angular app, I have a reactive form which for simplicity I will assume to have only one control called configJson which is represented by a <textarea> in the DOM.
I need to validate this form control to only accept valid JSON text from the user input, and display an error message otherwise.
Here's my component's class and template:
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>
I originally tried to edit the answer by the OP, but it was rejected by peer reviewers due to:
This edit was intended to address the author of the post and makes no
sense as an edit. It should have been written as a comment or an
answer.
So, here is my modified version:
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() {
...
}
}
One solution is creating a custom form validator and attach it to the form control. The job of the validator is to only accept valid JSON.
This is how my validator looks like:
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;
};
}
It can be easily unit-tested with the following:
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);
}
});
}
});
In the component's ngOnInit, the new validator should be added:
this.form.get('configJson').setValidators([
Validators.required, // this makes the field mandatory
jsonValidator(), // this forces the user to insert valid json
]);
So the component's class now looks like this:
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...
}
}

Angular service returns value but not displayed in page

I am learning Angular from scratch. I have created a component named 'mylink' and a service for the same.
I tried to return a string value from the service using 'observable, of' and 'subscribe'. The service returns the string, but it is not displayed in webpage. While debugging, in the component, it shows that the data received is
"Unexpected end of input". No error in console. How can I solve this? Thanks in advance.
mylink.component.ts
import { Component, OnInit } from '#angular/core';
import { MylinkService } from '../mylink.service';
import { dashCaseToCamelCase } from '#angular/compiler/src/util';
#Component({
selector: 'my-link',
templateUrl: './mylink.component.html',
styleUrls: ['./mylink.component.css']
})
export class MylinkComponent implements OnInit {
myName: String;
result: any;
constructor(private mylinkService: MylinkService) { }
ngOnInit() {
this.getMylinkData();
}
getMylinkData(): void {
this.mylinkService.getMylinkData()
.subscribe(dataV => this.myName = dataV);
}
}
mylink.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { catchError, map, tap } from 'rxjs/operators';
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
#Injectable()
export class MylinkService {
private mylinkUrl = 'api/mylink'; // URL to web api
constructor(private http: HttpClient) { }
getMylinkData (): Observable<String> {
var val = 'From service';
return of(val);
}
}
<h2>My Link page</h2>
<div>
<label>My name is:
<input #myName />
</label>
</div>
Try like this :
<div>
<label>My name is:
<input [(ngModel)]="myName" name="myName" />
</label>
</div>
I believe what you’re trying to do is something like
<label>My name is:
{{myName}}
</label>
On the component you are assigning the returned string from the service to the public property myName. If you want to display it in the page you just need to interpolate this value as shown in my example