Vue.js 3 v-model on form element got undefined type error on missing data from REST Call - rest

Coming from Svelte and being new to Vue.js 3, I encounter a situation that I found a little bit confusing. This is a quite simple scenario that you will get into all the time when dealing with REST call on no-sql backend... partial data set. Let me explain with this simple example:
<template>
<div v-if="info">
<form>
<input type="text" v-model="info.firstname">
<input type="text" v-model="info.lastname">
<input type="text" v-model="info.address.street">
<input type="text" v-model="info.address.city">
<input type="text" v-model="info.address.postalCode">
<button type="submit">Submit</button>
</form>
</div>
</template>
<script>
export default {
created() {
//Imagine a rest call here...
this.info = {
firstname: "Micheal",
lastname: "Jordan",
}
},
data() {
return {
info: {
firstname: '',
lastname: '',
address: {
city: '',
street: '',
postalCode: ''
},
}
}
}
}
</script>
This will get you a " Uncaught (in promise) TypeError: $data.info.address is undefined " because of the missing address block. But functionally speaking, the address is optional and will not be return if use MongoDB or DynamoDB backend. And has I said at the beginning, in Svelte, I simply apply a spread operator on my info object and the "missing" data is no concern. If I add the missing address section in my "simulated" API call, everythings fine.
What am I missing here?! (remember I'm starting to learn Vue here ;-)

Well... I don't why I didn't think of it faster...
....
this.info = {...this.info,
firstname: "Micheal",
lastname: "Jordan",
}
....
Simply use the spread operator! :-)

Related

How to set validation for UNIQUE fields in MongoDB, Vuejs?

I'm new to MongoDB and Vuejs. I'm not sure if it is possible that I was trying to set validation for Unique value, but somehow it does not work.
My defined schema from back-end:
const uuid = require("uuid");
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
let informationSchema = new Schema ({
_id: {type: String, default: uuid.v1},
firstName: {
type: String, required:true, trim: true, unique: true
},
lastName: {
type: String, required:true, trim: true, unique: true
}
});
module.exports = mongoose.model('informations', informationSchema)
My action form in Vuejs from Front-end:
<template>
<div class="row justify-content-center">
<div class="col-md-6">
<h3 class="text-center">Create Name</h3>
<form #submit.prevent="handleSubmitForm">
<div class="form-group">
<label>First Name</label>
<input type="text" class="form-control" v-model="name.firstName" required>
</div>
<div class="form-group">
<label>Last Name</label>
<input type="text" class="form-control" v-model="name.lastName" required>
</div>
<button class="btn btn-danger mt-3">Create</button>
</form>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
name: {
firstName: '',
lastName: ''
}
}
},
methods: {
handleSubmitForm() {
let apiURL = 'http://localhost:3000/name';
axios.post(apiURL, this.student).then(() => {
console.log(res)
}).catch(error => {
console.log(error)
});
}
}
}
</script>
My 1st inserted data: [{"firstName":"ABC", "lastName": "DCF"}]
I want to insert the 2nd one with data : firstName = "ABC", but last name = "XYZ". The error for validation occured because of validation (Unique) set in the defined schema from back-end).
My expected output:
Is there any possible ways to do:
If I insert the 1st data, it will work.
If I insert the 2nd data same as the 1st data, the error for validation will occur.
If I insert the 3rd data that is different from lastName only, it will work.
Thank you so much.
First of all, Mongo and Vue are completly separated, there is no relation.
Answering your question, the validation fail because you are telling mongo "Set firstName and lastName as unique fields". But not "Set these fields unique TOGETHER". So when you add a firstname, the second attemp try to add an existing firstName and fails.
So you can create a unique index:
informationSchema.index({ firstName: 1, lastName: 1 }, { unique: true });
And remove unique constraint in the schema.

Formdata post with vuex

I am very new to vuex and vue-js. I am trying to post my forms data using vuex. So far I managed to pass the Title successfully but I don’t know how to pass more than one piece of data?
data to pass:
// Title
<input type="text" name="subject_title">`
// selected data
<datepicker id="set_date" name="set_date"></datepicker>
// array of options
<multiselect v-model="value" :max-height="0" :options="options" :searchable="false" :multiple="true" group-label="language" track-by="name" label="name" placeholder=""><span class="arrow" slot="caret"></span></multiselect>
I have put some of my code on github: https://github.com/samB67/VuexPost
You can create a object to store your whole form in your file upload.vue;
data() {
//...
homeworkForm: {
title: '',
date: null,
somethingElse: ''
}
}
Now in your form, you simply put the v-model to bind to each corresponding keys from your homeworkForm object;
<input v-model="homeworkForm.title" type="text" class="form-control" id="formGroupExampleInput" placeholder="Enter text">
<!-- ... -->
<datepicker v-model="homeworkForm.date" id="set_date" name="set_date" />
Then when you want to submit the form, you send the whole homeworkForm object to vuex.
postHandler() {
this.$store.dispatch('createHomework', this.homeworkForm);
},

How to force Angular 2 to re-check validator?

I have a simple login form written in Angular 2 reactive (data-driven) template. It's working perfectly but when I refresh the page and browser fills e-mail+password (autocomplete), my form's valid property seems false.
But when I press any key or click anywhere in my page while form is invalid, angular updates some states (I guess) and my form become valid.
How can I trigger that state? How can I say angular "Hey, check my form again."? I can't trigger my own validation script because some validation messages are alerts. If I do this, when user open this page, he/she will see these alerts.
I remember, I use trigger('input') trick to update ng-model.
updateValueAndValidity() is what you are looking for. The document is here: AbstractControl. It can force recalculate the value and validation status of the control.
Here's a demo:
form.component.ts
export class FormComponent implements OnInit {
myform: FormGroup;
// explicit validation of each field
emailValid: AbstractControl;
passwordValid: AbstractControl;
constructor(private fb: FormBuilder) {
this.myform = fb.group({
'email': ['', Validators.required],
'password': ['', Validators.required],
});
this.emailValid = this.myform.controls['email'];
this.passwordValid = this.myform.controls['password'];
}
ngOnInit() {
this.myform.get('email').valueChanges.subscribe(()=>forceValidAgain());
this.myform.get('password').valueChanges.subscribe(()=>forceValidAgain());
}
forceValidAgain(){
this.emailValid.updateValueAndValidity();
this.passwordValid.updateValueAndValidity();
}
}
form.component.html
<form [formGroup]="myform" (ngSubmit)="onSubmit(myform.value)">
<div class="form-group">
<label for="email">Email</label>
<input type="email"
class="form-control"
id="email"
name="email"
[formControl]="myform.controls['email']"
[ngClass]="{'is-invalid': !emailValid.valid && emailValid.touched }">
<div
class="invalid-feedback"
*ngIf="emailValid.hasError('required')">
This field is required
</div>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password"
class="form-control"
id="password"
name="password"
[formControl]="myform.controls['password']"
[ngClass]="{'is-invalid': !passwordValid.valid && passwordValid.touched}">
<div
class="invalid-feedback"
*ngIf="passwordValid.hasError('required')">
This field is required
</div>
</div>
</form>
I would suggest creating a method like onValueChanged (that is mentioned in Angular2 docs), bind it to your form, and execute it while the component is initialized. So the binding to the form changes should be done in this way:
this.form.valueChanges.subscribe((data) => {
this.onValueChanged(data)
});
And execution for example just the line after like this:
this.onValueChanged();
This execution should solve your problem via validation during component initialization.
I think that the method implementation from the docs (below) is pretty clear.
onValueChanged(data?: any) {
if (!this.heroForm) { return; }
const form = this.heroForm;
for (const field in this.formErrors) {
// clear previous error message (if any)
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] + ' ';
}
}
}
}
Docs I am referring to: https://angular.io/docs/ts/latest/cookbook/form-validation.html

VueJS with Large Forms

I have a huge form with 20+ fields. i feel so much redundancy on the code i write now. What is the best way ?
<script>
new Vue({
data : {
user : {
first_name : "",
last_name : "",
username : "",
and 20+.........
}
}
});
</script>
<form>
<input name="first_name" v-model="first_name">
<input name="last_name" v-model="last_name">
<input name="username" v-model="username">
and 20+......... input fields
</form>
i feel something like this would be nice. the user object will be created dynamically.. is this possible ?
<script>
new Vue({
data : {
user : Object
}
});
</script>
<form v-model="user">
<input name="first_name">
<input name="last_name">
<input name="username">
and 20+......... input fields
</form>
Thank you in advance
Completely Redone in Vue 2
Your approach is the reverse of the usual Vue approach, in that you want to lay out your data structure in the view and have Vue pick it up, rather than laying it out in the data and having Vue render it. I can see how that would be desirable if you have a customized layout you want to achieve.
Unconventional needs require unconventional solutions, so this approach is unconventional. In particular, it is not generally recommended that a child component modify data in a parent.
That said, the approach is to create a component that will accept the form inputs as a slot and the parent object as a prop. In mounted, it gets all the input fields with name attributes and
Creates the member in the parent object, using $set
Sets a watch on the newly-created member
Adds an input event listener to complete the two-way binding
You would probably want to add more props to the component, to make the form itself more versatile, but this gives you the functionality you're looking for.
Vue.component('autoBindingForm', {
template: '<form><slot></slot></form>',
props: ['createIn'],
mounted() {
const inputs = this.$el.querySelectorAll('input[name]');
for (const i of inputs) {
this.$set(this.createIn, i.name, i.value);
this.$watch(`createIn.${i.name}`, (newVal, oldVal) => {
i.value = newVal;
});
i.addEventListener('input', (e) => {
this.createIn[i.name] = i.value;
});
}
}
});
const vm = new Vue({
el: '#app',
data: {
user: {}
}
});
// Testing that binding works both ways
setTimeout(() => {
vm.user.last_name = 'Set it';
}, 800);
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<auto-binding-form :create-in="user">
<input name="first_name" value="hi">
<input name="last_name">
<input name="username">
<div>{{Object.keys(user)}}</div>
<div>{{Object.values(user)}}</div>
</auto-binding-form>
</div>
How about
data: {
fields: { name: {v:''}, surname: {v:''}, ... }
}
and
<input v-for="(val, prop) in fields" :name="prop" v-model="val.v" />
?
https://jsfiddle.net/gurghet/dhdxqwjv/1/

Angular2 Forms - ngControl

I'm trying to use ngControl to apply error classes based on user's input.
Somehow, I can't make it to work. I see that appropriate classes are set (line ng-invalid), but when trying to use name.valid (where name is my ngControl) it doesn't work.
html:
<div ngClass="{alert: name.invalid}">
<label for="name">Name</label>
<input ngControl="name" #name id="name" [(ngModel)]="user.name"/>
</div>
</div>
js
export class App {
userForm: any;
user: any;
constructor(
private _formBuilder: FormBuilder) {
this.user = {name: 'Ben'};
this.userForm = this._formBuilder.group({
'name': ['', Validators.required]
});
}
}
I saw on angular.io examples that they do use it like this (just for other cases, like show/hide divs)?
Here's the simple plunker: http://plnkr.co/edit/BKx4yplIOu44tk7Mfolc?p=preview
When input field is empty, I would expect that upper div gets alert class, but that doesn't happen.
In fact there are three things to change in your template:
ngClass should be [ngClass]. Otherwise the value is considered as a string and not as an expression.
#name should be #name="ngForm". Otherwise you reference the DOM element and not the control.
there is no invalid property on controls in Angular2 but only a valid one.
Here is the refactored code:
<div [ngClass]="{alert: !name.valid}">
<label for="name">Name</label>
<input ngControl="name" #name="ngForm"
required id="name" [(ngModel)]="user.name"/>
</div>
Here is the plunkr: http://plnkr.co/edit/OJfb9VDqlrRH4oHXQJyg?p=preview.
Note that you can't leverage of FormBuilder with ngControl since the latter allows you to define inline form. With FormBuilder you must use ngFormControl instead.
Here is a sample:
<div [ngClass]="{alert: !userForm.controls.name.valid}">
<label for="name">Name</label>
<input [ngFormControl]="userForm.controls.name"
id="name" [(ngModel)]="user.name"/>
</div>
See this article for more details:
http://restlet.com/blog/2016/02/11/implementing-angular2-forms-beyond-basics-part-1/