How to display only single validation error at a time - validation

I have this code to which displaying errors on my form
<input [ngFormControl]="form1.controls['thing']" type="text" id="thing" #thing="ngForm">
<div *ngIf='thing.dirty && !thing.valid'>
<div class="err" *ngIf='thing.errors.required'>
Thing is required.
</div >
<div class="err" *ngIf='thing.errors.invalid'>
Thing is invalid.
</div >
</div>
But in case of thing has two errors in it the two error show up.
Lets say if my input has 5 validators so 5 divs will show up which is not nice.
How to display just one error div at a time?

You could create a custom pipe to get the first element of the errors object of the validator:
#Pipe({
name: 'first'
})
export class FirstKeyPipe {
transform(obj) {
var keys = Object.keys(obj);
if (keys && keys.length>0) {
return keys[0];
}
return null;
}
}
This way you would be able to display only one error:
#Component({
selector: 'my-app',
template: `
<form>
<input [ngFormControl]="form.controls.input1">
<div *ngIf="form.controls.input1.errors">
<div *ngIf="(form.controls.input1.errors | first)==='required'">
Required
</div>
<div *ngIf="(form.controls.input1.errors | first)==='custom'">
Custom
</div>
</div>
</form>
`,
pipes: [ FirstKeyPipe ]
})
export class MyFormComponent {
constructor(private fb:FormBuilder) {
this.form = fb.group({
input1: ['', Validators.compose([Validators.required, customValidator])]
});
}
}
See this plunkr: https://plnkr.co/edit/c0CqOGuzvFHHh5K4XNnA?p=preview.
Note: agreed with Günter to create a usable component ;-) See this article for more details:
http://restlet.com/blog/2016/02/17/implementing-angular2-forms-beyond-basics-part-2/

If you have consistent markup for your error message blocks, then you can use css to display only the first message and hide the rest:
css
.message-block .error-message {
// Hidden by default
display: none;
}
.message-block .error-message:first-child {
display: block;
}
markup
<div class="message-block">
<span class="error-message" *ngIf="myForm.get('email').hasError('required')">
Email is required (first-child of message block is displayed)
</span>
<span class="error-message" *ngIf="myForm.get('email').hasError('email')">
Invalid email format (error message hidden by default)
</span>
</div>

<input [ngFormControl]="form1.controls['thing']" type="text" id="thing" #thing="ngForm">
<div *ngIf='thing.dirty && !thing.valid'>
<div class="err" *ngIf='thing.errors.required'>
Thing is required.
</div >
<div class="err" *ngIf='!thing.errors.required && thing.errors.ivalid'>
Thing is invalid.
</div >
</div>
You could create a reusable component for showing errors so you don't need to repeat this code again and again.

This works and you don't have to hardcode the validations in you template like #Joes answer above.
Template:
<input id="password" placeholder="Password" type="password" formControlName="password" [(ngModel)]="password" [ngClass]="{'invalid-input': !formUserDetails.get('password').valid && formUserDetails.get('password').touched}">
<div class="validation-container">
<ng-container *ngFor="let validation of userValidationMessages.password">
<div class="invalid-message" *ngIf="formUserDetails.get('password').hasError(validation.type) && formUserDetails.get('password').touched">
{{validation.message}}
</div>
</ng-container>
</div>
CSS:
.validation-container div {
display: none;
}
.validation-container div:first-child {
display: block;
}

Angular2 behind the scene checks the status of the control and reacts accordingly. So if you don't want to have more validation at a time, you can logically play with AND(&&) or/and OR(||) or/and NOT(!) operators.

You can create a Custom Pipe that checks first error equals with specified error:
CUSTOM PIPE
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'equals'
})
export class Equals implements PipeTransform {
transform(errors: any, error: any, args?: any): any {
if (!errors)
return false;
const array = Object.keys(errors);
if (array && array.length > 0)
return errors[array[0]] === error;
return false;
}
}
You can have lots of error div but just one error will be shown:
// input is form.controls.input1
<div *ngIf="input.errors | equals:input.errors.required">Required</div>
<div *ngIf="input.errors | equals:input.errors.maxlength">MaxLength</div>
<div *ngIf="input.errors | equals:input.errors.pattern">Pattern</div>

Related

Vue.js 3 - Add list items from component dynamically

I am new to vue, I found some examples but I could not reproduce in my situation.
I have some inputs user will fill out and once it clicks the button, the information will populate a list of items. I am trying to crete < li > with a OrderListItem component. But I am getting this error:
runtime-core.esm-bundler.js?5c40:217 Uncaught ReferenceError: machines is not defined
at eval (OrderListItem.vue?fb66:14)
Here is the form
<template>
<div>
<div class="flex flex-col mb-4 md:w-full m-1">
<label for="planter_model_id">Planter</label>
<select v-model="registration.planter_model_id" name="planter_model_id">
<option disabled selected>Planter</option>
<option v-for="planter in planters" :key="planter" :value="planter.id">{{planter.brand.name}} {{planter.name }}</option>
</select>
</div>
<div class="flex flex-col mb-4 md:w-full m-1">
<label class=" for="lines">Lines</label>
<Input v-model="registration.lines" type="number" name="lines" />
</div>
<Button type="button" #click="addItemOrder">Add</Button>
</div>
<div>
<ul>
<OrderListItem :machines="machines" />
</ul>
<div>
</template>
Here is my code
export default {
components: {
OrderListItem,
Button,
},
props: {
planters:{
type:Array,
required: true
},
},
data() {
return {
machines: [], //this what I wish to be passed to OrderListItem component
registration:{
lines:null,
planter_model_id:null,
},
}
},
methods:{
addItemOrder(){
let item = {}
item.planter_model_id = this.registration.planter_model_id
item.lines = this.registration.lines
this.machines.push(item)
}
}
};
And here is my OrderListItem component that I created on another file:
<template>
<li v-for="machine in machines" :key="machine">
<div class="flex-initial w-3/5">
<p>Planter: {{machine.planter_model_id}}</p>
<p>Lines: {{machine.lines}}</p>
</div>
</li>
</template>
<script>
export default {
name: 'OrderListItem',
props:{
machines,
}
}
</script>
I don’t know if it is relevant but I am using vue3 and laravel.
I am very newbie here and from what I learned I think if machines array was a prop I would be able to access it on my component. But I will dynamically add a and remove itens from machines array and I think props receive data from server so It should not be declared there, that why I declared inside data().
Thanks =)
You should define the prop by adding its type and default value :
export default {
name: 'OrderListItem',
props:{
machines:{type:Array,default:()=>[]},
}
}

Angular 7 - When user types in input field, how can I make the actual value upper case?

Currently, when the user is typing in an input field, I have a css property that will transform the text to uppercase. I've run into a few problems however with validation.
For example, if a user is asked to confirm their email address and they copy and paste the value in one field to the next, it is telling them that the emails do not match.
My suspicion is that the actual value typed in is lowercase and the value they paste into the confirmation input is uppercase and therefore do not match (i could be wrong but thats not what I am trying to resolve at this time.
How can I make a text input field value all uppercase, as the user types using only angular 7?
My css file contains:
.make-uppercase {
text-transform: uppercase;
}
My HTML for the email field is as follows:
<div class="col-lg-8 registration-answer">
<label for="form-student-contact-3" class="col-lg-12" data-hint="yes">
<span class="form-required">*</span>{{'Confirm Email Address' | translate}}<i class="fa fa-info-circle ml-2" aria-hidden="true"></i>
</label>
<input
type="text"
formControlName="confirmEmail"
[ngClass]="{ 'is-invalid': submitted && registerFormControl.confirmEmail.errors }"
class="form-control make-uppercase col-md-8"
data-hint="yes"
id="form-student-contact-text-3"
>
<div *ngIf="registerFormControl.confirmEmail.errors" class="text-danger col-md-8">
<div *ngIf="this.registerFormControl.confirmEmail.touched && this.registerFormControl.confirmEmail.invalid && !this.registerFormControl.confirmEmail.errors.required">
Emails do not match.
</div>
<div *ngIf="registerFormControl.confirmEmail.errors.required" class="text-danger col-md-8">
Confirm Email is required.
</div>
</div>
</div>
In my TS file for my reactive form
ngOnInit() {
this.registerForm = this.formBuilder.group({
email: ['', Validators.required],
confirmEmail: ['', Validators.required],
}, {
validator: CustomValidators.MustMatch('email', 'confirmEmail')
});
And my custom validator for MustMatch
static MustMatch(controlName: string, matchingControlName: string) {
return (formGroup: FormGroup) => {
const control = formGroup.controls[controlName];
const matchingControl = formGroup.controls[matchingControlName];
if (matchingControl.errors && !matchingControl.errors.mustMatch) {
// return if another validator has already found an error on the matchingControl
return;
}
// set error on matchingControl if validation fails
if (control.value !== matchingControl.value) {
matchingControl.setErrors({ mustMatch: true });
} else {
matchingControl.setErrors(null);
}
}
}
After searching around I found an answer in case anyone was wondering.
Add this to the input property:
oninput="this.value = this.value.toUpperCase()"
Keep the CSS text transform to prevent the user from seeing lowercase letters being transformed into uppercase:
.make-uppercase {
text-transform: uppercase;
}

Passing the Div id to another vue component in laravel

I created a simple real-time chat application using vue js in laravel.
I am having a problem with the automatic scroll of the div when there is a new data.
What I want is the div to automatically scroll down to the bottom of the div when there is a new data.
Here is my code so far.
Chat.vue file
<template>
<div class="panel-block">
<div class="chat" v-if="chats.length != 0" style="height: 400px;" id="myDiv">
<div v-for="chat in chats" style="overflow: auto;" >
<div class="chat-right" v-if="chat.user_id == userid">
{{ chat.chat }}
</div>
<div class="chat-left" v-else>
{{ chat.chat}}
</div>
</div>
</div>
<div v-else class="no-message">
<br><br><br><br><br>
There are no messages
</div>
<chat-composer v-bind:userid="userid" v-bind:chats="chats" v-bind:adminid="adminid"></chat-composer>
</div>
</template>
<script>
export default {
props: ['chats','userid','adminid'],
}
</script>
ChatComposer.vue file
<template>
<div class="panel-block field">
<div class="input-group">
<input type="text" class="form-control" v-on:keyup.enter="sendChat" v-model="chat">
<span class="input-group-btn">
<button class="btn btn-primary" type="button" v-on:click="sendChat">Send Chat</button>
</span>
</div>
</div>
</template>
<script>
export default{
props: ['chats','userid','adminid'],
data() {
return{
chat: ''
}
},
methods: {
sendChat: function(e) {
if(this.chat != ''){
var data = {
chat: this.chat,
admin_id: this.adminid,
user_id: this.userid
}
this.chat = '';
axios.post('/chat/sendChat', data).then((response) => {
this.chats.push(data)
})
this.scrollToEnd();
}
},
scrollToEnd: function() {
var container = this.$el.querySelector("#myDiv");
container.scrollTop = container.scrollHeight;
}
}
}
</script>
I am passing a div id from the Chat.vue file to the ChatComposer.vue file.
As you can see in the ChatComposer.vue file there is a function called scrollToEnd where in it gets the height of the div id from Chat.vue file.
When the sendchat function is triggered i called the scrollToEnd function.
I guess hes not getting the value from the div id because I am getting an error - Cannot read property 'scrollHeight' of null.
Any help would be appreciated.
Thanks in advance.
the scope of this.$el.querySelector will be limited to only ChatComposer.vue hence child component can not able to access div of parent component #myDiv .
You can trigger event as below in ChatComposer
this.$emit('scroll');
In parent component write ScollToEnd method and use $ref to assign new height
<chat-composer v-bind:userid="userid" v-bind:chats="chats" v-bind:adminid="adminid" #scroll="ScollToEnd"></chat-composer>
..

How to pass data from one component to other in vue js?

I am learning vue+laravel. I want to pass value from one component to other component? I have used vue router for routing.
Here is the code for first and second component.
SelectPerson.vue
<template>
......
<div>
<input type="number" class="form-control" name="numberOfPersons" placeholder="Enter number of persons here" **v-model="inputPersons"**>
<br>
**<SelectTimeSlot v-bind:numberOfPersons="inputPersons"></SelectTimeSlot>**
</div>
<div>
<button class="btn btn-default float-right mt-2" v-on:click="selectTimeSlots">Next</button>
</div>
......
</template>
<script>
import SelectTimeSlot from './SelectTimeSlot.vue'
export default{
props:['numberOfPersons'],
data(){
return{
**inputPersons:0**
}
},
methods:{
selectTimeSlots(){
this.$router.push({name:'SelectTimeSlot'});
}
}
}
</script>
second component
SelectTimeSlot.vue
<template>
<h5>Welcome, You have selected **{{numberOfPersons}}** persons.</h5>
</template>
Can anybody help me do it?
To pass data from one component to other component, you need to use props:
First component:
<second-component-name :selectedOption="selectedOption"></second-component-name>
<script>
export default {
components: {
'second-component-name': require('./secondComponent.vue'),
},
data() {
return {
selectedOption: ''
}
}
}
</script>
Second Component:
<template>
<div>
{{ selectedOption }}
</div>
</template>
<script>
export default {
props: ['selectedOption']
}
</script>
Please visit this link.
Hope this is helpful for you!
Say I have a page with this HTML.
<div class="select-all">
<input type="checkbox" name="all_select" id="all_select">
<label #click="checkchecker" for="all_select"></label>
</div>
the function checkchecker is called in my methods
checkchecker() {
this.checker = !this.checker
}
This will show or hide my div on that page like this
<div v-show="checker === true" class="select-all-holder">
<button>Select All</button>
</div>
Now if I also want to toggle another div which is inside my child
component on that page I will pass the value like this.
<div class="content-section clearfix">
<single-product :checkers="checker"></single-product> //This is calling my child component
</div>
Now in my Child component I will have a prop declared like this
checkers: {
type: String,
default: false,
},
This is how I will write my div in my child component
<div v-show="checkers === true" class="select-holder clearfix">
<input type="checkbox" class="unchecked" name="single_select" id="1">
</div>

Server side validation for angular 2 forms

I am trying to figure out how I can send some data to a server side API call, let that do the validation and then return back with some validation errors and those validation errors should be displayed along with the component that caused the validation error. Here is how my data model looks like:
export class Order {
orderNo: string;
items: Item[];
constructor() {
this.items = [];
}
}
export class Item {
recNo: string;
}
This is how the HTML form looks like
<div class="form-group">
<label name="orderNo">Order Number</label>
<input type="text" formControlName="orderNo" />
</div>
<div formArrayName="items">
<div *ngFor="let item of orderForm.controls.items.controls; let i=index" [formGroupName]="i">
<div class="form-group">
<label>Item # {{i}}</label>
<input type="text" formControlName="recNo" />
</div>
</div>
</div>
The validation errors that I receive from the server are in this form
[
{
"field": "orderNo",
"message": "order number is required"
},
{
"field": "items[2].recNo",
"message": "record number is required"
}
]
I am not able to figure out how, once I receive the validation errors, can I update the errors objects on the form controls or populate some other structure to show the errors. Its a bit simpler for static fields like orderNo but a lot more complex for nested array fields like items[0].recNo to update the errors programmatically. Any ideas would be appreciated.
This is what I ended up doing:
<div class="form-group" [class.has-danger]="!ctrl.valid && ctrl.enabled">
<input type="text" class="form-control" [formControl]="ctrl"
value="{{valueFormatter(ctrl)}}"
[class.form-control-danger]="!ctrl.valid && ctrl.enabled"
[ngbTooltip]="ctrl.errors ? ctrl.errors.e : falsy" />
</div>
Basically, setting up error classes on the form control based on the ctrl valid/error state and here is an example of specifying an error on a control:
this.ctrl.setErrors({ "e": this.label + " is required"});
I just used a predefined error key e to display any errors. This way, once I receive the error list from the backend, I can set the e error on a control and have that show up in a tooltip on the control.

Resources