Angular2: Conditional required validation - validation

I am trying to create a conditional required validation on a specific field.I try doing this by return Validators.required back in my function, but this doesn't seem to work. How do I go about doing this? Here's my code:
private _ansat: AbstractControl = new FormControl('', Validators.required);
private _helbred: AbstractControl = new FormControl('', Validators.compose([this.useValidateIfRadio(this._ansat, 0, Validators.required)]) );
constructor(private _fb: FormBuilder) {
this.myForm = this._fb.group({
ansat: this._ansat,
helbred: this._helbred
});
}
useValidateIfRadio (c: AbstractControl, n: number, v) {
return function (control) {
return new Promise(resolve => {
// this.msg = ansatControl.value;
console.log(v);
if (c.value === n) {
resolve(v);
}
else {
resolve(null);
}
});
};
};
Any help is greatly appreciated.

I had a similar problem but couldn't find a answer. Since nobody has answered this yet I'll provide an example of how I solved my problem, and how you can solve your issue using the same solution.
Example: (Phone number is required only if email is not set)
export class UserComponent implements OnInit {
userForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
//Create my userForm and and add initial validators
this.userForm = this.fb.group({
username: [null, [Validators.required]],
name: [null, [Validators.required]],
email: [],
phoneNumber: [null, [Validators.required, Validators.minLength(4)],
});
//Listen to email value and update validators of phoneNumber accordingly
this.userForm.get('email').valueChanges.subscribe(data => this.onEmailValueChanged(data));
}
onEmailValueChanged(value: any){
let phoneNumberControl = this.userForm.get('phoneNumber');
// Using setValidators to add and remove validators. No better support for adding and removing validators to controller atm.
// See issue: https://github.com/angular/angular/issues/10567
if(!value){
phoneNumberControl.setValidators([Validators.required, Validators.minLength(4)]);
}else {
phoneNumberControl.setValidators([Validators.minLength(4)]);
}
phoneNumberControl.updateValueAndValidity(); //Need to call this to trigger a update
}
}
So in your case you should add a changeListener to "_ansat" equal to my email listener, and then add required to "_helbred" accordingly.

Just add validator for the field:
if(some_logic) {
this.your_form.get('field_name').setValidators([Validators.required]);
}

These answers got me most of the way there, but I found out a pretty big gotcha… in some cases, setValidators only adds to the existing array of validators and does not work well to clear them. In some cases, like when ngOnInit loads twice in a row, the conditions could be first negative and then positive for a passed-in value you're depending on. In such a case, you will set it to required, then later attempt to clear it, but the UI will still behave like it expects it. To fix this, consider the following...
const myControl = this.your_form.get('field_name');
if(some_logic) {
myControl.clearAsyncValidators();
myControl.clearValidators();
myControl.updateValueAndValidity({onlySelf:true});
} else {
myControl.setValidators([Validators.required, Validators.other…]);
}

Related

Reactive Form SetValue for FormGroup Array inside observable subscribe method

I am new the Angular but I am trying to understand How can we use Reactive Form and FormGroups array while we use the Service Observable.
My form is very simple, It has list of Articles, where you can select one of them and can edit the detail. It also has feature to add new article.
So, starting of the page itself, It has list of articles in left side and reactive blank form on other side.
Now, this form also contain some of the checkbox which provides the tags for an article.
Now, when I load the page, everything is coming as expected. Articles are coming and reactive form is also loading along with all the tags (un-checked)
When I click any of the article to make that editable, I am getting an error
"Must supply a value for form control at index: 0."
I tried to change the code little but but with that the new error started coming
"Must supply a value for form control with name: 'articleId'"
So, overall I am not getting what is fundamental issue. Seems like I am missing to give a name to the formgroup but not sure how to supply.
// Global Variables: Called from the constructor.
articleDetailForm: FormGroup;
tagFormArray: FormArray = new FormArray([]);
// While this.loadArticle(this.selectedArticleId); called on change event
private loadArticle(selectedArticleID: string) {
this.articleService.getArticle(selectedArticleID)
.subscribe(
(data: ArticleViewModel) => {
const _a = new ArticleViewModel(data);
debugger;
this.articleDetailForm.setValue({
articleBasicDetail: this.setBasicDetailForSelectedArticle(_a),
articleTagDetail: this.setTagDetailForSelectedArticle(_a.ArticleTagViewModels)
})
},
(err) => {
console.log(err);
});
}
private setBasicDetailForSelectedArticle(articleVM: ArticleViewModel) {
return new FormGroup({
articleId: new FormControl(articleVM.articleTitle, ),
articleTitle: new FormControl(articleVM.articleTitle),
articleContent: new FormControl(articleVM.articleContent)
});
}
private setTagDetailForSelectedArticle(articleTagsVM: ArticleTagViewModel[]) {
// want to loop through variable and checked only those tags which are available for this article
return new FormGroup({
tagId: new FormControl(111),
tagName: new FormControl("111"),
isChecked: new FormControl(true)
});
}
private createArticleDetailForm() {
let articleId = 'Dummy ID';
let articleTitle = 'Dummy Title';
let articleContent = 'Dummy Content';
this.articleDetailForm = this.formBuilder.group({
articleBasicDetail: this.formBuilder.group({
articleId: [{ value: articleId, disabled: true }, Validators.required],
articleTitle: [articleTitle, Validators.required],
articleContent: [articleContent, Validators.required],
}),
articleTagDetail: this.tagFormArray
});
}
ERROR Error: Must supply a value for form control with name: 'articleId'.
ERROR Error: "Must supply a value for form control at index: 0."

Define Knockout validation rule that takes an observable parameter using typescript

I have defined a validation rule like this
ko.validation.rules["studentValidation"] = {
validator: (val: any, params: any) => {
return (this.IsInRequiredRangeForStudent(params.DateOfBirth) && val === false);
}
}
IsInRequiredRangeForStudent = (dateOfBirth: any) () => {
//my implementation
}
Here is my ViewModel class, where i consume and apply this rule on an observable
this.isStudent = ko.observable<boolean>(isStudent).extend({
studentValidation: {
message: "Invalid student option!",
params: {
DateOfBirth: this.dateOfBirth()
}
}
});
In my validation rule implementation, I always get params.DateOfBirth as null. What I am doing wrong here?
params.DateOfBirth can be null for several reasons. But firstly I would check one scenario. There is a chance that when you extending isStudent observable, you define validation params assigning value of dateOfBirth observable. But the value is evaluated at the moment of assigning, I don't see the rest of your code but it's highly possible that dateOfBirth observable is null at the moment of assigning to params. So every further check of params.DateOfBirth may return NULL value.
Please try following:
this.isStudent = ko.observable<boolean>(isStudent).extend({
studentValidation: {
message: "Invalid student option!",
params: {
DateOfBirth: this.dateOfBirth
}
}
});
and this:
ko.validation.rules["studentValidation"] = {
validator: (val: any, params: any) => {
return (this.IsInRequiredRangeForStudent(params.DateOfBirth()) && val === false);
}
}
What it changes? It defines params.DateOfBirth as function (not a value), so you can evaluate its value on every validation call.

Validation using validation service

I have partly model-driven partly template-driven form and I want to validate one of the inputs using custom logic. The problem is, control is in invalid state even validation returns as valid. What am I doing wrong?
<span *ngIf="identityNumber.touched && !identityNumber.valid">invalid</span>
..
this.form = this.formBuilder.group({
identityNumber: ['', Validators.required,
ValidationService.validateIdentityNumber]
});
..
public static validateIdentityNumber(control: Control) {
let val: string = <string>control.value;
if (val && val.length == 11) {
return null;
} else {
return { "invalidIdentityNumber": true };
}
}
http://plnkr.co/edit/1IY4iXEeQDXUanCOSCKX?p=preview
It displays as "invalid" even when you type 11 characters.
(I know there's a length validator, this is for sample's sake)
If you want to pass more than one validator use Validators.compose
identityNumber: ['',
Validators.compose([
Validators.required,
ValidationService.validateIdentityNumber
])
]
(the 3rd parameter is for async validators)

General: asynchonious validation in angular2

Since couple evening I've played with form validation in augular2.
All basic cases were easy to implement and they works fine but I stick with asynchronous validation. I have created a very tiny example http://plnkr.co/edit/Xo8xwJjhlHkXrunzS8ZE and it didn't work.
According to test "should fire an event after the status has been updated to pending" from model_spec.ts Registration via creation of control group suppose to work in a way
builder.group({login: ["",Validators.required,validationFuctionWhichReturnsPromise]
I spent a full evening to discovered that this code has been released in alfa-46 (and I used alfa-45) and after update depencies the async validation started to work. The feature is very fresh and is not fully documented but
(for those who haven't tried it yet) Basically async validator is a function which have a Control argument and return a promise which validation result. There are two ways to register a validator. 1) the one which I used in my example and 2) as a directive which Provide validators via NG_ASYNC_VALIDATORS (See UniqLoginValidator and NgFormControl to see how it work). You can compose more than one validator (not tested yet but functions to do this are in code, see https://github.com/angular/angular/commit/cf449dd).
But when I finally reach to up and running validators a new problem arrived. Async validator is perfect to used it in server side validation. But the validation is invoked after each change of model.fe after each keyup. So if we will send request to a server after each key up, it won't be too efficient way ;) I checked how it is done in angular 1 and they is a possibility to debounce validation events.
My questions are:
How to implement throttle or debounce with async validators? I saw some ideas but none of them were fine (mostly because they need to change angular code itself). Is there any valid way to do this without waiting for new angular release ?
I was thinking about to warping a validator function with debounce (from underscorejs) but it will not work because angular expects to get a valid promise every time.
My second though was that if all event use RxJs under the hood then maybe I can apply debounce on stream of event which is responsible for validation. In model.ts the promise returned from validator is change to observable and a new subscribed is added. We don't have any access to obs(Observable) to apply debounce there.
Is there any way or id to change,easy extend a control over the form validation ?
I spotted a close related problem in How to trigger Form Validators in angular2
PS there is other issue related to async validators and it is still open https://github.com/angular/angular/issues/1068
Here is a helper class that you can use to debounce all your async validators:
import {Component} from 'angular2/core';
import {Observable} from 'rxjs/Observable';
import {Observer} from 'rxjs/Observer';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import {Control} from 'angular2/common';
export class AsyncValidator {
_validate;
constructor(validator: (control: Control) => any, debounceTime = 1000) {
let source: any = new Observable((observer: Observer<Control>) => {
this._validate = (control) => observer.next(control);
});
source.debounceTime(debounceTime)
.distinctUntilChanged(null, (x) => x.control.value)
.map(x => { return { promise: validator(x.control), resolver: x.promiseResolver }; })
.subscribe(
(x) => x.promise.then(resultValue => x.resolver(resultValue),
(e) => { console.log('async validator error: %s', e); }));
}
private _getValidator() {
return (control) => {
let promiseResolver;
let p = new Promise((resolve) => {
promiseResolver = resolve;
});
this._validate({ control: control, promiseResolver: promiseResolver });
return p;
};
}
static debounce(validator: (control: Control) => any, debounceTime = 400) {
var asyncValidator = new this(validator, debounceTime);
return asyncValidator._getValidator();
}
}
Then all you have to do where use async validators is just wrap your validator with this call and write your validator the same as you would normally:
AsyncValidator.debounce(control => this.asyncValidator(control));
Here is an example usage:
export class AppComponent {
form: ControlGroup;
constructor(private _formBuilder: FormBuilder) {
var validator = AsyncValidator.debounce(control => this.asyncValidator(control));
this.form = _formBuilder.group({
name: ['', Validators.required, validator],
});
}
asyncValidator(control): any {
let p = new Promise(resolve => {
// get from server information need to validate control
if (control.value === 'valid value') {
resolve(null);
} else {
resolve({
asyncValidator: {
valid: false
}
});
}
});
return p;
}
}
There is an awesome issue on angular site that deals with the problem of both debouncing and switchMapping the validation:
https://github.com/angular/angular/issues/6895
This is mine working solution (but all the credit goes to guys from thread)
class AsyncValidator{
private validatorInput: Subject<string>;
private validatorChain: Observable<any>;
constructor(service: ManageUsersService) {
this.validatorInput = new Subject();
this.validatorChain = this.validatorInput
.debounceTime(400)
.distinctUntilChanged()
.switchMap(value => service.findUsersByName(value)
.map(() => ({error: 'Error'})) //example of failed validation
.catch(() => Observable.of(null))) //example of successful validation
.do(v => console.log('mapped', v))
.share()
.take(1);
}
validate = (control: AbstractControl) => {
// An immediate timeout is set because the next has to occur after the
// validator chain is subscribed to.
setTimeout(() => this.validatorInput.next(control.value), 0);
return this.validatorChain;
}
You use it like this:
this.createUserForm = fb.group({
login: [ null,
Validators.required,
new AsyncValidator(userService).validate
],
});
}

Breeze client-side custom validation with server-side data

I created a custom validator that check if a username is used on a DB.
The whole process of validation works. What is not working is result.
function createExistingUsernameValidator() {
var name = 'existingUsernameValidator';
var ctx = { messageTemplate: 'Questa partita I.V.A. o codice fiscale sono già stati inseriti.', displayName: "Partita IVA o Codice Fiscale" };
var val = new Validator(name, valFunction, ctx);
return val;
function valFunction(value, context) {
var result = ko.observable(true);
require('services/datacontext').getIsUserByUsername(value, result)
.then(function () {
debugger;
return !result();
});
}
}
The promise works: I know because it hits the debbugger line and the retunrnig value is correct.
But the validator always evaluate as false because I'm not returning anything when the validator is called. In other words: it won't wait for the promise.
Is it my bad javascript or something else?
Any help is welcome.
Thank you!
Edited after answer
I've come to a solution that involves Knockout Validation (very useful script).
function createIsExistingUserKoValidation() {
ko.validation.rules['existingUsername'] = {
async: true,
validator: function (val, params, callback) {
if (val) {
var result = ko.observable();
require('services/datacontext').getIsUserByUsername(val, result)
.then(function () {
callback(!result());
});
}
},
message: ' Existing username.'
};
ko.validation.registerExtenders();
}
In the entity creation:
var createDitta = function () {
var ditta = manager.createEntity(entityNames.ditta,
{
id: newGuid(),
legaleRappresentante: createPersona(),
isAttiva: true
});
ditta.pivaCodFiscale.extend({ existingUsername: { message: ' Existing username.', params: true } });
ditta.pivaCodFiscale.isValidating(false);
return ditta;
};
ditta.pivaCodFiscale.isValidating(false); this is needed because isValidating is initialized with true.
The problem is that your valFunction as written will ALWAYS return 'undefined'. ( which is 'falsy'.
The 'return !result()' expression is NOT the return value of 'valFunction', it is simply the result of an anonymous function that executes AFTER valFunction has already returned. This is the async nature of promises.
What you are trying is to write an 'asynchronous' validation which is NOT supported out of the box with Breeze, but the idea IS a good one.
I think that you might be able to accomplish what you want by having your async callback actually 'set' a value on the entity and have that set operation itself trigger a seperate 'synchronous' validation.
This IS a good idea for Breeze to support more naturally so please feel free to add a feature request to the Breeze User Voice for something like "asynchonous validation". We use this to gauge the communities interest in the various proposed features/extensions to Breeze.

Resources