Applying a pipe or transform to a Reactive Form value - date

I'm building a simple reactive form. For simplicity, lets say the only data I want to display is a date.
test.component.html
<form novalidate [formGroup]="myForm">
<input type="date" formControlName="date">
</form>
test.component.ts
private date: Date = Date.now();
ngOnInit() {
this.myForm = this.fb.group({
date: [this.date, [Validators.required]]
});
}
The input type=date field on the template requires the date to be in the format of 'yyyy-MM-dd'. The value in event is a JavaScript Date object.
How can I modify the data at the template level so the input value is correct?
What I've tried:
One way to do this would be to inject the DatePipe into my component and apply the conversion in code.
date: [datePipe.transform(this.event.date, 'yyyy-MM-dd'), [Validators.required]]
But this ties the implementation detail of the template to the component. For example, what if a NativeScript template requires the date to be in the format MM/dd/yyyy? The formGroup is no longer valid.

The only way I've been able come up, with the help of #n00dl3 is to wrap the md-input component and provide the proper value via a ControlValueAccessor
import { Component, Input, ViewChild } from '#angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
import { DatePipe } from '#angular/common';
import { MdInput } from '#angular/material';
#Component({
selector: 'md-date-input',
template: `
<md-input [placeholder]="placeholder"
type="date"
(change)="update()"
[value]="dateInput">
</md-input>`,
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: DateInputComponent, multi: true }]
})
export class DateInputComponent implements ControlValueAccessor {
#Input() placeholder: string;
#ViewChild(MdInput) mdInput: MdInput;
dateInput: string;
onChange: (value: any) => void;
constructor(private datePipe: DatePipe) {
}
writeValue(value: any) {
this.dateInput = value == null ? '' : this.datePipe.transform(value, 'yyyy-MM-dd');
}
registerOnChange(fn: (value: any) => void) {
this.onChange = fn;
}
registerOnTouched(fn: (value: any) => void) {
}
update() {
this.onChange(this.mdInput.value ? new Date(this.mdInput.value) : '');
}
}

Related

Angular 2 Custom Validator (Template Form) Validation

I am trying to implement a custom validator that checks if the keyed username exists. However, I am running into a problem where the control is always invalid. I have confirmed the webApi is returning the correct values and the validator function is stepping into the proper return statements.
My custom validator is as follows:
import { Directive, forwardRef } from '#angular/core';
import { AbstractControl, ValidatorFn, NG_VALIDATORS, Validator, FormControl } from '#angular/forms';
import { UsersService } from '../_services/index';
import { Users } from '../admin/models/users';
function validateUserNameAvailableFactory(userService: UsersService): ValidatorFn {
return (async (c: AbstractControl) => {
var user: Users;
userService.getUserByName(c.value)
.do(u => user = u)
.subscribe(
data => {
console.log("User " + user)
if (user) {
console.log("Username was found");
return {
usernameAvailable: {
valid: false
}
}
}
else {
console.log("Username was not found");
return null;
}
},
error => {
console.log("Username was not found");
return null;
})
})
}
#Directive({
selector: '[usernameAvailable][ngModel]',
providers: [
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => UserNameAvailableValidator), multi: true }
]
})
export class UserNameAvailableValidator implements Validator {
validator: ValidatorFn;
constructor(private usersService: UsersService) {
}
ngOnInit() {
this.validator = validateUserNameAvailableFactory(this.usersService);
}
validate(c: FormControl) {
console.log(this.validator(c));
return this.validator(c);
}
}
And the form looks like:
<form #userForm="ngForm" (ngSubmit)="onSubmit()" novalidate>
<div class="form form-group col-md-12">
<label for="UserName">User Name</label>
<input type="text" class="form-control" id="UserName"
required usernameAvailable maxlength="50"
name="UserName" [(ngModel)]="user.userName"
#UserName="ngModel" />
<div [hidden]="UserName.valid || UserName.pristine" class="alert alert-danger">
<p *ngIf="UserName.errors.required">Username is required</p>
<p *ngIf="UserName.errors.usernameAvailable">Username is not available</p>
<p *ngIf="UserName.errors.maxlength">Username is too long</p>
</div>
<!--<div [hidden]="UserName.valid || UserName.pristine" class="alert alert-danger">Username is Required</div>-->
</div>
</form>
I have followed several tutorials proving this structure works, so I am assuming that I am possibly messing up the return from within the validator function.
Also, is there a way I can present the list of errors (I have tried using li with ngFor, but I get nothing from that)?
So, there was something wrong with using .subscribe the way I was. I found a solution by using the following 2 links:
https://netbasal.com/angular-2-forms-create-async-validator-directive-dd3fd026cb45
http://cuppalabs.github.io/tutorials/how-to-implement-angular2-form-validations/
Now, my validator looks like this:
import { Directive, forwardRef } from '#angular/core';
import { AbstractControl, AsyncValidatorFn, ValidatorFn, NG_VALIDATORS, NG_ASYNC_VALIDATORS, Validator, FormControl } from '#angular/forms';
import { Observable } from "rxjs/Rx";
import { UsersService } from '../_services/index';
import { Users } from '../admin/models/users';
#Directive({
selector: "[usernameAvailable][ngModel], [usernameAvailable][formControlName]",
providers: [
{
provide: NG_ASYNC_VALIDATORS,
useExisting: forwardRef(() => UserNameAvailableValidator), multi: true
}
]
})
export class UserNameAvailableValidator implements Validator {
constructor(private usersService: UsersService) {
}
validate(c: AbstractControl): Promise<{ [key: string]: any }> | Observable<{ [key: string]: any }> {
return this.validateUserNameAvailableFactory(c.value);
}
validateUserNameAvailableFactory(username: string) {
return new Promise(resolve => {
this.usersService.getUserByName(username)
.subscribe(
data => {
resolve({
usernameAvailable: true
})
},
error => {
resolve(null);
})
})
}
}
This is now working. However, I now have an issue where the the control temporarily invalidates itself while the async validator is running.
Return for true should be:
{usernameAvailable: true}
or null for false.

Angular 2 | How to handle input type file in FormControl?

Good day,
How can i handle input type file in formControl? im using reactive form but when i get the value of my form it returns null value on my <input type="file">??
You need to write your own FileInputValueAccessor. Here is the plunker and the code:
#Directive({
selector: 'input[type=file]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: FileValueAccessorDirective,
multi: true
}
]
})
export class FileValueAccessorDirective implements ControlValueAccessor {
onChange;
#HostListener('change', ['$event.target.value']) _handleInput(event) {
this.onChange(event);
}
constructor(private element: ElementRef, private render: Renderer2) { }
writeValue(value: any) {
const normalizedValue = value == null ? '' : value;
this.render.setProperty(this.element.nativeElement, 'value', normalizedValue);
}
registerOnChange(fn) { this.onChange = fn; }
registerOnTouched(fn: any) { }
nOnDestroy() { }
}
And then you will be able to get updates like this:
#Component({
moduleId: module.id,
selector: 'my-app',
template: `
<h1>Hello {{name}}</h1>
<h3>File path is: {{path}}</h3>
<input type="file" [formControl]="ctrl">
`
})
export class AppComponent {
name = 'Angular';
path = '';
ctrl = new FormControl('');
ngOnInit() {
this.ctrl.valueChanges.subscribe((v) => {
this.path = v;
});
}
}
there is no way to handle this in angular form-control.
we can provide some hack to make this work if you want to upload the image.
just add the <input type=file> as form control on which user can add the file and acter grabbing the file we can change it to the base64code and then assign that value to the hidden field of our main form.
then we can store the image file in that way.
else you can go for the ng-file-upload moduleng file upload

Angular2 - access the AbstractControl instance in validation directive

I have to trigger validation from the inside of a validator directive.
Here is the directive I have. It works as expected. However I want it to trigger the validation process when the validator function changes. I.e. when its input variable maxDate changes.
How could I do this ?
If I could access the AbstractControl instance in the constructor I could easily do this. I can't think of a way to do it, however.
import { AbstractControl, FormGroup, ValidatorFn, Validator, NG_VALIDATORS, Validators } from '#angular/forms';
import { Directive, Input, OnChanges, SimpleChanges, EventEmitter } from '#angular/core';
function parseDate(date: string):any {
var pattern = /(\d{2})-(\d{2})-(\d{4})/;
if (date) {
var replaced = date.search(pattern) >= 0;
return replaced ? new Date(date.replace(pattern,'$3-$1-$2')) : null;
}
return date;
}
export function maxDateValidator(maxDateObj): ValidatorFn {
return (control:AbstractControl): {[key: string]: any} => {
const val = control.value;
let date = parseDate(val);
let maxDate = parseDate(maxDateObj.max);
if (date && maxDate && date > maxDate) {
return {
maxDateExceeded: true
};
}
return null;
};
}
...
#Directive({
selector: '[maxDate]',
providers: [{provide: NG_VALIDATORS, useExisting: maxDateDirective, multi: true}]
})
export class maxDateDirective implements Validator, OnChanges {
#Input() maxDate: string;
private valFn = Validators.nullValidator;
constructor() { }
ngOnChanges(changes: SimpleChanges): void {
const change = changes['maxDate'];
if (change) {
const val: string = change.currentValue;
this.valFn = maxDateValidator(val);
}
else {
this.valFn = Validators.nullValidator;
}
//This is where I want to trigger the validation again.
}
validate(control): {[key: string]: any} {
return this.valFn(control);
}
}
Usage:
<input type="text" [(ngModel)]="deathDateVal">
<input class="form-control"
type="text"
tabindex="1"
[maxDate]="deathDateVal"
name="will_date"
[textMask]="{pipe: datePipe, mask: dateMask, keepCharPositions: true}"
ngModel
#willDate="ngModel">
Here is what I've just come up with:
#Directive({
selector: '[maxDate]',
providers: [{provide: NG_VALIDATORS, useExisting: maxDateDirective, multi: true}]
})
export class maxDateDirective implements Validator, OnChanges {
#Input() maxDate: string;
private valFn = Validators.nullValidator;
private control:AbstractControl;
constructor() { }
ngOnChanges(changes: SimpleChanges): void {
const change = changes['maxDate'];
if (change) {
const val: string = change.currentValue;
this.valFn = maxDateValidator(val);
}
else {
this.valFn = Validators.nullValidator;
}
if (this.control) {
this.control.updateValueAndValidity(this.control);
}
}
validate(_control:AbstractControl): {[key: string]: any} {
this.control = _control;
return this.valFn(_control);
}
}
It works. Validate is called on initialization so I just store its parameter.
It is fuckin' ugly but it works.
To get your hands on the abstractControl of the input you can do something like this:
#Directive({
// tslint:disable-next-line:directive-selector
selector: 'input[type=date][maxDate]'
})
export class InputFullWithDirective implements Validator, OnChanges {
constructor(#Self() private control: NgControl) {}
/** the rest is mostly unchanged from the question */
}

Angular 2: Custom Input Component with Validation (reactive/model driven approach)

I have to create a component with custom input element (and more elements inside the component, but its not the problem and not part of the example here) with reactive / model driven approach and validation inside and outside the component.
I already created the component, it works fine, my problem is that both formControl's (inside child and parent) are not in sync when it comes to validation or states like touched. For example if you type in a string with more then 10 characters, the form control inside the form is stil valid.
Plunkr
//our root app component
import {Component, Input} from '#angular/core'
import {
FormControl,
FormGroup,
ControlValueAccessor,
NG_VALUE_ACCESSOR,
Validators
} from '#angular/forms';
#Component({
selector: 'my-child',
template: `
<h1>Child</h1>
<input [formControl]="childControl">
`,
providers: [
{provide: NG_VALUE_ACCESSOR, useExisting: Child, multi: true}
]
})
export class Child implements ControlValueAccessor {
childControl = new FormControl('', Validators.maxLength(10));
writeValue(value: any) {
this.childControl.setValue(value);
}
registerOnChange(fn: (value: any) => void) {
this.childControl.valueChanges.subscribe(fn);
}
registerOnTouched() {}
}
#Component({
selector: 'my-app',
template: `
<div>
<h4>Hello {{name}}</h4>
<form [formGroup]="form" (ngSubmit)="sayHello()">
<my-child formControlName="username"></my-child>
<button type="submit">Register</button>
</form>
{{form.value | json }}
</div>
`
})
export class App {
form = new FormGroup({
username: new FormControl('username', Validators.required)
});
constructor() {
this.name = 'Angular2';
}
sayHello() {
console.log(this.form.controls['username'])
}
}
I have no clue how to solve this problem in a proper way
There exists a Validator interface from Angular to forward the validation to the parent. You need to use it and provide NG_VALIDATORS to the component decorator's providers array with a forwardRef:
{
provide: NG_VALIDATORS,
useExisting: CustomInputComponent,
multi: true
}
your component needs to implement the Validator interface:
class CustomInputComponent implements ControlValueAccessor, Validator,...
and you have to provide implementations of the Validator interfaces' methods, at least of the validate method:
validate(control: AbstractControl): ValidationErrors | null {
return this.formGroup.controls['anyControl'].invalid ? { 'anyControlInvalid': true } : null;
}
there is also another to sync the validators when their inputs change:
registerOnValidatorChange(fn: () => void): void {
this.validatorChange = fn;
}

Angular2 (rc.5) setting a form to invalid with a custom validator

I am using a template-driven form with a custom validator on one of the fields :
<button type="button" (click)="myForm.submit()" [disabled]="!myForm.valid" class="btn">Save</button>
<form #myForm="ngForm" (ngSubmit)="submit()">
...
<input type="text"
class="validate"
[(ngModel)]="myDate"
name="myDate"
ngControl="myDate"
myValidator/>
</form>
myValidator :
import { Directive, forwardRef } from '#angular/core'
import { NG_VALIDATORS, FormControl, Validator } from '#angular/forms'
function check(s: string) {
...
}
function customValidation() {
return (c: FormControl) => {
return check(c.value) ? null : {
myValidator: false
}
}
}
#Directive({
selector: '[myValidator ][ngModel],[myValidator ][formControl]',
providers: [
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => MyValidator), multi: true }
]
})
export class MyValidator implements Validator {
validator: Function
constructor() {
this.validator = customValidation()
}
validate(c: FormControl) {
return this.validator(c)
}
}
Everything is working just fine on the field. The only issues comes when the validator set the field to invalid, the form isn't set to invalid and thus the save button isn't disabled. I can't exactly get why. I guess I forgot a link between the validator and the form.
I am using angular_forms 0.3.0
Here is a plunkr : http://plnkr.co/edit/qUKYGFNLyjh6mNiqYY5I?p=preview which really seems to work... (rc.4 though)
I have put your code in plunkr.
http://plnkr.co/edit/qUKYGFNLyjh6mNiqYY5I?p=preview
And it works just fine. You might check with other parts of your code. Specifically I have my own check function.
function check(s: string) {
if(s.length > 0){
return true;
}else{
return false;
}
}
Have you initialized the myDate value? If it's not initialized i got a valid form on start.
ngOnInit() {
this.myDate = ''
}