Reputation: 39
I am currently developing a quite long form using Angular 2 RC4 and the new form API.
I have my own input component and would like to take it one step further to handle validation.
I am trying to have my validators working with my input however the validator updates the component InputFieldComponent instead of my input field...
here is my html:
<label [attr.for]="inputId"><ng-content></ng-content></label>
<small class="text-muted">{{helptext}}</small>
<small [hidden]="(input.valid) || (input.untouched && input.pristine)" class="text-danger">
{{errormsg}}
</small>
<input
class="form-control custom-input"
[id]="inputId"
[required]="required"
[type]="type"
[attr.name]="name"
[(ngModel)]="value"
[pattern]="pattern"
#input="ngModel"
>
called by
<custom-input-field
name="birthDate"
[(ngModel)]="model.birthDate"
placeholder="DD/MM/YYYY"
helptext="Date of birth"
required="true"
pattern="^[0-3]?[0-9]\/[01]?[0-9]\/[12][90][0-9][0-9]$"
errormsg="The date of birth is mandatory and should be entered as dd/mm/yyyy"
>
Date of birth
</custom-input-field>
Here is my code :
import {
Component,
Input,
forwardRef,
} from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
const noop = () => {};
let nextUniqueId = 0;
export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputFieldComponent),
multi: true
};
@Component({
moduleId: module.id,
selector: 'custom-input-field',
styleUrls: ['input-field.component.css'],
templateUrl: 'input-field.component.html',
providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
})
export class InputFieldComponent implements ControlValueAccessor {
//the external properties
@Input() id: string = `custom-field-${nextUniqueId++}`;
@Input() helptext: string = null;
@Input() placeholder: string = null;
@Input() required: boolean = false;
@Input() type: string = 'text';
@Input() name: string = null;
//The internal data model
private innerValue: any = '';
//Placeholders for the callbacks which are later provided
//by the Control Value Accessor
private onTouchedCallback: () => void = noop;
private onChangeCallback: (_: any) => void = noop;
//get accessor
get value(): any {
return this.innerValue;
};
//set accessor including call the onchange callback
set value(v: any) {
if (v !== this.innerValue) {
this.innerValue = v;
this.onChangeCallback(v);
}
}
//From ControlValueAccessor interface
// The writeValue function allows you to update your internal model with incoming values,
// for example if you use ngModel to bind your control to data.
writeValue(value: any) {
if (value !== this.innerValue) {
this.innerValue = value;
}
}
//NOTE: Both following functions are later provided by Angular 2 itself. But we need to register dummy functions to be
// able code and transpile it without errors.
//From ControlValueAccessor interface
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
//From ControlValueAccessor interface
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
//** Read-only properties */
get inputId(): string { return `${this.id}-input`; }
};
Upvotes: 2
Views: 1573
Reputation: 39
I finally found a workaround to achieve what I wanted.
Expression 'xxxx' was changed after it was checked
)In ended up using the custom component shadow dom to test if the component was valid and it works and make the code even lighter.
here is the code of my custom input component with its CSS selectors
@Component({
moduleId: module.id,
selector: 'custom-input-field',
templateUrl: 'input-field.component.html',
providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR],
directives: [DefaultValueAccessor, NgModel],
host: {
'(click)': 'focus()',
'[class.custom-input]': 'true',
},
/**
* This styles the input error msg that is integrated into our component using the automatic classes
* that ngModel updates on the component.
*
* It uses the shadow-dom and therefore has to be declared line by line in the component file
* (does not seem to work with Sass transpiler)
*/
styles: [
`
:host.ng-touched.ng-invalid >>> input {
border-left: 5px solid #a94442; /* red */
}
:host.ng-touched.ng-valid >>> input {
border-left: 5px solid #42A948; /* green */
}
:host.ng-valid:not([required]).ng-touched.ng-dirty >>> input {
border-left: 5px solid #42A948; /* green */
}
:host.ng-pristine >>> .error-msg {
display:none;
}
:host.ng-valid >>> .error-msg {
display:none;
}
:host.ng-untouched >>> .error-msg {
display:none;
}
:host.ng-touched.ng-invalid >>> .error-msg {
display:inline;
}
.text-danger { font-weight: 500; }
}
`]
})
and here is the template content (enriched of more properties):
<label [attr.for]="inputId"><ng-content></ng-content></label>
<small class="text-muted">{{helptext}}</small>
<small class="error-msg text-danger">
<ng-content select="input-error"></ng-content>
</small>
<input #input
class="form-control custom-input"
[disabled]="disabled"
[id]="inputId"
[attr.list]="list"
[attr.max]="max"
[attr.maxlength]="maxLength"
[attr.min]="min"
[attr.minlength]="minLength"
[readonly]="readOnly"
[required]="required"
[spellcheck]="spellCheck"
[attr.step]="step"
[attr.tabindex]="tabIndex"
[type]="type"
[attr.name]="name"
(focus)="_handleFocus($event)"
(blur)="_handleBlur($event)"
(change)="_handleChange($event)"
[(ngModel)]="value"
>
I call my component as such:
<custom-input-field
name="birthDate"
[(ngModel)]="model.birthDate"
placeholder="JJ/MM/AAAA"
helptext="format jj/mm/aaaa"
#birthDate="ngModel"
required="true"
pattern="^[0-3]?[0-9]\/[01]?[0-9]\/[12][90][0-9][0-9]$"
>
Date of birth
<input-error><br />The date of birth is mandatory and should be entered as dd/mm/yyyy</input-error>
</custom-input-field>
Upvotes: 1