Angular Validation: Validate/Highlight child input on button click - javascript

Using Angular 6 here:
I have a parent component and within that I have a child component. The child component has a text fields. The parent has a submit button.
On the submit click button, I want to validate all the inputs of the child as they are required and throw error accordingly.
I am using reactive forms and was successfully able to pass form info from parent to child. But I am not sure how to highlight my text input when the submit button is clicked.
I have used $touched property on my child, which works and shows the required error. But I want the error to also show in case user just clicked the button.
Here is some relevant code.
--Parent--
<form class="form-horizontal" [formGroup]="myForm" (ngSubmit)="onSubmit()">
<child [myForm]="myForm"></child>
<button type="submit">Submit</button>
</form>
<br>
Form Valid: <pre>{{myForm.valid}}</pre>
export class AppComponent {
myForm: FormGroup
constructor(private fb: FormBuilder) {
this.myForm = this.fb.group({
uname: ['', Validators.required]
});
}
onSubmit() {
console.log('value: ', this.myForm.value);
console.log('the whole form and its controls: ', this.myForm)
}
}
--Child--
<div class="form-group" [formGroup]="myForm">
<label for="myForm" class="col-sm-3 control-label">Name:</label>
<div class="col-sm-8" [ngClass]="{ 'has-error': myForm.controls?.uname.errors }">
<input type="text" formControlName="uname" placeholder="Enter Name...">
<em *ngIf="myForm.hasError('required', 'uname') && myForm.controls?.uname.touched">*Required</em>
</div>
</div>
export class ChildComponent {
#Input() myForm: FormGroup;
ngOnInit() {
}
}
I have also created a demo to show this at:
https://stackblitz.com/edit/angular-dbevnj
FYI, this is just a sample I created to show my issue. I would be having 2-3 child components and few form controls on each.
Any inputs how to get this resolved?

We resolved this by calling markAsTouched on all form controls when submitting the form.
In your case you can add this.myForm.get('uname').markAsTouched(); to your onSubmit() method.

Related

Angular - if form block in focus, display inputs

I've got a form in angular that looks something like this (shortened version with its context removed):
<form [formGroup]="myForm">
Name: <input type="text" formControlName="myName">
<div>
Gender: <input type="text" formControlName="myGender">
</div>
</form>
what I want to accomplish is, by clicking on the gender input field, 2 radio buttons will be displayed and when clicking somewhere outside the gender formblock, the buttons disappear. for that I added a boolean variable to my components ts:
export class MyFormComponent implements OnInit {
genderFocused: boolean = false;
constructor(
) { }
ngOnInit() {
}
}
and tried something like this:
<form [formGroup]="myForm">
Name: <input type="text" formControlName="myName">
<div tabindex="-1" (focusout)="genderFocused = false">
Gender: <input type="text" formControlName="myGender" (focus)="genderFocused = true">
<label>Yes<input type="radio"></label>
<label>No<input type="radio"></label>
</div>
</form>
with that however, as soon as I click on one of the radio buttons, they immediately disappear. I looked around and have not found anything that helped me solve this issue.
I replaced the radio buttons with a text input field, and it behaved the exact same way. as soon as the input field within the div is focused, it disappears, which is not intended
You can use #HostListener to check if element is clicked outside and set a flag for the *ngIf condition:
#HostListener('document:click', ['$event'])
clickout(event) {
if(this.eRef.nativeElement.contains(event.target)) {
this.clickedOutside = false;
} else {
this.clickedOutside = true;
}
}
As this answer suggested:
Detect click outside Angular component

How to disable a Dynamic forms in Angular2

I have created a dynamic form based on data[fields] from JSON, but I need the form to be initially disabled, so that when we click on Edit then only form becomes editable.
Here is my code for form:
<div class="col-md-8 " [ngSwitch]="fieldInfo.dataTypeName">
<input *ngSwitchCase="'Text'"
class="form-control"
[(ngModel)]="pageInfoBeans.nameValueMap[fieldInfo.name]"
name="{{fieldInfo.name}}"
[required]="fieldInfo.preRequiredInd"
[maxLength]="fieldInfo.fieldSize">
<input *ngSwitchCase="'Email Address'"
type="email"
class="form-control"
[(ngModel)]="pageInfoBeans.nameValueMap[fieldInfo.name]"
name="{{fieldInfo.name}}"
[required]="fieldInfo.preRequiredInd"
[maxLength]="fieldInfo.fieldSize">
and in my component HTML which populates from above switch case :
<app-form class="" [fieldInfo]="fieldItem.fieldInfo" [pageInfoBeans]="pageInfoBeans"></app-form>
Initially set the form to disabled.
component.ts
showForm?:boolean = false;
component.html
<button (click)="showForm = !showForm">Edit</button>
<form *ngIf="showForm">
...form markup
</form>
You need to do something like this:
<button class='form-control' (click)='isEditable = !isEditable'>Edit Mode</button>
<div class="col-md-8 " *ngIf='isEditable' [ngSwitch]="fieldInfo.dataTypeName">
<input *ngSwitchCase="'Text'"
class="form-control"
[(ngModel)]="pageInfoBeans.nameValueMap[fieldInfo.name]"
name="{{fieldInfo.name}}"
[required]="fieldInfo.preRequiredInd"
[maxLength]="fieldInfo.fieldSize" />
<input *ngSwitchCase="'Email Address'"
type="email"
class="form-control"
[(ngModel)]="pageInfoBeans.nameValueMap[fieldInfo.name]"
name="{{fieldInfo.name}}"
[required]="fieldInfo.preRequiredInd"
[maxLength]="fieldInfo.fieldSize" />
</div>
[disabled]="!isEditable" where initialize isEditable = 'disabled' this could be added in both the text and email input fields.
Also in your edit button you can add a callback for click where you can set isEditable = ''.
#Directive({
selector : ["canbedisabled"]
})
export class Canbedisabled{
constructor(private el: ElementRef) {
}
#Input()
set blocked(blocked : boolean){
this.el.nativeElement.disabled = blocked;
}
}
<input formControlName="first" canbedisabled [blocked]="isDisabled">
You can solve it with a Directive. A directive named Canbedisabled and a property "blocked", for example. Write a setter for blocked and set it to nativelement.disabled property.
refer: https://github.com/angular/angular/issues/11271

Angular 2 Form Validation Using Validators WIth An Element That Is Dynamically Built Using *ngIf

I have a form and am using Reactive Forms for validation. I can get all the validation to work EXCEPT I have some inputs that are served conditionally that are still keeping the form from validating even when they are not served to the view using *ngIf. How can I say "Validators.required" only if a condition is true. Please see example.
constructor(
private QRService:QRService,
fb: FormBuilder
){
this.title = 'My Form';
this.showDeliveryTypes = false;
this.complexForm = fb.group({
'itemnumber' : [null, Validators.required],
'dateofbirth' : [null, Validators.required],
'deliverytype' : [null, Validators.required],
'pickuplocation' : [null, Validators.required], //validates even if these aren't included using *ngIf
'paymentoptions' : [null, Validators.required] //validates even if these aren't included using *ngIf
})
};
I only want to validate pickuplocations or paymentoptions if the *ngIf includes them in the form.
<form [formGroup]="complexForm" (ngSubmit)="findScript(complexForm.Form)">
<h2>Script Number</h2>
<input type="number" name="script" [(ngModel)]="itemno" (blur)="getScripts(itemno)" [formControl]="complexForm.controls['itemnumber']">
<h2>Date of birth</h2>
<input type="date" name="dob" [(ngModel)]="dateofbirth" (blur)="getScripts(scriptno, dateofbirth)" [formControl]="complexForm.controls['dateofbirth']" required>
<div *ngIf="showDeliveryTypes" name="deliverytypes">
<h3>Delivery Method</h3>
<select #select [(ngModel)]="selectedId" (change)="validateDeliveryTypes(select.value)" name="deliveryoptions" [formControl]="complexForm.controls['deliverytype']">
<option *ngFor="let item of qrData.DeliveryTypes" [value]="item.DeliveryTypeId">{{item.DeliveryTypeName}}</option>
</select>
</div>
<div *ngIf="showPickupLocations" name="pickuptypes">
<h3>Pickup Locations</h3> //I don't want to validate these UNLESS the *ngIf is true
<select #select [(ngModel)]="selectedPickupId" (change)="checkVals(select.value)" name="pickupoptions" [formControl]="complexForm.controls['pickuplocation']">
<option *ngFor="let item of selectedItem.PickupLocations" [value]="item.PickupLocationId">{{item.PickupLocationName}}</option>
</select>
</div>
<div *ngIf="showPaymentOptions" name="paymentoptions">
<h3>Payment Types</h3> //I don't want to validate these UNLESS the *ngIf is true
<select #select [(ngModel)]="selectedPayment" (change)="checkVals(select.value)" name="selectpaymentoptions" [formControl]="complexForm.controls['paymentoptions']">
<option *ngFor="let item of selectedItem.PaymentTypes" [value]="item.PaymentTypeId">{{item.PaymentTypeName}}</option>
</select>
</div>
<button (click)="findScript()" type="submit" name="submitbutton" [disabled]="!complexForm.valid">Find Prescription</button>
</form>
I'm still fairly new to Angular2 and can do this easily in Angular 1, but really wanting to move to Angular2 with future development.
One way would be to disable the form field when you are hiding it from the DOM. Disabling the form control, excludes that form field from the form entirely, which is what you want. If you do need to get the value included in your form upon submit, you can use the method getRawValue. But otherwise the form fields will be excluded entirely.
Since your form is pretty complex, I have set up a simple example code and plunker for you, where we disable lastname and remove it from the DOM.
Since not knowing how you toggle the showPickupLocations and showPaymentOptions, I'll just use a button here, which toggles the visibility of lastname. So the form would look like this:
<button (click)="toggle()">Toggle lastname</button>
<form [formGroup]="myForm">
<label>firstname: </label>
<input formControlName="firstname"/>
<div *ngIf="toggl">
<label>lastname: </label>
<input formControlName="lastname"/>
</div>
</form>
And when we toggle lastname visibility in DOM, we also toggle the enabling and disabling of the formfield:
toggle() {
this.toggl = !this.toggl;
let control = this.myForm.get('lastname')
control.enabled ? control.disable() : control.enable()
}
As said, this excludes the field from the form, so validation will not be a problem. In this demo plunker you can see how the toggling removes the lastname field from the form values entirely.
Demo

Angular 2 Dynamic Form - Implement Cancel functionality

I'm trying to implement Cancel functionality which should revert back to the previous values in the form, which were present during the last time the form was submitted. I'm trying to create a copy of the data object that's being passed to the form and in the Submit function i'm replacing the new values to the copy and in Cancel function, i'm replacing the copy values to the original values. But i'm not getting the previous values when i call the cancel function.
Working code with errors: http://plnkr.co/edit/SL949g1hQQrnRUr1XXqt?p=preview
I implemented the cancel functionality based on the template driven code of Angualr 2 forms: http://plnkr.co/edit/LCAPYTZElQDjrSgh3xnT?p=preview
Typescript class code:
import { Component, Input, OnInit } from '#angular/core';
import { FormGroup, REACTIVE_FORM_DIRECTIVES } from '#angular/forms';
import { QuestionBase } from './question-base';
import { QuestionControlService } from './question-control.service';
#Component({
selector: 'dynamic-form',
templateUrl: 'app/dynamic-form.component.html',
directives: [REACTIVE_FORM_DIRECTIVES],
providers: [QuestionControlService]
})
export class DynamicFormComponent implements OnInit {
#Input() questions: QuestionBase<any>[] = [];
form: FormGroup;
payLoad:object;
questiont: QuestionBase<any>;
constructor(private qcs: QuestionControlService) { }
ngOnInit() {
this.form = this.qcs.toFormGroup(this.questions);
console.log("Form Init",this.questions);
this.questiont=this.questions;
}
onSubmit() {
this.payLoad = JSON.stringify(this.form.value);
this.payLoad2=this.payLoad;
this.questiont=this.questions;
console.log("Original Data",this.questions);
console.log("Duplicate Data",this.questiont);
}
cancel(){
this.questions=this.questiont;
console.log("Original Data",this.questions);
console.log("Duplicate Data",this.questiont);
console.log("Canceled");
}
}
HTML code:
<div>
<form [formGroup]="form">
<div *ngFor="let question of questions" class="form-row">
<label [attr.for]="question.key">{{question.label}}</label>
<div [ngSwitch]="question.controlType">
<input *ngSwitchCase="'textbox'" [formControlName]="question.key"
[id]="question.key" [type]="question.type" [(ngModel)]="question.value">
<select [id]="question.key" [(ngModel)]="question.value" *ngSwitchCase="'dropdown'" [formControlName]="question.key" >
<option *ngFor="let opt of question.options" [ngValue]="opt.key" >{{opt.value}}</option>
</select>
</div>
<div class="errorMessage" *ngIf="!form.controls[question.key].valid">{{question.label}} is required</div>
</div>
<div class="form-row">
<button type="submit" [disabled]="!form.valid" (click)="onSubmit()">Save</button>
<button type="button" class="btn btn-default" (click)="cancel()">Cancel</button>
</div>
</form>
<div *ngIf="payLoad" class="form-row">
<strong>Saved the following values</strong><br>{{payLoad}}
</div>
</div>
Anyone got this problem or tried to implement this.
Form reset will be implemented in RC5.
Your approach does not work with objects:
this.questiont=this.questions; //you are sharing refrence to the same object
You should clone object instead (there are many ways to do that, i'm using JSON):
this.questiont = JSON.parse(JSON.stringify(this.questions));

Creating a Dynamic Form in a Aurelia View

Problem Overview
I have a controller with a view model which contains initially an empty array which will be used for storing 'Test Inputs'. I want to provide the user a button to add a new Test Input on the form which adds a new Test Input object to the array and displays the fields needed to edit its properties.
This works when the button is pressed for the first time (but with incorrect binding) but fails to create additional form elements when pressed again.
The Controller with View Model
import {inject} from 'aurelia-framework';
import {HttpClient, json} from 'aurelia-fetch-client';
import {Router} from 'aurelia-router';
import 'fetch';
import toastr from 'toastr';
#inject(HttpClient, Router)
export class create {
constructor(http, router) {
this.vm = {
test: {
id: 0,
description: "",
testOutput: {
id: 0,
valueType: "",
value: ""
},
testInputs: []
}
};
}
}
The user will be able to add a Test Input to the array by pressing a button which delegates to this function:
addTestInput() {
this.vm.test.testInputs.push({
argumentName: "",
valueType: "",
value: ""
});
}
This function pushes to the Test Inputs array in my view model object a new testInput object.
View
In my view I have added a repeat for binding for each object in the TestInputs array. The loop is intending to create the form elements for editing the properties of each Test Input object in the TestInputs array.
<p if.bind="vm.test.testInputs.length === 0">This test has no inputs. Click the button below to add one.</p>
<template if.bind="vm.test.testInputs.length > 0" repeat.for="testInput of vm.test.testInputs">
<div class="form-group">
<label for="testInputArgumentName${$index}">Argument Name</label>
<input value.bind="testInput.argumentName" type="text" class="form-control" id="testInputArgumentName${$index}" placeholder="Argument Name">
</div>
<div class="form-group">
<div class="form-group">
<label for="testInputValueType${$index}">Expected Value Type</label>
<select class="form-control" value.bind="testInput.valueType">
<option repeat.for="valueType of valueTypeList" value.bind="valueType">${valueType}</option>
</select>
</div>
</div>
<div class="form-group">
<label for="testInputValue${$index}">Expected Value</label>
<template if.bind="testInput.valueType === 'Boolean'">
<select class="form-control" value.bind="testInput.valueType">
<option>true</option>
<option>false</option>
</select>
</template>
<template if.bind="testInput.valueType !== 'Boolean'">
<input value.bind="testInput.value" type="text" class="form-control" id="testInputValue${$index}" placeholder="Expected Value">
</template>
</div>
</template>
<button click.delegate="addTestInput()" type="submit" class="btn btn-success">Add Test Input</button> <button type="submit" class="btn btn-success">Create Test</button>
When I first press the Add Test Input button the form elements are added to the page as expected. However if I press the button again the additional from elements for the new object added to the array are not created.
Also the fields seem to be binding to the local loop variable testInput rather than the specific object in the array.
Attempted Solutions
I have had a go using the suggestions at:
Blog Post on Dynamic Forms in Aurelia
Two Way Binding Array in Aurelia
Aurelia Gitter Chat Log - Help
from jsobell
But they don't seem to have worked for me. Anyone have any ideas?
Your problem is simple. You cannot use if and repeat on the same element. Also in your case are redundant with the p on the first line.
Simple do this:
<template repeat.for="testInput of vm.test.testInputs">
...
</template>
You can find more info here

Categories