What is sane way in vuejs + vuex form handling? - forms

I have a large forms to submit in single page.
<container>
<formA>
<formB>
<formC>
<submitButton>
<container>
it looks apparently like this. and I have a store which save every form data. then when user click submit button, I gather all form data using vuex store.
The problem is I need to update the form data in store everytime.
so I'll be like this in vue component
watch: {
userInput (val) {
this.updateState(val)
}
update state when input changes by watching form data(binded with v-model).
or like this which is documented in vuex doc.
userInput: {
get () {
return this.$store.state.userInput
},
set (val) {
this.updateState(val)
}
}
well.. I don't think these are good idea. Is there any better way to form handling with vuex?

I made a little tool which makes form handling wit Vuex a lot easier: vuex-map-fields
Example
Store
import Vue from 'vue';
import Vuex from 'vuex';
// Import the `getField` getter and the `updateField`
// mutation function from the `vuex-map-fields` module.
import { getField, updateField } from 'vuex-map-fields';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
fieldA: '',
fieldB: '',
},
getters: {
// Add the `getField` getter to the
// `getters` of your Vuex store instance.
getField,
},
mutations: {
// Add the `updateField` mutation to the
// `mutations` of your Vuex store instance.
updateField,
},
});
Component
<template>
<div id="app">
<input v-model="fieldA">
<input v-model="fieldB">
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields';
export default {
computed: {
// The `mapFields` function takes an array of
// field names and generates corresponding
// computed properties with getter and setter
// functions for accessing the Vuex store.
...mapFields([
'fieldA',
'fieldB',
]),
},
};
</script>
You can read more about vuex-map-fields on my blog: How to Handle Multi-row Forms with Vue, Vuex and vuex-map-fields

I would use deep watchers for this and have all fields in a object, you could use multiple approaches for saving the data, iterating over Object.keys to store each field with it's variable name in the form object, or storing the entire form, whatever you might need.
You could also use v-model.lazy="form.myfield" to indicate that you only want the binding to update once the user has left the field.
Form component
<template>
<div>
<!-- You can optionally use v-model.lazy="form.field1" to only update once user has exited the field or pressed enter -->
<input v-model="form.field1" />
<input v-model.lazy="form.field2" />
</div>
</template>
<script>
export default {
props: ['value'],
data: function () {
return {
internalForm: {
field1: null,
field2: null
}
}
},
watch: {
internalForm: {
handler: function (newValue) {
// Emit new form object to parent component so we can use v-model there
this.$emit('input', this.form)
// Or save form data
this.handleFormSave(this.form)
},
// Tell vue to do a deep watch of entire form object to watch child items in the object
deep: true
}
}
}
</script>
Parent component
<template>
<form-component v-model="forms.form1" />
<submit-button #click="saveAllFormData" />
</template>
<script>
export default {
data: function () {
return {
forms: {
form1: null // This will be updated when 'input' is emitted
}
}
},
watch: {
forms: {
handler: function (newValue) {
if (allFormsValid && readyToSave)
saveAllFormData(newValue);
},
deep: true
}
}
}
</script>

I had headache regarding this probem to.
Vuex doc describes that we need to update store for every field.
It's a loot of typing whatfor?
We make one solution that works.
It based on cloning store object to local one.
//We are passing (vuexstore) 'item' object from parent component:
//<common-item v-bind:item="item" ....
props: ['item'],
// create localItem - this is reactive object for vuex form
data: () => {
return {
localItem: null
}
},
// make clone on created event
created: function() {
this.localItem = this._clone(this.item)
},
// watch vuexstore 'item' for changes
watch: {
item: function(val) {
this.localItem = this._clone(this.item)
}
},
// map mutations and update store on event
methods: {
...mapMutations([
'editItem'
]),
updateItemHandler: function() {
this.editItem({ item: this._clone(this.localItem) })
},
_clone: function(o){
return JSON.parse(JSON.stringify(o))
}
},
Inside form use:
<input v-model="localItem.text" #keyup="updateItemHandler" type="text" class="form-control"></input>
I think this is only lack of vuex. There should be much shorter and built in solution.

Related

Handling form data in PreactJS

I am porting a react app to preact. Handling data from a form on submit throws a TypeError: this.createIssue is not a function at Object.createTestIssue [as click] error.
The code is a follows:
class IssueList extends Component {
state = { issues: [] };
createIssue(newIssue) {
const updatedIssues = this.state.issues.slice();
newIssue.id = this.state.issues.length + 1;
updatedIssues.push(newIssue);
this.setState({ issues: updatedIssues });
}
createTestIssue() {
const issue = {
status: 'New',
owner: 'Pieta',
created: new Date(),
title: 'Completion date should be optional'
};
this.createIssue(issue);
}
render(props, state) {
return (
<div>
<h1>Issue Tracker</h1>
<IssueFilter />
<hr />
<IssueTable issues={state.issues} />
<hr />
<IssueAdd createIssue={this.createIssue} />
<hr />
<button onClick={this.createTestIssue}>Add Test</button>
</div>
);
}
}
export default IssueList;
I have tried to create a constructor and setting the state from inside the constructor, however there is a ReferenceError: state is not defined at new IssueList.
I have looked at linkState module that preact recommends for forms, however, I have not been able to set it up correctly. Do I pass an empty object and an object with the data that I want to be added to my array; something like:
render({}, { state }) {
...
}
But that does not allow me to access the state. Any help is appreciated.
In preact props and state are passed into the render function, as you have shown.
render(props, state) {
...
}
This allows you to use destructuring#MOZDocs to
render({}, { state }) {
...
}
What you have ^^ would not be beneficial, and might hide state behind another object. What you should have is:
render({}, { ...state }) {
...
}
OR in my opinion the best solution assuming state = {key: 'test', value: 1}:
render({}, { key, value }) {
...
}
If you do not wish to use anything from props you can either use the empty object as shown above or just have props, but then not use it.

Dynamically changing the language of columns labels in VueJS

I implemented a multilanguage support for the website. Using VueJS and VueI18n. There are 3 pages - home, registers and messages. The problem is in messages, where there is a dynamically rendered table - vue-good-table. While being on this page(with the table) if I click on the buttons for changing languages, everywhere the languages is being changed dynamically, but not the labels and placeholders of the table. If I go to one of the other pages and comeback to the table, the labels and placeholders are updated correctly. Can you help me make it change while I am on the same page?
I was wondering if beforeMount() would help in this situation?
main.js
import VueI18n from 'vue-i18n'
import {messages} from './locales/bg_en_messages'
Vue.use(VueI18n)
const i18n = new VueI18n({
locale: 'bg', // set locale
fallbackLocale: 'bg',
messages // set locale messages
});
Vue.prototype.$locale = {
change (lang) {
i18n.locale = lang
},
current () {
return i18n.locale
}
};
Messages.vue:
<vue-good-table
:columns="columns"
:rows="items"
:paginate="true"
:lineNumbers="true">
</vue-good-table>
<script type="text/javascript">
export default {
data(){
return {
items:[],
columns: [
{
label: this.$t("columns.date"),
field: 'changeddate',
type: 'String',
filterable: true,
placeholder: this.$t("columns.date")
},
{
label: this.$t("columns.userChange"),
field: 'userchange',
type: 'String',
filterable: true,
placeholder: this.$t("columns.userChange")
}
]
}
}
}
App.vue
<div style="padding: 10px; width: 99%;">
<ui-button #click="changeLang('bg')">
<img src="../src/assets/images/skin/Flag1.png" v-bind:alt="home" height="15" width="25"/>
</ui-button>
<ui-button #click="changeLang('en')">
<img src="../src/assets/images/skin/Flag2.png" v-bind:alt="home" height="15" width="25"/>
</ui-button>
</div>
<script>
export default {
name: 'Localization',
methods: {
changeLang: function(newLang){
this.$locale.change(newLang)
}
}
}
</script>
The reason is that the data that's changing is nested inside an object itself and your template only listens to changes to that parent object and not for its children (your language data). So even if that changes, your view wont notice it. If you use data from your translation directly in the template using the template syntax, the data will re-render automatically because that is not nested(that's why it probably works everywhere else).
Now of course you can't do that in your table's case, because your table component accepts nested data only, so the workaround would be to use a computed property for your columns, instead of putting them into your component's data. This way all changes will be mirrored to your component.
Simply change your component like that and you should be good to go:
export default {
data(){
return {
items:[]
}
},
computed: {
columns () {
return [
{
label: this.$t("columns.date"),
field: 'changeddate',
type: 'String',
filterable: true,
placeholder: this.$t("columns.date")
},
{
label: this.$t("columns.userChange"),
field: 'userchange',
type: 'String',
filterable: true,
placeholder: this.$t("columns.userChange")
}
]
}
}
}

Angular 4 Create Dynamic formArray inside array using reactive forms

Here, we are creating dynamically form array's inside array.
Below is the sample structure of expected result given below.
{
"optionsRadios": null,
"Package_Title": null,
"HotelData": [
{
"Htitle": "",
"HDescription": "",
"hotelStar": "",
"RoomData": [
{
"Hotel_Room_Type": ""
},
{
"Hotel_Room_Type": ""
}
]
}
]
}
I want to create HotelData Dynamically, within that HotelData array i want to create RoomData array fields also dynamically.
I created HotelData fields by the following codes:
export class AddPackageComponent implements OnInit {
ngOnInit() {
this.invoiceForm = this._formBuild.group({
Package_Title: [],
HotelData: this._formBuild.array([this.addRows()])
})
}
addRows() {
return this._formBuild.group({
Htitle: [''],
HDescription: [''],
hotelStar: ['']
});
}
addHotel() {
const control: FormArray = this.invoiceForm.get(`HotelData`) as FormArray;
control.push(this.addRows());
}
}
You are on the right track, we just need to add some more code...
addRows need the form array RoomData, and here we also initially push an empty form group of room. If you don't want that, modify it.
addRows() {
let group = this._formBuild.group({
...
RoomData: this._formBuild.array([])
});
// push formgroup to array initially
this.addRoom(group.controls.RoomData)
return group;
}
addRoom looks like this:
addRoom(hotel:any) {
let group = this._formBuild.group({
Hotel_Room_Type: ['']
})
hotel.push(group)
}
addRoom is also the method we are calling from template when we want to add a new room to a hotel. Remember to pass the current hotel as parameter from template.
As for adding a new hotel, your addHotel stays the way you have it now.
Then over to your template, the relevant part should look something like this:
<div formArrayName="HotelData">
<div *ngFor="let hotel of invoiceForm.get('HotelData').controls; let i = index" [formGroupName]="i" >
<!-- form controls here -->
<button (click)="addRoom(hotel.get('RoomData'))">Add Room</button>
<div formArrayName="RoomData">
<div *ngFor="let room of hotel.get('RoomData').controls; let j = index" [formGroupName]="j">
<!-- form controls here -->
</div>
</div>
</div>
</div>
Finally, here's a Demo: http://plnkr.co/edit/7tcLcnzALew3oKGenjwK?p=preview

How do I communicate the form parameters from the template to the route in ember.js?

I have a form like this in a template:
<form {{action 'add' on='submit'}}>
<label for="name">Name</label>
{{input value=name placeholder='Enter name' required="required"}}
</form>
Then I have the following route:
import Ember from 'ember';
export default Ember.Route.extend({
actions: {
add: function() {
alert(this.get('name'));
}
}
});
The alert is fired, but the output is "undefined". I tried to create a model in the route, but it didn't help. What am I missing to get the alert to show the string I type in the form? I don't want to use a controller, since controllers are discouraged.
1. About Controllers
If you do not like to use controllers this would work for you (but I do not recommend you to follow this way):
export default Ember.Route.extend({
actions: {
add: function() {
alert(this.controllerFor( this.get('routeName') ).get('name'));
}
}
});
In fact, if you use name in template:
{{input value=name}}
you are actually using controller property name.
So there is suggestion to use triples (route, controller, template) for every route, and in future triples could be transformed to routing components. You could read about it here:
https://gist.github.com/samselikoff/1d7300ce59d216fdaf97
2. Data communication
I'd suggest you to use model for data communication. You could define model hook in route:
// route
export default Ember.Route.extend({
model: function() {
// your logic here, for example
// return Ember.Object.create({name: 'DefaultName'});
// or
// return this.store.createRecord('yourModel', {name: 'DefaultName'});
}
});
then you'll have model property in controller, you could use it in template
// template
<form {{action 'add' on='submit'}}>
<label for="name">Name</label>
{{input value=model.name placeholder='Enter name' required="required"}}
</form>
then you could place your action in controller:
// controller
export default Ember.Controller.extend({
action: {
add: function() {
// whatever you'd like to do with model, for example
// alert( this.get('model.name') );
// or
// var self = this;
// this.get('model').save().then(function() {
// self.transitionToRoute('someRoute');
// });
}
}
});
You could also place action in route, but then you have to write:
this.modelFor('yourRouteName') to get model,
this.controllerFor('yourRouteName').get('someProperty') to get controller property someProperty.
I'm not sure what is a "correct way", but this might help: this.controller.get('name')

Comparing two input values in a form validation with AngularJS

I am trying to do a form validation using AngularJS. I am especially interested in comparing two values. I want the user to confirm some data he entered before he continues. Lets say I have the code below:
<p>
Email:<input type="email" name="email1" ng-model="emailReg">
Repeat Email:<input type="email" name="email2" ng-model="emailReg2">
<p>
and then I can use validation with:
<span ng-show="registerForm.email1.$error.required">Required!</span>
<span ng-show="registerForm.email1.$error.email">Not valid email!</span>
<span ng-show="emailReg !== emailReg2">Emails have to match!</span> <-- see this line
registerForm.$valid will react correctly as to the text in inputs except I do not know how to use comparison within this validation to force the emails to be the same before allowing the user to submit the form.
I would love to have a solution without custom directives, but if this can't be achieved without it I will deal with it. Here is an answer that addresses similar issue with a custom directive.
Any help appreciated, thank you
You should be able to use ng-pattern/regex for comparing 2 input values
Email:<input type="email" name="email1" ng-model="emailReg">
Repeat Email:<input type="email" name="email2" ng-model="emailReg2" ng-pattern="emailReg">
and validation with:
<span ng-show="registerForm.email2.$error.pattern">Repeat Email should have the same value with email!</span>
One way to achieve this is with a custom directive. Here's an example using a custom directive (ng-match in this case):
<p>Email:<input type="email" name="email1" ng-model="emailReg">
Repeat Email:<input type="email" name="email2" ng-model="emailReg2" ng-match="emailReg"></p>
<span data-ng-show="myForm.emailReg2.$error.match">Emails have to match!</span>
NOTE: It's not generally recommended to use ng- as a prefix for a custom directive because it may conflict with an official AngularJS directive.
Update
It's also possible to get this functionality without using a custom directive:
HTML
<button ng-click="add()></button>
<span ng-show="IsMatch">Emails have to match!</span>
Controller
$scope.add = function() {
if ($scope.emailReg != $scope.emailReg2) {
$scope.IsMatch=true;
return false;
}
$scope.IsMatch=false;
}
trainosais - you are right, validation should be done on a directive level. It's clean, modular and allows for reusability of code. When you have basic validation like that in a controller you have write it over and over again for different forms. That's super anti-dry.
I had a similar problem recently and sorted it out with a simple directive, which plugs in to the parsers pipeline, therefore stays consistent with Angular architecture. Chaining validators makes it very easy to reuse and that should be considered the only solution in my view.
Without further ado, here's the simplified markup:
<form novalidate="novalidate">
<label>email</label>
<input type="text"
ng-model="email"
name="email" />
<label>email repeated</label>
<input ng-model="emailRepeated"
same-as="email"
name="emailRepeated" />
</form>
And the JS code:
angular.module('app', [])
.directive('sameAs', function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ngModel) {
ngModel.$parsers.unshift(validate);
// Force-trigger the parsing pipeline.
scope.$watch(attrs.sameAs, function() {
ngModel.$setViewValue(ngModel.$viewValue);
});
function validate(value) {
var isValid = scope.$eval(attrs.sameAs) == value;
ngModel.$setValidity('same-as', isValid);
return isValid ? value : undefined;
}
}
};
});
The directive hooks into the parsers pipeline in order to get notified of any changes to the view value and set validity based on comparison of the new view value and the value of the reference field. That bit is easy. The tricky bit is sniffing for changes on the reference field. For that the directive sets a watcher on the reference value and force-triggeres the parsing pipeline, in order to get all the validators run again.
If you want to play with it, here is my pen:
http://codepen.io/jciolek/pen/kaKEn
I hope it helps,
Jacek
I recently wrote a custom directive which can be generic enough to do any validation. It take a validation function from the current scope
module.directive('customValidator', [function () {
return {
restrict: 'A',
require: 'ngModel',
scope: { validateFunction: '&' },
link: function (scope, elm, attr, ngModelCtrl) {
ngModelCtrl.$parsers.push(function (value) {
var result = scope.validateFunction({ 'value': value });
if (result || result === false) {
if (result.then) {
result.then(function (data) { //For promise type result object
ngModelCtrl.$setValidity(attr.customValidator, data);
}, function (error) {
ngModelCtrl.$setValidity(attr.customValidator, false);
});
}
else {
ngModelCtrl.$setValidity(attr.customValidator, result);
return result ? value : undefined; //For boolean result return based on boolean value
}
}
return value;
});
}
};
}]);
To use it you do
<input type="email" name="email2" ng-model="emailReg2" custom-validator='emailMatch' data-validate-function='checkEmailMatch(value)'>
<span ng-show="registerForm.email2.$error.emailMatch">Emails have to match!</span>
In you controller then you can implement the method, that should return true or false
$scope.checkEmailMatch=function(value) {
return value===$scope.emailReg;
}
The advantage is that you do not have to write custom directive for each custom validation.
When upgrading angular to 1.3 and above I found an issue using Jacek Ciolek's great answer:
Add data to the reference field
Add the same data to the field with the directive on it (this field is now valid)
Go back to the reference field and change the data (directive field remains valid)
I tested rdukeshier's answer (updating var modelToMatch = element.attr('sameAs') to var modelToMatch = attrs.sameAs to retrieve the reference model correctly) but the same issue occurred.
To fix this (tested in angular 1.3 and 1.4) I adapted rdukeshier's code and added a watcher on the reference field to run all validations when the reference field is changed. The directive now looks like this:
angular.module('app', [])
.directive('sameAs', function () {
return {
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
var modelToMatch = attrs.sameAs;
scope.$watch(attrs.sameAs, function() {
ctrl.$validate();
})
ctrl.$validators.match = function(modelValue, viewValue) {
return viewValue === scope.$eval(modelToMatch);
};
}
};
});
Updated codepen
No need a function or a directive. Just compare their $modelValue from the view:
ng-show="formName.email.$modelValue !== formName.confirmEmail.$modelValue"
More detailed example:
<span ng-show="(formName.email.$modelValue !== formName.confirmEmail.$modelValue)
&& formName.confirmEmail.$touched
&& !formName.confirmEmail.$error.required">Email does not match.</span>
Please note that the ConfirmEmail is outside the ViewModel; it's property of the $scope. It doesn't need to be submitted.
use ng-pattern, so that ng-valid and ng-dirty can act correctly
Email:<input type="email" name="email1" ng-model="emailReg">
Repeat Email:<input type="email" name="email2" ng-model="emailReg2" ng-pattern="emailReg">
<span ng-show="registerForm.email2.$error.pattern">Emails have to match!</span>
#Henry-Neo's method was close, it just needed more strict Regex rules.
<form name="emailForm">
Email: <input type="email" name="email1" ng-model="emailReg">
Repeat Email: <input type="email" name="email2" ng-model="emailReg2" ng-pattern="(emailReg)">
</form>
By including the regex rule inside parentheses, it will match the entire string of emailReg to emailReg2 and will cause the form validation to fail because it doesn't match.
You can then drill into the elements to find out which part is failing.
<p ng-show="emailForm.$valid">Form Valid</p>
<p ng-show="emailForm.email1.$error">Email not valid</p>
<p ng-show="emailForm.email1.$valid && emailForm.email1.$error.pattern">
Emails Do Not Match
</p>
This module works well for comparing two fields. Works great with Angular 1.3+. Simple to use
https://www.npmjs.com/package/angular-password
It also allows saving the module as a generic. Just include them in packages list of your module.
Here is my simple version of the custom validator directive:
angular.module('app')
.directive('equalsTo', function () {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ngModel) {
scope.$watchGroup([attrs.equalsTo, () => ngModel.$modelValue], newVal => {
ngModel.$setValidity('equalsTo', newVal[0] === newVal[1]);
});
}
};
})
Here is an angular 1.3 version of the sameAs directive:
angular.module('app').directive('sameAs', [function() {
'use strict';
return {
require: 'ngModel',
restrict: 'A',
link: function(scope, element, attrs, ctrl) {
var modelToMatch = element.attr('sameAs');
ctrl.$validators.match = function(modelValue, viewValue) {
return viewValue === scope.$eval(modelToMatch);
};
}
};
}]);
Mine is similar to your solution but I got it to work. Only difference is my model. I have the following models in my html input:
ng-model="new.Participant.email"
ng-model="new.Participant.confirmEmail"
and in my controller, I have this in $scope:
$scope.new = {
Participant: {}
};
and this validation line worked:
<label class="help-block" ng-show="new.Participant.email !== new.Participant.confirmEmail">Emails must match! </label>
Thanks for the great example #Jacek Ciolek. For angular 1.3.x this solution breaks when updates are made to the reference input value. Building on this example for angular 1.3.x, this solution works just as well with Angular 1.3.x. It binds and watches for changes to the reference value.
angular.module('app', []).directive('sameAs', function() {
return {
restrict: 'A',
require: 'ngModel',
scope: {
sameAs: '='
},
link: function(scope, elm, attr, ngModel) {
if (!ngModel) return;
attr.$observe('ngModel', function(value) {
// observes changes to this ngModel
ngModel.$validate();
});
scope.$watch('sameAs', function(sameAs) {
// watches for changes from sameAs binding
ngModel.$validate();
});
ngModel.$validators.sameAs = function(value) {
return scope.sameAs == value;
};
}
};
});
Here is my pen: http://codepen.io/kvangrae/pen/BjxMWR
You have to look at the bigger problem. How to write the directives that solve one problem. You should try directive use-form-error. Would it help to solve this problem, and many others.
<form name="ExampleForm">
<label>Password</label>
<input ng-model="password" required />
<br>
<label>Confirm password</label>
<input ng-model="confirmPassword" required />
<div use-form-error="isSame" use-error-expression="password && confirmPassword && password!=confirmPassword" ng-show="ExampleForm.$error.isSame">Passwords Do Not Match!</div>
</form>
Live example jsfiddle
I need to do this just in one form in my entire application and i see a directive like a super complicate for my case, so i use the ng-patter like some has point, but have some problems, when the string has special characters like .[\ this broke, so i create a function for scape special characters.
$scope.escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
and in the view
<form name="ExampleForm">
<label>Password</label>
<input ng-model="password" required />
<br>
<label>Confirm password</label>
<input ng-model="confirmPassword" required ng-pattern="escapeRegExp(password)"/>
</form>
Of course for very simple comparisons you can always use ngMin/ngMax.
Otherwise, you can go with a custom directive, and there is no need of doing any $watch or $observe or $eval or this fancy $setValidity back and forth. Also, there is no need to hook into the postLink function at all. Try to stay out of the DOM as much as possible, as it is against the angular spirit.
Just use the lifecycle hooks the framework gives you. Add a validator and $validate at each change. Simple as that.
app.directive('sameAs', function() {
return {
restrict: 'A',
require: {
ngModelCtrl: 'ngModel'
},
scope: {
reference: '<sameAs'
},
bindToController: true,
controller: function($scope) {
var $ctrl = $scope.$ctrl;
//add the validator to the ngModelController
$ctrl.$onInit = function() {
function sameAsReference (modelValue, viewValue) {
if (!$ctrl.reference || !modelValue) { //nothing to compare
return true;
}
return modelValue === $ctrl.reference;
}
$ctrl.ngModelCtrl.$validators.sameas = sameAsReference;
};
//do the check at each change
$ctrl.$onChanges = function(changesObj) {
$ctrl.ngModelCtrl.$validate();
};
},
controllerAs: '$ctrl'
};
});
Your plunker.
I've modified method of Chandermani to be compatible with Angularjs 1.3 and upper. Migrated from $parsers to $asyncValidators.
module.directive('customValidator', [function () {
return {
restrict: 'A',
require: 'ngModel',
scope: { validateFunction: '&' },
link: function (scope, elm, attr, ngModelCtrl) {
ngModelCtrl.$asyncValidators[attr.customValidator] = function (modelValue, viewValue) {
return new Promise(function (resolve, reject) {
var result = scope.validateFunction({ 'value': viewValue });
if (result || result === false) {
if (result.then) {
result.then(function (data) { //For promise type result object
if (data)
resolve();
else
reject();
}, function (error) {
reject();
});
}
else {
if (result)
resolve();
else
reject();
return;
}
}
reject();
});
}
}
};
}]);
Usage is the same