Typing the Ref Composition API Vue - forms

how could I type correctly my Ref firstName as string? There are 2 errors underlined, in the template - {{ firstName }}, and in the script - const firstName = ref('') as String. I suppose it has to do with the typing, for the Ref I asuume is working correctly.
<template>
</div> -->
<v-sheet >
<v-form fast-fail #submit.prevent>
<v-text-field
label="First name"
>{{ firstName }}</v-text-field>
</v-form>
</v-sheet>
</template>
<script lang="ts">
import { ref } from 'vue'
export default {
setup() {
const firstName = ref('') as String
},
}
</script>

Related

Validate multiple selects with a vue Form

I am new to Vue and I am trying to validate a form made by multiple select rendered by a vfor. The date is coming from a Json, simulated mock-server-json.
I can use Vue Vanilla or vee-validate. I saw I could use useFieldArray with vee-validate but I could not make it work.
<template>
<ux-loader v-if="dataArray.length == 0" loading></ux-loader>
<transition name="onEnter">
<div v-if="dataArray.length != 0">
<form #submit.prevent="handleSubmit">
<div class="form">
<div v-for="(data, index) in dataArray" :key="index" class="select">
{{ index }}
<ux-input-a11y-select v-model="form.selected[index]">
<option data-placeholder value="">-- Choisir une valeur --</option>
<option v-for="option in data.option" :key="option" :value="option">{{ option }}</option>
</ux-input-a11y-select>
</div>
</div>
<button class="submit">Valider</button>
</form>
<Modal v-show="isModalVisible" #close="closeModal" />
</div>
</transition>
<div v-if="error != null">{{ error }}</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import Modal from '../components/Modal.vue'
import { UxBtn, UxInputA11ySelect, UxLoader } from '#libui/ux-default-lib'
import getForm from '../composables/getForm.js'
import postForm from '../composables/postForm.js'
UxInputA11ySelect.define()
UxBtn.define()
UxLoader.define()
export default defineComponent({
name: 'FormUser',
components: {
Modal,
},
setup() {
const dataArray = ref([])
const { error, load } = getForm()
const { sendData } = postForm()
load().then((result: any) => {
dataArray.value = result
})
return { sendData, error, dataArray }
},
data() {
return {
isModalVisible: false,
form: {
selected: [],
},
}
},
methods: {
handleSubmit() {
this.isModalVisible = true
},
async closeModal() {
this.isModalVisible = false
console.log(this.form.selected)
console.log(Object.values(this.form.selected))
this.sendData(this.form.selected)
this.$router.push('Display')
},
},
})
</script>
This is my current code. It is working as I can get an object containing the results of the selects but I am wondering if there is a better way of doing it, like in Angular where you get an object with all results without doing anything particular.
Any help appreciated.

How to change text inputs into tags, in an existing Vue 3 form, using a Tag Input Component?

I have an existing form in a Vue 3 project that posts successfully to a Cloud Firestore database.
How do I use change two of the type="text" input fields into tag inputs, using a Tag Input Component like this? Here's a link to it on CodeSandbox
The standalone tutorial is very clear and works fine. What I'm struggling with is how to incorporate that into an existing Vue 3 form and continue posting everything to the Firestore database.
I stripped-away styles and many other input fields from the "base" code below:
<template>
<form #submit.prevent="handleSubmit" >
<h3>What kind of pet?</h3>
<div class="space-y-0">
<select required name="pet_type" id="pet_type" v-model="pet_type">
<option value="">Please select</option>
<option value="cat">Cat</option>
<option value="dog">Dog</option>
<option value="wolverine">Wolverine</option>
</select>
</div>
<hr>
<div> <!-- Want to change this into the first tag input -->
<h3>Pros</h3>
<div for="petPros">What are 3 positive things about this pet?</div>
<input type="text" placeholder="separate , with , commas" id="petPros" v-model="petPros">
<label for="petPros">separate , with , commas</label>
</div>
<hr>
<div>
<h3>Cons</h3> <!-- Want to change this into a second tag input -->
<div for="petCons">And what are 3 negative things about this pet?</div>
<input type="text" placeholder="separate , with , commas" id="petPros" v-model="petCons">
<label for="petCons">separate , with , commas</label>
</div>
<hr>
<h3>Privacy</h3>
<div>
<div>
<input type="radio" id="Fullpublic" value="Fullpublic" name="reviewPrivacy" required v-model="reviewPrivacy">
<label class="text-base inline-flex ml-4 align-bottom" for="Fullpublic">Public</label>
</div>
<div>
<input type="radio" id="keepFullyPrivate" value="keepFullyPrivate" name="reviewPrivacy" required v-model="reviewPrivacy">
<label class="text-base inline-flex ml-4 align-bottom" for="keepFullyPrivate">Private</label>
</div>
</div>
<hr>
<button v-if="!isPending" >Submit</button>
<button v-else disabled>Saving...</button>
</form>
</template>
<script>
import { ref } from 'vue'
import useStorage from '#/composables/useStorage'
import useCollection from '#/composables/useCollection'
export default {
setup() {
const { filePath, url, uploadImage } = useStorage()
const { error, addDoc } = useCollection('reviews')
const { user } = getUser()
const router = useRouter()
const pet_type = ref('')
const petPros = ref('')
const petCons = ref('')
const reviewPrivacy = ref('')
const file = ref(null)
const fileError = ref(null)
const isPending = ref(false)
const handleSubmit = async () => {
if (file.value) {
isPending.value = true
await uploadImage(file.value)
const res = await addDoc({
pet_type: pet_type.value,
petPros: petPros.value,
petCons: petCons.value,
reviewPrivacy: reviewPrivacy.value,
userId: user.value.uid,
userName: user.value.displayName,
createdAt: timestamp()
})
isPending.value = false
if (!error.value) {
router.push({ name: 'ReviewDetails', params: { id: res.id }})
}
}
}
// allowed file types
const types = ['image/png', 'image/jpeg']
const handleChange = (e) => {
let selected = e.target.files[0]
console.log(selected)
if (selected && types.includes(selected.type)) {
file.value = selected
fileError.value = null
} else {
file.value = null
fileError.value = 'Please select an image file (png, jpg or jpeg)'
}
}
return { pet_type, petPros, petCons, reviewPrivacy, handleSubmit, fileError, handleChange, isPending }
}
}
</script>
<style>
</style>
Thanks for any help!
In the form, replace the two <input type="text">s with <TagInput>. Keep the v-model the same, as TabInput implements v-model.
<!-- BEFORE -->
<input type="text" placeholder="separate , with , commas" id="petPros" v-model="petPros">
<input type="text" placeholder="separate , with , commas" id="petPros" v-model="petCons">
<!-- AFTER -->
<TagInput id="petPros" v-model="petPros" />
<TagInput id="petCons" v-model="petCons" />
Locally register TagInput.vue in the form component:
import TagInput from "./TagInput.vue";
export default {
components: {
TagInput,
},
}
Change the initial values of petPros and petCons refs to arrays, as the TabInput outputs a string array of the input tag values:
// BEFORE
const petPros = ref('')
const petCons = ref('')
// AFTER
const petPros = ref([])
const petCons = ref([])
TabInput normally adds a tag upon hitting TAB, but your placeholder suggests that you want to use the comma key , instead. To do that, update the modifier in the #keydown binding:
<!-- BEFORE -->
<input v-model="newTag"
#keydown.prevent.tab="addTag(newTag)"
>
<!-- AFTER -->
<input v-model="newTag"
#keydown.prevent.,="addTag(newTag)"
>
demo

Pass new password value to validator.ts in Angular 4

I am working through the challenges for a Udemy course on Angular 4, but I am stuck on a challenge where I have to create an input for a new password and then another input to confirm the new password using reactive forms.
I have an external .ts file called password.validators.ts that has custom form validation code, and I can get the value of the currently selected input box by passing a control object with AbstractControl, but how do I pass a value to my component.ts file and then from my component.ts to my password.validators.ts ? I need to be able to compare the new password value to the confirm password value and I'm stuck!
new-password.component.html
<form [formGroup]="form">
<div class="form-group">
<label for="oldPassword">Old Password</label>
<input
formControlName="oldPassword"
id="oldPassword"
type="text"
class="form-control">
<div *ngIf="oldPassword.pending">Checking password...</div>
<div *ngIf="oldPassword.touched && oldPassword.invalid" class="alert alert-danger">
<div *ngIf="oldPassword.errors.required">Old password is required</div>
<div *ngIf="oldPassword.errors.checkOldPassword">Password is incorrect</div>
</div>
</div>
<div class="form-group">
<label for="newPassword">New password</label>
<input
formControlName="newPassword"
id="newPassword"
type="text"
class="form-control">
<div *ngIf="newPassword.touched && newPassword.invalid" class="alert alert-danger">
<div *ngIf="newPassword.errors.required">New password is required</div>
</div>
</div>
<div class="form-group">
<label for="confirmNewPassword">Confirm new password</label>
<input
formControlName="confirmNewPassword"
id="confirmNewPassword"
type="text"
class="form-control">
<div *ngIf="confirmNewPassword.touched && confirmNewPassword.invalid" class="alert alert-danger">
<div *ngIf="confirmNewPassword.errors.required">Confirm password is required</div>
<div *ngIf="confirmNewPassword.errors.confirmNewPassword">Passwords don't match</div>
</div>
</div>
<button class="btn btn-primary" type="submit">Change Password</button>
</form>
new-password.component.ts
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, Validators } from '#angular/forms';
import { PasswordValidators } from './password.validators';
#Component({
selector: 'new-password',
templateUrl: './new-password.component.html',
styleUrls: ['./new-password.component.css']
})
export class NewPasswordComponent {
form = new FormGroup({
oldPassword: new FormControl('', Validators.required, PasswordValidators.checkOldPassword),
newPassword: new FormControl('', Validators.required),
confirmNewPassword: new FormControl('', Validators.required )
})
get oldPassword() {
return this.form.get('oldPassword');
}
get newPassword() {
return this.form.get('newPassword');
}
get confirmNewPassword() {
return this.form.get('confirmNewPassword');
}
addNewPassword(newPassword: HTMLInputElement) {
let np = this.newPassword;
return np;
}
}
password.validators.ts
import { AbstractControl, ValidationErrors } from '#angular/forms';
export class PasswordValidators {
static checkOldPassword(control: AbstractControl) : Promise<ValidationErrors | null> {
return new Promise((resolve, reject) => {
setTimeout(() => {
if(control.value !== '1234')
resolve({ checkOldPassword: true }) ;
else resolve(null);
}, 2000);
});
}
static confirmNewPassword(control: AbstractControl) : ValidationErrors | null {
if(control.value === control.newPassword.value)
return null;
}
}
I have used the following code for my password validation may be this can help you
In password.validator write this code
import {AbstractControl} from '#angular/forms';
export class PasswordValidation {
static MatchPassword(AC: AbstractControl) {
let password = AC.get('password').value;
let confirmPassword = AC.get('confirmPassword').value;
if(password != confirmPassword) {
console.log('false');
AC.get('confirmPassword').setErrors( {MatchPassword: true} )
} else {
console.log('true');
return null
}
}
}
and in the component file use this code
constructor(fb: FormBuilder)
{
this.form = fb.group({
password: ['', Validators.required],
confirmPassword: ['', Validators.required]
}, {
validator: PasswordValidation.MatchPassword // your validation method
})
}
and in html file to find error use this code
<div class="alert alert-danger" *ngIf="form.controls.confirmPassword.errors?.MutchPassword">Password not match</div>
Hope it would help you

How to access form inputs in react js with nested object

I'm working a react form using the controlled inputs to access the change on the form inputs. It contained lot of fields but i haven't posted all the fields.
import React from 'react';
import Links from './Links.jsx';
import axios from 'axios';
import Dialog from 'react-bootstrap-dialog';
import {Typeahead} from 'react-bootstrap-typeahead';
import Autocomplete from 'react-autocomplete';
import style from './app.css';
class AddNewEmployee extends React.Component {
constructor(props){
super(props);
this.state = {
firstName : '',
middleName : ''
}
this.handleUserInput = this.handleUserInput.bind(this);
}
handleUserInput(){
const name = e.target.name;
const value = e.target.value;
this.setState({
[name]: value
})
}
render() {
return(
<div className = "col-sm-6">
<input type="text"
placeholder="Enter First Name" name="firstName"
value={this.state.firstName} onChange={(event) => this.handleUserInput(event)}>
</input>
</div>
<div className = "col-sm-6">
<input type="text"
placeholder="Enter First Name" name="middleName"
value={this.state.middleName} onChange={(event) => this.handleUserInput(event)}>
</input>
</div>
)
}
}
export default AddNewEmployee;
The above code is working fine so far, but i got a requirement to keep an nested object to save the form input values.
But i'm missing some thing here how to handle the user input on form elements.
How shall we handle the user input when we have a nested object.
import React from 'react';
import Links from './Links.jsx';
import axios from 'axios';
import Dialog from 'react-bootstrap-dialog';
import {Typeahead} from 'react-bootstrap-typeahead';
import Autocomplete from 'react-autocomplete';
import style from './app.css';
class AddNewEmployee extends React.Component {
constructor(props){
super(props);
this.state = {
employee : {
firstName : '',
middleName : ''
}
}
this.handleUserInput = this.handleUserInput.bind(this);
}
handleUserInput(){
// here how to handle those input changes
}
render() {
return(
<div className = "col-sm-6">
<input type="text"
placeholder="Enter First Name" name="firstName"
value={this.state.employee.firstName} onChange={(event) => this.handleUserInput(event)}>
</input>
</div>
<div className = "col-sm-6">
<input type="text"
placeholder="Enter First Name" name="middleName"
value={this.state.employee.middleName} onChange={(event) => this.handleUserInput(event)}>
</input>
</div>
)
}
}
export default AddNewEmployee;
handleUserInput(e) {
// Option #1 (mutable data)
let {employee} = this.state;
const name = e.target.name;
const value = e.target.value;
employee[name] = value;
this.setState({
employee
});
// Option #2 (immutable data)
const { employee } = this.state;
const name = e.target.name;
const value = e.target.value;
this.setState({
employee: {
...employee,
[name] : value
}
});
}
Also, since you are binding handleUserInput in the constructor, you can simplify onChange={(event) => this.handleUserInput(event)} to onChange={this.handleUserInput}

Angular 2 - Form validation for warnings/hints

I'm trying to add form validations which don't invalidate the form. The validation should only appear as warnings.
E.g. an age validation. An age greater than 90 shows a warning and an age greater than 120 shows an error.
I've tried it with two FormGroups on a form and two [formControl]s on the input field. Only the first [formControl] is used.
Is it possible to use Angulars form validation for this kind of validation? Which approach is the way to go?
I have done it by creating custom validator, which always return null. Also this validator creates additional property warnings. Then just simple check this property from your view.
export interface AbstractControlWarn extends AbstractControl { warnings: any; }
export function tooBigAgeWarning(c: AbstractControlWarn) {
if (!c.value) { return null; }
let val = +c.value;
c.warnings = val > 90 && val <= 120 ? { tooBigAge: {val} } : null;
return null;
}
export function impossibleAgeValidator(c: AbstractControl) {
if (tooBigAgeWarning(c) !== null) { return null; }
let val = +c.value;
return val > 120 ? { impossibleAge: {val} } : null;
}
#Component({
selector: 'my-app',
template: `
<div [formGroup]="form">
Age: <input formControlName="age"/>
<div *ngIf="age.errors?.required" [hidden]="age.pristine">
Error! Age is required
</div>
<div *ngIf="age.errors?.impossibleAge" [hidden]="age.pristine">
Error! Age is greater then 120
</div>
<div *ngIf="age.warnings?.tooBigAge" [hidden]="age.pristine">
Warning! Age is greater then 90
</div>
<p><button type=button [disabled]="!form.valid">Send</button>
</div>
`,
})
export class App {
age: FormControl;
constructor(private _fb: FormBuilder) { }
ngOnInit() {
this.form = this._fb.group({
age: ['', [
Validators.required,
tooBigAgeWarning,
impossibleAgeValidator]]
})
this.age = this.form.get("age");
}
}
Example: https://plnkr.co/edit/me0pHePkcM5xPQ7nzJwZ?p=preview
The accepted answer from Sergey Voronezhskiy works perfect in development mode but if you try build in --prod mode you will get this error.
... Property 'warnings' does not exist on type 'FormControl'.
In order to fix this error I did adjustments to the original code (App class), basically to fix loosely typed variables. This is the new version:
export class FormControlWarn extends FormControl { warnings: any; }
export function tooBigAgeWarning(c: FormControlWarn ) {
if (!c.value) { return null; }
let val = +c.value;
c.warnings = val > 90 && val <= 120 ? { tooBigAge: {val} } : null;
return null;
}
export function impossibleAgeValidator(c: AbstractControl) {
if (tooBigAgeWarning(c) !== null) { return null; }
let val = +c.value;
return val > 120 ? { impossibleAge: {val} } : null;
}
#Component({
selector: 'my-app',
template: `
<div [formGroup]="form">
Age: <input formControlName="age"/>
<div *ngIf="age.errors?.required" [hidden]="age.pristine">
Error! Age is required
</div>
<div *ngIf="age.errors?.impossibleAge" [hidden]="age.pristine">
Error! Age is greater then 120
</div>
<div *ngIf="age.warnings?.tooBigAge" [hidden]="age.pristine">
Warning! Age is greater then 90
</div>
<p><button type=button [disabled]="!form.valid">Send</button>
</div>
`,
})
export class App {
get age(): FormControlWarn{
return <FormControlWarn>this.form.get("age");
}
constructor(private _fb: FormBuilder) { }
ngOnInit() {
this.form = this._fb.group({
age: new FormControlWarn('', [
Validators.required,
tooBigAgeWarning,
impossibleAgeValidator])
});
}
}
This is probably how I would have done it.
<form #form="ngForm" (ngSubmit)="save()">
<input formControlName="controlName">
<span *ngIf="form.pristine && form.controls.controlName.value > 90 && form.controls.controlName.value < 120">
Warning: Age limit is high..
</span>
<span *ngIf="form.pristine && form.controls.controlName.value > 120">
Error: Age limit exceeded..
</span>
<form>
Ok
its can be easy by angular.io for form validation hinting you can read documents on https://angular.io/docs/ts/latest/cookbook/form-validation.html
but the similar way that can help you better may be in my mind.
first we create a abstract class named Form it contains some common function and properties.
import {FormGroup} from "#angular/forms";
export abstract class Form {
form: FormGroup;
protected abstract formErrors: Object;
protected abstract validationMessages: Object;
onValueChanged(data?: any) {
if (!this.form) { return; }
const form = this.form;
for (const field in this.formErrors) {
this.formErrors[field] = '';
const control = form.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] = messages[key];
break;
}
}
}
}
}
then you should to create a form component for example named LoginComponent like below
import {Component, OnInit} from "#angular/core";
import {Form} from "./form";
import {Validators, FormBuilder} from "#angular/forms";
#Component({
templateUrl: '...'
})
export class LoginComponent extends Form implements OnInit {
protected formErrors = {
'email': '',
'password': ''
}
protected validationMessages = {
'email': {
'required': 'email required message',
'email': 'email validation message'
},
'password': {
'required': 'password required message',
'minlength': 'password min length message',
'maxlength': 'password max length message',
}
}
constructor(private _fb: FormBuilder) { }
ngOnInit() {
this.buildForm();
}
buildForm() {
this.form = this._fb.group({
'email': ['', [
Validators.required,
// emailValidator
]],
'password': ['', [
Validators.required,
Validators.minLength(8),
Validators.maxLength(30)
]]
});
this.form.valueChanges
.subscribe(data => this.onValueChanged(data));
this.onValueChanged(); //
}
}
first we should inject FormBuilder for reactive forms (do not forgot to import ReactiveFormModule in your main module) and then in [buildForm()] method we build a group of form on form property that was inherited from abstract class Form.
then in next we create a subscribe for form value changes and on value change we call [onValueChanged()] method.
in [onValueChanged()] method we check the fields of form has valid or not, if not we get the message from protected validationMessages property and show it in formErrors property.
then your template should be similar this
<div class="col-md-4 col-md-offset-4">
<form [formGroup]="form" novalidate>
<div class="form-group">
<label class="control-label" for="email">email</label>
<input type="email" formControlName="email" id="email" class="form-control" required>
<div class="help help-block" *ngIf="formErrors.email">
<p>{{ formErrors.email }}</p>
</div>
</div>
<div class="form-group">
<label class="control-label" for="password">password</label>
<input type="password" formControlName="password" id="password" class="form-control" required>
<div class="help help-block" *ngIf="formErrors.password">
<p>{{ formErrors.password }}</p>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-block btn-primary" [disabled]="!form.valid">login</button>
</div>
</form>
</div>
the template is so easy, inner you check the field has error or not if has the error bind.
for bootstrap you can do something else like below
<div class="form-group" [ngClass]="{'has-error': form.controls['email'].dirty && !form.controls['email'].valid, 'has-success': form.controls['email'].valid}">
<label class="control-label" for="email">email</label>
<input type="email"
formControlName="email"
id="email"
class="form-control"
required>
<div class="help help-block" *ngIf="formErrors.email">
<p>{{ formErrors.email }}</p>
</div>
</div>
UPDATE: I attempt to create a very simple but almost complete sample for you certainly you can develop it for wilder scale :
https://embed.plnkr.co/ExRUOtSrJV9VQfsRfkkJ/
but a little describe for that, you can create a custom validations like below
import {ValidatorFn, AbstractControl} from '#angular/forms';
function isEmptyInputValue(value: any) {
return value == null || typeof value === 'string' && value.length === 0;
}
export class MQValidators {
static age(max: number, validatorName: string = 'age'): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} => {
if (isEmptyInputValue(control.value)) return null;
const value = typeof control.value == 'number' ? control.value : parseInt(control.value);
if (isNaN(value)) return null;
if (value <= max) return null;
let result = {};
result[validatorName] = {valid: false};
return result;
}
}
}
this custom validator get a optional param named validatorName, this param cause you designate multi similar validation as in your example the formComponent should be like below :
buildForm () {
this.form = this._fb.group({
'age': ['', [
Validators.required,
Validators.pattern('[0-9]*'),
MQValidators.age(90, 'abnormalAge'),
MQValidators.age(120, 'incredibleAge')
]]
});
this.form.valueChanges
.subscribe(data => this.onValueChanged(data));
this.onValueChanged();
}
onValueChanged(data?: any): void {
if (!this.form) { return; }
const form = this.form;
for (const field in this.formErrors) {
this.formErrors[field] = '';
const control = form.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] = messages[key];
}
}
}
}
formErrors = {
age: ''
}
validationMessages = {
'age': {
'required': 'age is required.',
'pattern': 'age should be integer.',
'abnormalAge': 'age higher than 90 is abnormal !!!',
'incredibleAge': 'age higher than 120 is incredible !!!'
}
}
Hope I helped.