Angular reactive form submit programmatically without button - html

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
}
},
});

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);
}
}

#viewChild and #ViewChildern gives undefined

I'm working on Angular 9 and want to access an input field after clicking on a button. right now it gives me undefined. I have tried #ViewChild and #viewChildern because I'm using ngIf.
Template.html file
<div class="search-input" #searchDiv *ngIf="serachActive">
<input
#searched
autofocus
type="text"
class="serach-term"
placeholder="Search"
[(ngModel)]="searchTerms"
(ngModelChange)="applySearch()"
/>
<button (click)="toggleSearch(!serachActive)">
<span class="material-icons"> search </span>
</button>
<ul class="search-list">
<li *ngFor="let result of results">
<a [routerLink]="['/', 'video', 'details', result._id]">{{
result.title ? result.title : ''
}}</a>
</li>
</ul>
</div>
Template.ts file
import { Component, OnInit,AfterViewInit,ElementRef,ViewChild,ViewChildren } from '#angular/core';
import { UserService } from '../../../user.service';
import { VideoService } from '../../../services/video.service';
import { Subject } from 'rxjs';
import { distinctUntilChanged, debounceTime } from 'rxjs/operators';
import { Router } from '#angular/router';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css'],
})
export class HeaderComponent implements OnInit,AfterViewInit{
serachActive: boolean = false;
#ViewChildren('searched') searchElement: ElementRef;
#ViewChildren("searched") input: ElementRef;
user;
subject = new Subject<string>();
results = [];
searchTerms;
loggedIn: Boolean = false;
constructor(
private userService: UserService,
private videoService: VideoService,
private router: Router
) {
this.user = this.userService.getUser();
this.loggedIn = this.userService.isAuthenticated();
}
ngOnInit() {
console.log('on init', this.input); //undefined
this.subject
.pipe(debounceTime(400), distinctUntilChanged())
.subscribe((value) => {
this.router.navigate(['search'], { queryParams: { term: value } });
});
}
ngAfterViewInit() {
console.log('on after', this.input); //undefined
}
toggleSearch(toggledata) {
this.serachActive = toggledata;
this.results = [];
this.searchTerms = '';
console.log(this.input) //undefined
console.log(this.searchElement.nativeElement) //undefined
}
applySearch() {
const searchText = this.searchTerms;
this.subject.next(searchText);
this.searchElement.nativeElement.focus(); //undefined
}
menuButtonClick(button){
if(button === "history"){
this.router.navigate(['history'])
}
}
}
Use ViewChild since you're only searching for 1 element ID.
If adding { static: true } or { static: false } in your ViewChild options doesn't work as what is stipulated on Angular Static Query Migration Documentation
Use ChangeDetectorRef instead:
#Component({...})
export class AppComponent {
#ViewChild('searchInput') input: ElementRef;
isShow: boolean = false;
constructor(private cdr: ChangeDetectorRef) {}
toggle(): void {
this.isShow = !this.isShow;
this.cdr.detectChanges(); // Detects changes which this.isShow is responsible on showing / hiding
// the element you're referencing to in ViewChild
if (this.isShow) // If element is shown, console the referenced element
console.log(this.input);
}
}
Have created a Stackblitz Demo for your reference

How to display custom validation error message

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

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 4 : How to use pattern in an input list

I have a form html and the button submit becomes enable only when the form is valid. That is, when a particular input contains the recommended pattern. I need to use this pattern in an inputlist. It works with a simple input but with the input list, the list disappears :
<input list="refNumbers" pattern="[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]" formControlName="refNb" type="text" name="article" maxlength="8" size="15" required title="8 characters" />
<datalist id="refNumbers">
<option *ngFor="let ref of listOfArticles">{{ref.refNumber.input}}</option>
</datalist>
Otherwise, how can i disable button submit if the ref number selected is not in the datalist ? Because it seems that the valid condition on the form is not enough :
<button type="submit" [disabled]="!myFormGroup.valid" >Valider</button>
component.ts :
import { Component, OnInit } from '#angular/core';
import 'rxjs/add/operator/switchMap';
import { ManagementArbologistiqueService } from "../management-arbologistique.service";
import { ActivatedRoute, Params } from '#angular/router';
import { FormGroup, FormControl, FormBuilder, FormArray, Validators } from '#angular/forms';
#Component({
selector: 'app-arbologistique',
templateUrl: './arbologistique.component.html',
styleUrls: ['./arbologistique.component.css']
})
export class ArbologistiqueComponent implements OnInit {
private reponseTest: String;
private listOfArticles :Array<Object>
private pathDownload: any;
private myFormGroup: FormGroup;
fileToUpload: File = null;
private buttonSubmitEnabled: boolean = false;
constructor(public fb: FormBuilder, private managementArbo: ManagementArbologistiqueService, private route: ActivatedRoute) { }
ngOnInit() {
this.myFormGroup = this.fb.group({
itemRows: this.fb.array([this.initItemRows()])
})
this.myFormGroup.valueChanges.subscribe(x => this.buttonSubmitEnabled = false);
this.getListBdd();
}
initItemRows() {
return this.fb.group({
... //other fields
refNb: ['',Validators.required],
... //other fields
})
}
addRow(index: number) {
console.log("functionAddRow called");
const control = <FormArray>this.myFormGroup.controls['itemRows'];
control.insert(index, this.initItemRows());
}
deleteRow(index: number) {
console.log("functionDeleteRow called");
const control = <FormArray>this.myFormGroup.controls['itemRows'];
control.removeAt(index);
}
sendForm() {
this.buttonSubmitEnabled=true;
console.log("functionExportCalled");
this.route.params.subscribe((params: Params) => {
let subroute = "exportation";
this.managementArbo.postProducts(subroute, JSON.stringify(this.myFormGroup.value))
.subscribe(
res => { this.reponseTest = res; console.log('reponse:' + res); }
,
err => console.log(err),
() => console.log('getProducts done'));
});
}
getListBdd() {
this.route.params.subscribe((params: Params) => {
let subroute = "getRefNumber";
this.managementArbo.getProducts(subroute)
.subscribe(
res => { this.listOfArticles = res; console.log('reponse:' + res); }
,
err => console.log(err),
() => console.log('getProducts done'));
});
}
get refNb() {
return this.myFormGroup.get('itemRows.refNb');
}
}
If you want to add Validation in reactive form you can use build in Validators
initItemRows() {
const regEx="[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]";
return this.fb.group({
... //other fields
refNb: ['',Validators.required,Validators.pattern(regEx)],
... //other fields
})
}
<button type="submit" [disabled]="!myFormGroup.valid" >Valider</button>