Angular Reactive Forms validations - angular-material

I have a form with 3 inputs, 2 of the inputs are required and the third isn't.
My problem is that when I go the page of the form I see that the field that isn't required is in a valid state and is already colored with green, even tho the field isn't dirty or touched.
Is there anything I can do to make the input be grayed out until I validate the field / form or is it like this by design?
Here's the code I use in the component:
export class SystemSettingsComponent implements OnInit {
form: FormGroup;
constructor(private formBuilder: FormBuilder) {
this.form = new FormGroup({});
}
ngOnInit() {
this.form = this.formBuilder.group({
serviceName: ['', Validators.required],
serviceDesc: [''],
serviceId: [{value: SystemSettingsComponent.generateId(), disabled: true}, Validators.required]
});
}
static generateId() {
return Math.random().toString(36).substr(2, 9);
}}
And the template :
<form [formGroup]="form" novalidate>
<div class="row">
<div class="col-lg-4">
<mat-form-field>
<input matInput placeholder="שם השירות" formControlName="serviceName"/>
</mat-form-field>
<mat-form-field>
<textarea matInput placeholder="תיאור השירות" formControlName="serviceDesc"></textarea>
</mat-form-field>
<mat-form-field>
<input matInput class="ltr text-align-left" placeholder="מזהה שירות" formControlName="serviceId"/>
</mat-form-field>
</div>
</div>
<div>
<button mat-raised-button class="mat-raised-button mat-primary" matStepperNext>הבא</button>
</div>

You can listen to the form's status changes and set the optional form control's disabled state accordingly. Something like:
ngOnInit() {
this.form = this.formBuilder.group({
serviceName: ['', Validators.required],
serviceDesc: [{value: '', disabled: true}],
serviceId: [{value: SystemSettingsComponent.generateId(), disabled: true}, Validators.required]
});
this.form.statusChanges.subscribe(status => {
if (status === 'VALID' && this.form.controls.serviceDesc.disabled) {
this.form.controls.serviceDesc.enable();
} else if (status !== 'VALID' && this.form.controls.serviceDesc.enabled) {
this.form.controls.serviceDesc.disable();
}
});
}

Related

Properties not getting value in ngOnIt() in Angular 11

I am getting the data from the database using an Api which is in the LeaveService. To get the data the method used is applyLeaveGet(id:string) which is in LeaveService. This method provides certain values like the joiningDate from the database.
I am using Angular Material UI with reactive form.
The issue is that even though the leave instance is getting the data, but the in ngOnInt(), the properties are not getting the values.ngOnInt() does get triggred. For example I get the error undefined for joining date.
How can I solve the issue. Thank you for the help
LeaveService : applyLeaveGet(id:string) method
applyLeaveGet(id:string){
return this.http.get<Leave>('https://localhost:44330/api/leave/ApplyLeaveGet/'+ id);
}
ApplyLeave Component
import { HttpClient } from '#angular/common/http';
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormControl, FormGroup} from '#angular/forms';
import { ActivatedRoute } from '#angular/router';
import { AuthenticationService } from 'src/app/authentication.service';
import { Leave } from '../interfaces/leave';
import { LeaveService } from '../services/leave.service';
#Component({
selector: 'app-add-edit-leave',
templateUrl: './add-edit-leave.component.html',
styleUrls: ['./add-edit-leave.component.css']
})
export class AddEditLeaveComponent implements OnInit {
form: FormGroup;
currentDate = new Date();
minDate: Date;
maxDate: Date;
selectedFile: File = null;
url : any;
leave :Leave;
constructor(private fb: FormBuilder, private http: HttpClient
,private leaveService : LeaveService
,private route : ActivatedRoute
,private authenticationService: AuthenticationService
) {
leaveService.applyLeaveGet(authenticationService.getUserId())
.subscribe(data =>
this.leave = data
);
// Set the minimum to January 1st 20 years in the past and December 31st a year in the future.
const currentYear = new Date().getFullYear();
this.minDate = new Date(currentYear - 20, 0, 1);
this.maxDate = new Date(currentYear + 1, 11, 31);
}
ngOnInit() {
this.form = this.fb.group({
"currentDate": new FormControl(this.currentDate.toISOString().split("T")[0]),
"joiningDate":[''],
"fromDate":[''],
"tillDate":[''],
"leaveType":[''],
"duration":[''],
"reason":[''],
"filePath":['']
});
}
onSelectFile(event: any) {
this.selectedFile = <File>event.target.files[0];
if (event.target.files && event.target.files[0]) {
var reader = new FileReader();
reader.onload = (event: any) => {
this.url = event.target.result;
}
reader.readAsDataURL(event.target.files[0]);
}
else
{
this.url = "";
}
}
onSubmit(){
var userId = this.authenticationService.getUserId();
const formData = new FormData();
formData.append('userId', userId);
formData.append('currentDate', this.form.value.fullName);
formData.append('joiningDate', this.form.value.fullName);
formData.append('fromDate', this.form.value.fullName);
formData.append('tillDate', this.form.value.fullName);
formData.append('leaveType', this.form.value.fullName);
formData.append('duration', this.form.value.fullName);
formData.append('reason', this.form.value.fullName);
formData.append('balanceAnnualLeave', this.form.value.fullName);
formData.append('balanceSickLeave', this.form.value.fullName);
formData.append('balanceAnnualLeave', this.form.value.fullName);
formData.append('File', this.selectedFile);
}
}
this is the html
<p>add-edit-leave works!</p>
<div class="container">
<form class="form-container" [formGroup]="form" (ngSubmit)="onSubmit()">
<mat-card>
<mat-card-header>
<mat-card-title>Apply Leave</mat-card-title>
</mat-card-header>
<mat-card-content>
<div class="row">
<div class="col-md-6">
<mat-form-field class="full-width">
<mat-label>Today's Date</mat-label>
<input formControlName="currentDate" type="date" class="form-control" matInput placeholder="Today's Date" readonly >
</mat-form-field>
</div>
<div class="col-md-6">
<mat-form-field class="full-width">
<mat-label>Joining Date</mat-label>
<input formControlName="joiningDate" type="datetime" class="form-control" matInput placeholder="Joining Date">
</mat-form-field>
</div>
</div>
<!--Date Requested For-->
<div class="row">
<div class="col-md-6">
<mat-form-field class="full-width" appearance="fill">
<mat-label>From Date</mat-label>
<input matInput formControlName="fromDate" [min]="minDate" [max]="maxDate"
[matDatepicker]="picker">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
</div>
<div class="col-md-6">
<mat-form-field class="full-width" appearance="fill">
<mat-label>Till Date</mat-label>
<input matInput formControlName="tillDate" [min]="minDate" [max]="maxDate"
[matDatepicker]="pickerTillDate">
<mat-datepicker-toggle matSuffix [for]="pickerTillDate"></mat-datepicker-toggle>
<mat-datepicker #pickerTillDate></mat-datepicker>
</mat-form-field>
</div>
</div>
<!--Leave Type Duration(dropdown box)-->
<div class="row">
<div class="col-md-6">
<mat-form-field class="full-width" appearance="fill">
<mat-label>Leave Type</mat-label>
<select formControlName="leaveType" matNativeControl id="mySelectId">
<option value="" disabled selected></option>
<option value="Annual Leave">Annual Leave</option>
<option value="Sick Leave">Sick Leave</option>
</select>
</mat-form-field>
</div>
<div class="col-md-6">
<mat-form-field class="full-width" appearance="fill">
<mat-label>Duration</mat-label>
<select formControlName="duration" matNativeControl id="mySelectId">
<option value="" disabled selected></option>
<option value="Full Day ">Full Day</option>
<option value="First Half Day">First Half Day</option>
<option value="Second Half Day">Second Half Day</option>
</select>
</mat-form-field>
</div>
</div>
<!--Reason-->
<div class="row">
<div class="col-md-12">
<mat-form-field class="full-width">
<mat-label>Reason</mat-label>
<input formControlName="reason" matInput placeholder="Reason">
</mat-form-field>
</div>
</div>
<div class="row">
<div class="col-md-6">
<mat-label>Leave Balacne</mat-label><br>
<ul>
<li>
annualLeaveBalacne: <span>{{leave.balanceAnnualLeave}}</span> <br>
</li>
<li>
sickLeaveBalance: <span>{{leave.balanceSickLeave}}</span>
</li>
</ul>
</div>
</div>
<!--Submit-->
<mat-card-actions>
<button mat-stroked-button type="submit>Basic</button>
</mat-card-actions>
</mat-card-content>
</mat-card>
</form>
</div>
In the current version of your question it does not look like you are any setting values from this.leave in the form in OnInit. From your problem description and the rest of your code I think your problem is the asynchronous call of LeaveService in the constructor. As it it asynchronous code it might not be finished before ngOnInit is called and executed, thus the error messages about undefined values. You have to wait untill the HTTP call is finished before accessing the data.
As you should not perform potentially long running HTTP calls in the constructor, I suggest moving that part in OnInit. Everything that's not long running and asynchronous can be performed in the constructor. Your code could be refactored like this:
constructor(private fb: FormBuilder,
private http: HttpClient,
private leaveService : LeaveService,
private route : ActivatedRoute,
private authenticationService: AuthenticationService
) {
// Set the minimum to January 1st 20 years in the past and December 31st a year in the future.
const currentYear = new Date().getFullYear();
this.minDate = new Date(currentYear - 20, 0, 1);
this.maxDate = new Date(currentYear + 1, 11, 31);
this.form = this.fb.group({
"currentDate": new FormControl(this.currentDate.toISOString().split("T")[0]),
"joiningDate": [''],
"fromDate": [''],
"tillDate": [''],
"leaveType": [''],
"duration": [''],
"reason": [''],
"filePath": ['']
});
}
ngOnInit() {
leaveService.applyLeaveGet(authenticationService.getUserId())
.subscribe(data =>
{
this.leave = data;
// now data is here and can be used to set initial form values, example:
this.form.get('leaveType').setValue(this.leave.Type);
}
);
}

How to register 'react-bootstrap-typeahead' component using React 'useForm' hook?

I'm building a simple html form using "react-hook-form" library: https://react-hook-form.com/
I've incorporated "react-bootstrap-typeahead" into the html form but haven't been able to register this component with 'useForm' hook. Hence, "react-bootstrap-typeahead" input data is ignored during onSubmit.
"react-bootstrap-typeahead" doesn't provide a "name" prop which makes it difficult to register the component.
I've read the 'useForm' documentation on the different options for registering this type of components but still don't understand how to achieve this: https://react-hook-form.com/get-started#Registerfields
Does anybody have faced such challenge before?
It would be great to see a working example to get a better idea on how to implement "react-bootstrap-typeahead" + "react-hook-form" in my application. Thanks!
Here's my sample code:
import useForm from 'react-hook-form';
import { Typeahead } from 'react-bootstrap-typeahead';
import 'react-bootstrap-typeahead/css/Typeahead.css';
const myForm = (props) => {
const { register, handleSubmit, errors } = useForm();
const onSubmit = data => {
// api post request with form data
})
};
const mydata = [ "one", "two", "three" ];
return (
<>
<form onSubmit={handleSubmit(onSubmit)} >
<div className="form-group">
{/* Here I'm registering text input using ref: */}
<input type="text" className="form-control" name="name" ref={register({ required: true })} />
</div>
<div className="form-group mb-0">
{/* How can I register the below component with useForm? */}
<Typeahead
id="multiple-typeahead"
clearButton
multiple
options={mydata}
/>
</div>
<button type="submit">Save</button>
</form>
</>
);
}
This is how i was able to register the component:
import useForm from 'react-hook-form';
import { useForm, Controller } from "react-hook-form";
import 'react-bootstrap-typeahead/css/Typeahead.css';
const myForm = (props) => {
const { register, handleSubmit, errors, control } = useForm();
const onSubmit = data => {
// api post request with form data
})
};
const mydata = [ "one", "two", "three" ];
return (
<>
<form onSubmit={handleSubmit(onSubmit)} >
<div className="form-group">
<input type="text" className="form-control" name="name" ref={register({ required: true })} />
</div>
<div className="form-group mb-0">
<Controller
as={Typeahead}
control={control}
name="typeahead_component"
rules={{ required: true }}
id="multiple-typeahead"
clearButton
multiple
options={mydata}
defaultValue=""
/>
</div>
<button type="submit">Save</button>
</form>
</>
);
}

angular2 async validation this.subscribe exception?

I try to implement async(isUnique) and sync(cannotContainSpace) validation functions for username field, I want to see "this username already in use alert msg, if textbox value is "yener". sync function works fine but i think ss below can help explain my issue;
Note:canNotContain sync validation func works fine, aafter I added isUnique async function this exception occured..
how can I fix it ?
usernamevalidator.ts
import {FormControl} from '#angular/forms';
export class UsernameValidator{
static isUnique(control:FormControl){
return new Promise((resolve, reject)=>{
setTimeout(function(){
debugger
if(control.value =="yener")
resolve({isUnique:true})
else
resolve(null)
},1000);
});
}
static cannotContainSpace(control:FormControl){
if(control.value.indexOf(' ')>=0)
return { cannotContainSpace:true };
return null;
}
}
postmessage.component.ts
import { Component } from '#angular/core';
import {FormControl,FormGroup,FormBuilder,Validators} from '#angular/forms';
import {UsernameValidator} from '../../validators/usernamevalidator';
#Component({
moduleId:module.id,
selector: 'post-message',
templateUrl: '../../templates/postmessage.component.html'
})
export class PostComponent {
form : FormGroup;
constructor(fb:FormBuilder){
this.form = fb.group({
username:['', Validators.compose([Validators.required, UsernameValidator.cannotContainSpace]),Validators.compose([UsernameValidator.isUnique])],
email:['', Validators.required],
message:['', Validators.required]
});
}
signup(){
console.log(this.form.value);
}
}
here is html template:
<form class="from-horizontal" [formGroup]="form" (ngSubmit)="signup()">
<div class="form-group row">
<label for="username" class="control-label col-md-2">Name:</label>
<div class="col-md-6">
<input type="text" id="username" class="form-control" formControlName="username">
<div *ngIf="form.controls['username'].touched && form.controls['username'].errors">
<div class="alert alert-danger"
*ngIf="form.controls['username'].errors.required">
User name is required.
</div>
<div class="alert alert-danger"
*ngIf="form.controls['username'].errors.cannotContainSpace">
User name can not contain space
</div>
<div class="alert alert-danger" *ngIf="form.controls['username'].errors.isUnique">
This user name already in use.
</div>
</div>
</div>...
it's strange, i though we can use "Validators.compose()" function in form builder initializer as async parameters but angular2 dont agree with me..
I just changed;
username:['', Validators.compose([Validators.required,
UsernameValidator.cannotContainSpace]),Validators.compose([UsernameValidator.isUnique])],
to
username:['', Validators.compose([Validators.required,
UsernameValidator.cannotContainSpace]),UsernameValidator.isUnique],
and it works
EDITED:
also I figured out if you want to use multiple async validators in a component use
Validators.composeAsync()
function.

Form validation on non form field angular2

im quite new with angular2, but i seem to manage ok for a beginner. However, i'm stuck on some validation issues. i want to validate a component that is not a form field (e.g. input, select e.t.c).
I'm using a bootstrap dropdown which uses an unsorted list.
dropdownButtons.html
<div class="btn-group" dropdown>
<button type="button" class="btn btn-default" dropdownToggle >
{{ selected ? selected : 'Type...'}}
</button>
<ul class="dropdown-menu" dropdownMenu>
<li *ngFor="let value of values;let i = index" (click)="onChange(value)">
{{value.label}}
</li>
</ul>
</div>
dropdownButtons.component.ts
import {Component, EventEmitter, Output, Input, Injectable} from '#angular/core';
import { DROPDOWN_DIRECTIVES } from 'ng2-bootstrap/ng2-bootstrap';
#Component({
selector: 'dropdown-buttons',
template: require('./dropdownButtons.html'),
directives: [DROPDOWN_DIRECTIVES]
})
#Injectable()
export class DropdownButtons {
#Input()
values: DropdownValue[] = [{ "value": "RSS", "label": "RSS" },{ "value": "REST", "label": "REST" }];
#Input()
selected : string;
#Output()
select: EventEmitter<DropdownValue>;
constructor() {
this.select = new EventEmitter();
}
onChange(type) {
this.select.emit(type);
}
}
export class DropdownValue {
value:string;
label:string;
constructor(value:string,label:string) {
this.value = value;
this.label = label;
}
}
My form looks like this.
<form (ngSubmit)="onSubmit()" #refererform="ngForm">
<div class="form-group">
<label for="inputUrl">Url</label>
<input type="text" class="form-control" id="inputUrl" placeholder="Url" required
[(ngModel)]="model.url" name="url">
</div>
<div class="form-group">
<dropdown-buttons [(selected)]="model.type" (select)="onSelect($event)" ></dropdown-buttons>
</div>
<div class="form-group" ng-show="showDetails" *ngIf="isShown()">
<label for="header">Header</label>
<textarea placeholder="Default Input" class="form-control" id="header"
[(ngModel)]="model.header" name="header"></textarea>
</div>
<div class="form-group" ng-show="showDetails" *ngIf="isShown()">
<label for="payload">Payload</label>
<textarea placeholder="Default Input" class="form-control" id="payload"
[(ngModel)]="model.payload" name="payload"></textarea>
</div>
<button type="submit" class="btn btn-danger" >Submit</button>
</form>
I tried to use ngModel (using required in the tags) and the FormGroup option, where i define some formcontrols. It works fine with form controls, but i can't seem to figure out how i validate non form components, is it even possible?
thanks in advance.

Dynamically add multiple fields to be validated using bootstrap validator

I have gone through the example here. But it illustrates dynamic addition of a single input field. I have to add multiple dynamic input fields. How can I achieve it? Refer this example jsfiddle
I need to dynamically add all the three fields in the table row on clicking button through.
You can try somethings like this:
<form id="myForm" action="myAction">
<div class="row" id="line_1">
<div class="col-md-2 form-group">
<input type="text" class="form-control input-sm" id="idFirstField_1" name="firstField[]">
</div>
<div class="col-md-2 form-group">
<input type="text" class="form-control input-sm" id="idSecondField_1" name="secondField[]">
</div>
<div class="col-md-2 form-group">
<input type="text" class="form-control input-sm" id="idThirdField_1" name="thirdField[]">
</div>
</div>
<a id="cloneButton">add line</a>
</form>
In the JavaScript file you must to use the function clone() and to change the id of each input if you want:
$(document).ready(function () {
var count = 2;
$('#cloneButton').click(function () {
var klon = $('#line_1');
klon.clone().attr('id', 'line_' + (++count)).insertAfter($('#line_1'));
$('#line_' + count).children('div').children('input').each(function () {
$(this).val('');
var oldId = $(this).attr('id').split('_');
$(this).attr('id', oldId[0] + '_' + count);
});
});
//if you want to validate the fields, then you can use this code:
$('#myForm').bootstrapValidator({
fields: {
'firstField[]': {
validators: {
notEmpty: {
message: 'Enter a value'
}
}
},
'secondField[]': {
validators: {
notEmpty: {
message: 'Enter a value'
}
}
},
'thirdField[]': {
validators: {
notEmpty: {
message: 'Enter a value'
}
}
}
}
});
});
Now the bootstrap validation does not will work for cloned fields because you must to use in the JavaScript file somethings like this
$('#myForm').bootstrapValidator('addField', $option); //(from your link http://bootstrapvalidator.com/examples/adding-dynamic-field/ )
but who will contains all fields. I don't now how to do it.

Resources