Angular, passing parameters in validation model driven - angularjs

so here's my trouble, I'm validating Emails, and I'd like to be a bit permissive (depending on rather the email should be absolutely perfect or not), to do so I use a custom validator with a parameter withsuspicious (that'll trigger the exclusion of suspicious emails) so here's the code I have:
Component :
FormEmailAddress : FormGroup;
constructor(private fb: FormBuilder) { }
ngOnInit(){
this.FormEmailAddress = this.fb.group({
emailAddress: ['', Email.validateEmail]
});
}
Validator :
static validateEmail(control, withsuspicious) {
if (Validators.required(control)!== undefined && Validators.required(control)!== null) return null;
return Email.checkValidity(control.value, withsuspicious)? null : { pattern: {invalid: true}}
}
Email.checkValidity is just a function that validates email by returning true if good, false if not (with regexp and so on)
View
<form [formGroup]="FormEmailAddress" novalidate>
<h4>Email Model Driven</h4>
<input type="text" formControlName="emailAddress"/>
<p>Email : {{FormEmailAddress.get('emailAddress').value}}</p>
<p *ngIf="!FormEmailAddress.valid"> Wrong Email </p>
</form>
So I'd like to find a way to pass, as well as my "emailAddr" the parameter withsuspicious into my validator can you help me plz? Thanks

Finally found how to do it, didn't change anything in view or validator, juste did this on component :
this.FormEmailAddress = this.fb.group({
emailAddress: [''],
withsuspicious: [true]
}, {validator : this.validateEmail('emailAddress', 'withsuspicious')});
validateEmail(emailAddressKey, withsuspiciousKey){
return(group : FormGroup) => {
let emailInput = group.controls[emailAddressKey]
let withsuspiciousInput : boolean = group.controls[withsuspiciousKey].value
return Email.validateEmail(emailInput, withsuspiciousInput)
}
}

Related

Vue input tag element - how to fill it with tags properly?

I am using vue element <input-tag>, and I need to make edit page for one entity which contains tags. So the <input-tag> field should be populated based on the current values ​​in the entity.
I managed to do that, but the whole JSON object appears in <input-tag> field (example: {"id":2, "tagName": "vuejsproblem", "newsId": 1}), not only tagName as it should be.
Also, in console I got error, I don't know why, because this.tags is obviously not "":
[Vue warn]: Invalid prop: type check failed for prop "value". Expected Array, got String with value "".
found in
---> <InputTag>
This is code of my vue page:
<template>
<div class="pt-5">
<form #submit.prevent="editNews">
<div id="app">
<label for="tags">Tags</label>
<input-tag placeholder="Add Tag" v-model.trim="tags"></input-tag>
<br>
</div>
<button type="submit" class="btn btn-primary mt-2">Submit</button>
</form>
</div>
</template>
<script>
export default {
name: "EditNews",
data() {
return {
tags: '',
}
},
beforeMount () {
this.$axios.get(`/api/news/${this.$route.params.id}`,{
id: this.$route.params.id,
}).then((response) => {
this.tags = response.data.tags;
});
/* this.tags.forEach(element => element.tagName);
*/
},
methods: {
editNews() {
this.message = '';
if(this.tags.length > 1) {
console.log(this.tags)
this.$axios.post(`/api/news/${this.$route.params.id}`, {
tags: this.tags,
}).then(
data => {
this.message = data.message;
},
error => {
this.message = error.response.data
alert(this.message);
});
}
},
},
}
</script>
<style scoped>
</style>
As it is said here, tags must be Array. So,
define tags as Array in data() like:
data() {
return {
tags: [],
}
}
Also response.data.tags must be Array something like [ "Jerry", "Kramer", "Elaine", "George" ] as here.
You can convert response.data.tags to Array which contains only tagName by doing this: this.tags = response.data.tags.map(x => x.tagName)
Based on what I could understand from your question (if I got it right), you can do that using the render helper from vuejs, depending on which version you're trying to achieve this v2 or v3
Back in v2 you could do something like:
https://github.com/vubular/elements/blob/master/src/text/Plain.vue
using the this._v you can bind directly the content from the slot of the component or you could wrap with any html tag before passing the content in for example this._v('<span>'+this.content+'</span>'), you can also define tag as prop and then render prop instead of hardcoded tags.
Meanwhile in v3 you can return the h helper imported from vue:
render() { return h('span', {}, this.transformedContent); },. Again you can accept the span/tag as prop in child component context.
When doing so you don't need to define a <template> for your component so that you can render the component using vue helpers (where it then handles the DOM interactions).

How can I set customvalidity to have a Link in it?

I have a customvalidity function for when a username or email are taken, and I want to give the user the option to go to signin from there. Is this possible?
I have tried creating an object for the Link object, from react-router dom, but it eiter doesn't come up when added with a comma, or comes back object object when inserted with a plus sign. I want the email taken notification to have the link in it so the user can click on login directly.
handleSubmit = async (e) => {
e.preventDefault();
const EmailField = document.getElementById('email');
const userField = document.getElementById('username');
const signinRedirect = <Link to='/signin'> login </Link>;
const newUser = {
username : this.state.username,
email : this.state.email,
password : this.state.password,
confirmPassword : this.state.confirmPassword,
};
//check passwords match before submitting
if (this.state.passwordsMatch) {
let url = window.location.hostname.split('.')[0] === 'localhost' ? 'http://localhost:3306' : 'https://petsosbackend.apps.nonprod-mpn.ro11.allstate.com';
try {
await axios.post(url + '/register', newUser)
.then(res=> {
if (res.status===202) {
this.props.history.push('/registersuccess');
//if email is taken
} else if (res.data==='email'){
EmailField.setCustomValidity('Email address is in use, please select another or '+ {signinRedirect});
EmailField.reportValidity();
//if name is taken
} else if (res.data==='name') {
userField.setCustomValidity('Username is in use, please select another or' + signinRedirect);
userField.reportValidity();
}
})
} catch(error) {
console.log("Catch = ", error.response);
}
}
}
There is more logic obviously but think these are the two key parts and just need help figuring out how to have the signinRedirect appear in the validity warning.
Thanks a lot!
Principle:
return (
<div className="form-control">
<input type="text" name="email" value={email} onChange={handleChange} />
{isEmailInUse && (
<div className="error">
Email address is in use, please select another or <Link to='/signin'>Sign in</Link>
</div>
)}
</div>
);
You are trying to use native messages in a JSX validation method. So you can not do.
JSX has nothing to do with HTML, besides being a way of describing the structure of a document and a similar syntax.
JSX In Depth
Just do not use native error reporting. Use your own solutions, like the ones I gave in the answer.
You are also working incorrectly with DOM elements. In React, it is common practice to use Ref API or monitored components.
And whenever possible, there should be no conditions in the code like:
window.location.hostname.split('.')[0] === 'localhost'
For such tasks there are env variables and configurations. You can set BASE_URL and use different values for different modes.

Angular Reactive Forms - Read / Set value from array

I have 3 files:
A service that collects my data from firestore and builds a form
A component.ts file
A component.html file
For most of my inputs I have the form correctly reading the data from the service / form builder. The area I'm having difficulty is using the Angular Material Select (dropdown) snippet.
In my component.ts file I have an array of two options for the user to select from. Once selected, that option will be saved as a string to firestore.
As you can see below, currently my HTML is currently reading from an array from its associated component.ts file. Ideally I'd like it to display the current value from firestore with the two available options available when clicking on it.
How do I provide those two options in the HTML as well as displaying the currently set value from firestore?
Service
export interface IUserSettings {
usersetting_dateformat: string;
}
#Injectable()
export class SettingsService {
// Database
private settingsDocRef: AngularFirestoreDocument<any>;
settingsDocument: Observable<any>;
// User
userID: string;
// Form
editForm = new FormGroup({});
// User Settings Behaviour Subject
userSettings$: BehaviorSubject<IUserSettings>;
constructor(
private readonly afs: AngularFirestore, fb: FormBuilder) {
this.editForm = fb.group({
usersetting_dateformat: [''],
});
this.userSettings$ = new BehaviorSubject({
dateformat: 'DD/MM/YYYY',
});
}
getSettingsData() {
this.settingsDocRef = this.afs.doc(`settings/${this.userID}`);
this.settingsDocument = this.settingsDocRef.snapshotChanges();
this.settingsDocument.subscribe(value => {
const userSettings = value.payload.data() as IUserSettings;
this.userSettings$.next(userSettings);
this.editForm.patchValue(this.userSettings$.getValue());
});
}
}
Component.ts
...
dateFormats = ["DD/MM/YYYY", "MM/DD/YYYY"];
...
Component.html
<form [formGroup]="settingsService.editForm">
<mat-select placeholder="Date Format">
<mat-option *ngFor="let dateFormat of dateFormats" [value]="dateFormat"> {{dateFormat}} </mat-option>
</mat-select>
</form>
Not sure why you're unnecessarily complicating it with BehaviourSubject when you're already getting the value and then you're calling the patchValue method on the form.
You can simplify the code by removing the use of the BehaviorSubject like this:
export interface UserSettings {
dateFormat: string;
}
#Injectable()
export class SettingsService {
// Database
private settingsDocRef: AngularFirestoreDocument<any>;
settingsDocument: Observable<any>;
// User
userID: string;
// Form
editForm = new FormGroup({});
constructor(
private readonly afs: AngularFirestore,
fb: FormBuilder
) {
this.editForm = fb.group({
dateFormat: [],
});
}
getSettingsData() {
this.settingsDocRef = this.afs.doc(`settings/${this.userID}`);
this.settingsDocument = this.settingsDocRef.snapshotChanges();
this.settingsDocument.subscribe(value => {
const userSettings = value.payload.data() as UserSettings;
this.editForm.patchValue(userSettings);
});
}
}
And then you just need to add a formControlName to your template.
<form [formGroup]="settingsService.editForm">
<mat-select placeholder="Date Format" formControlName="dateFormat">
<mat-option *ngFor="let dateFormat of dateFormats" [value]="dateFormat">
{{dateFormat}}
</mat-option>
</mat-select>
</form>
This should fix your issue. Here's a Sample StackBlitz for your ref. Doesn't have the same code as in your eg. But should give you a context.
TL;DR; You just need the FormControl initialized with the value and then add a formControlName attribute to your Form Template to make this work.

How to bind checkboxes to Vuex store?

I have a component that contains some checkboxes. I need to be able to access which checkboxes are checked from other components in my Vue application, but I cannot for the life of me figure out (nor find online) how to properly connect the checkboxes to my Vuex store.
What is the right way to connect checkboxes within a component to a Vuex store, so that they act just as if the checkbox was connected to the components data via v-model?
Here is a starting point for what I'm trying to do (in a very very basic sense)
https://jsfiddle.net/9fpuctnL/
<div id="colour-selection">
<colour-checkboxes></colour-checkboxes>
</div>
<template id="colour-checkboxes-template">
<div class="colours">
<label>
<input type="checkbox" value="green" v-model="colours"> Green
</label>
<label>
<input type="checkbox" value="red" v-model="colours"> Red
</label>
<label>
<input type="checkbox" value="blue" v-model="colours"> Blue
</label>
<label>
<input type="checkbox" value="purple" v-model="colours"> Purple
</label>
<chosen-colours></chosen-colours>
</div>
</template>
<template id="chosen-colours-template">
<div class="selected-colours">
{{ colours }}
</div>
</template>
const store = new Vuex.Store({
state: {
colours: []
}
});
Vue.component('colour-checkboxes', {
template: "#colour-checkboxes-template",
data: function() {
return {
colours: []
}
}
});
Vue.component('chosen-colours', {
template: "#chosen-colours-template",
computed: {
colours() {
return store.state.colours
}
}
});
const KeepTalkingSolver = new Vue({
el: "#colour-selection"
});
The aim is to get the colours that are selected in the colour-checkboxes component to output in the chosen-colours component, going through the Vuex store.
You can use computed property with getter as vuex getter and setter in computed property which will call a mutation for that state property to do this.
You can see an example of this here with two-way Computed Property:
<input v-model="message">
// ...
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
I wanted to provide an answer that actually uses checkboxes.
There is one possible solution outlined here:
Vuex dynamic checkboxes binding
And a simpler solution can be achieved something like the following:
<div v-for="tenant in tenants"
v-bind:key="tenant.id"
class="form-group form-check">
<input type="checkbox"
class="form-check-input"
v-bind:id="tenant.id"
v-bind:value="tenant.name"
#change="updateSelectedTenants">
Key here is calling a method using on-change, it will pass an event to the method with all the details needed to make the change.
The #change function:
updateSelectedTenants(e) {
console.log('e', e.target)
console.log('e', e.target.value)
this.$store.dispatch('updateSelectedTenants', e.target)
}
Here I want the value, in this case will be the tenants name, but further inspection of the target also gives the 'id', and whether or not the checkbox is 'checked' or unchecked.
Over in the store, we can manipulate the 'selectedTenants' array:
updateSelectedTenants (context, tenant) {
if(tenant.checked) {
// Tenant checked, so we want to add this tenant to our list of 'selectedTenants'
context.commit('addSelectedTenant', { id: tenant.id, name: tenant.value })
} else {
// otherwise, remove the tenant from our list
context.commit('removeSelectedTenant', tenant.id)
}
}
Here are the actual mutators:
addSelectedTenant (state, tenant) {
this.state.selectedTenants.push(tenant)
},
removeSelectedTenant (state, id) {
this.state.selectedTenants = this.state.selectedTenants.filter(tenant => {
return tenant.id != id
})
The vuejs docs are great, but sometimes they can be a little light on with real world examples. I don't think it's possible to achieve the above using a computed value, with get(), set()... but I'd like to see a solution that can.
OK I have been challenged to show my solution. Here it is on jsfiddle
the html is:
<div id="app">
<label v-for="brother in ['Harpo','Groucho','Beppo']">
<input type='checkbox' v-model='theBrothers' v-bind:value='brother' />
{{ brother }}
</label>
<div>
You have checked: {{ theBrothers }}
</div>
</div>
and the js is:
const store = new Vuex.Store({
state: {
theBrothers: []
},
})
new Vue({
el: "#app",
store: store,
computed: {
theBrothers: {
set(val){this.$store.state.theBrothers = val},
get(){ return this.$store.state.theBrothers }
}
},
})
2021 - easy, readable, & taking advantage of the power of Vue/Vuex...
There are lots of complicated answers for a simple problem. Run the snippet below to see it in action.
Here is a working solution that solves all of the issues described below:
const store = new Vuex.Store({
state: {
names: ['Max'],
},
mutations: {
setNames(state, names) {
state.names = names;
}
}
});
new Vue({
el: '#app',
store,
computed: {
selectedNames: {
get: function() {
return this.$store.state.names;
},
set: function(val) {
console.log(val);
this.$store.commit('setNames', val);
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vuex#3.6.2/dist/vuex.js"></script>
<div id="app">
<div>
<input type="checkbox" v-model="selectedNames" :value="'John'" id="checkbox-1" />
<label for="checkbox-1">Click me to add my value to the state</label>
<br />
<input type="checkbox" v-model="selectedNames" :value="'Max'" id="checkbox-2" />
<label for="checkbox-2">I am preselected since my value already exists in the state <code>names</code> array</label>
<div>State: <strong>{{ names }}</strong></div>
</div>
</div>
Long story short all you need to do is take a piece of state (names below), create a mutation (setNames below) to set it, and then bind the v-model to a computed (selectedNames below) that has a getter and setter, the getter gets the piece of state names, and the setter calls the mutation setNames.
In my opinion this is the cleanest solution to this problem because it follows the natural pattern of Vue/Vuex and how checkboxes are typically implemented.
Other answers in here attempt to mutate the state directly without mutations, while some other answers avoid using a v-model which presents issues with having a preselected value and requires much more code, and finally the accepted answer doesn't even show any HTML template code on how to implement it.
Use #change to update Vuex as needed:
HTML:
<input
v-for='item in items'
#change='update_checkboxes'
v-model='selected_checkboxes'
:value='item.id'
type='checkbox
/>
<label>{{item.name}}</label>
JS:
data: function(){
return {
selected_checkboxes: [] // or set initial state from Vuex with a prop passed in
}
},
methods: {
update_checkboxes: function(){
this.$store.commit('update_checkboxes', this.selected_checkboxes)
}
}
Based on the solution from #Saurabh - it is important to use actions and getters rather than directly accessing vuex state - this will ensure consistency throughout the application.
<p>Mega test: <input type="checkbox" v-model="mega" /></p>
computed: {
mega: {
get () {
return this.$store.getters.mega
},
set (value) {
this.$store.dispatch('updateMega', value)
}
}
}
const store = new Vuex.Store({
state: {
mega: false
},
getters: {
mega (state) {
return state.mega
}
},
mutations: {
updateMega (state, value) {
state.mega = value
}
},
actions: {
updateMega (context, value) {
context.commit('updateMega', value)
}
}
})
You shoud remove colours = [] in data.

Get element sibling value in React

I have this method inside a React component (which I later pass to the render() method):
renderNutrientInputs: function (num) {
var inputs = [];
for (var i =0; i < num; i++) {
inputs.push(<div key={i}>
<label>Nutrient name: </label><input type="text"/>
<label>Nutrient value: </label><input type="text" />
</div>);
}
return inputs;
}
I'm trying on each change of the "Nutrient value" textbox, to also grab the current value of the "Nutrient name" textbox. I first though of assigning "ref" to both of them, but I figured there might be multiple pairs of them on the page (and the only way to identify them would be by key). I also tried something like this:
<label>Nutrient name: </label><input type="text" ref="nutName"/>
<label>Nutrient value: </label><input type="text" onChange={this.handleNutrientValueChange.bind(null, ReactDOM.findDOMNode(this.refs.nutName))}/>
but got a warning from React:
Warning: AddForm is accessing getDOMNode or findDOMNode inside its
render(). render() should be a pure function of props and state. It
should never access something that requires stale data from the
previous render
Is there some way to attach onChange event listener to Nutrient value text box and access the current value of "Nutrient name" textbox in the event listener function?
You don't want to access DOM elements directly. There is no need to do so... Work with your data, forget about DOM!
What you want is to "listen to changes to n-th nutritient. I want to know it's name and it's value". You will need to store that data somewhere, let's say in state in this example.
Implement getInitialState method. Let's begin with empty array, let user to add nutritients.
getInitialState() {
return { nutritients: [] };
},
In render method, let user add nutrition by click on "+", let's say
addNutritient() {
const nutritients = this.state.nutritients.concat();
nutritients.push({ name: "", value: undefined });
this.setState({ nutritients });
},
render() {
return (
<div>
<div onClick={this.addNutritient}>+</div>
</div>
)
}
Okay, let's focus on rendering and updating nutritients:
addNutritient() {
const nutritients = this.state.nutritients.concat();
nutritients.push({ name: "", value: undefined });
this.setState({ nutritients });
},
renderNutritients() {
const linkNutritient = (idx, prop) => {
return {
value: this.state.nutritients[idx][prop],
requestChange: (value) {
const nutritients = this.state.nutritients.concat();
nutritients[idx][prop] = value;
this.setState({ nutritients });
},
}
};
const nutritients = [];
return (
this.state.nutritients.map((props, idx) => (
<div>
<input valueLink={linkNutritient(idx, "name")} />
<input valueLink={linkNutritient(idx, "value")} />
</div>
))
)
},
render() {
return (
<div>
{ this.renderNutritients() }
<div onClick={this.addNutritient}>+</div>
</div>
)
}
Coding by hand, sorry for syntax error or typings.
Edit:
Take a look at this working Fiddle https://jsfiddle.net/Lfrk2932/
Play with it, it will help you to understand what's going on.
Also, take a look at React docs, especialy "valueLink" https://facebook.github.io/react/docs/two-way-binding-helpers.html#reactlink-without-linkedstatemixin
I prefer not to use 2 way binding with React which is kind of a flux anti-pattern. Just add a onChange listener to your input element and setState.
Your state will be something like this:
{0: {nutrientName: xyz, nutrientValue: 123},
1: {nutrientName: abc, nutrientValue: 456}}
So when you change the nutrientvalue 456 to say 654, you can say its corresponding name is abc and vice versa.
The whole thing about React is about handling the data not the DOM :)

Resources