I am using ngx-bootstrap for date picker, my date picker has input format "dd/MM/yyyy" like 25/07/2019
so I create an component for capturing the date and convert it into my reactive form (because mysql accept date as yyyy-mm-dd). I use ngModel in input and doing value changes everytime user did input value.
the code as follow :
import { environment } from './../../../environments/environment';
import { Component, OnInit, Input } from '#angular/core';
import { BsDatepickerConfig } from 'ngx-bootstrap';
import { FormGroup } from '#angular/forms';
import * as moment from "moment";
#Component({
selector: 'app-input-date',
templateUrl: './input-date.component.html',
styleUrls: ['./input-date.component.css']
})
export class InputDateComponent implements OnInit {
#Input() form: FormGroup;
#Input() fieldName: string;
#Input() fieldDescription: string;
datePickerConfig: Partial<BsDatepickerConfig>;
tempDate: any ;
isInvalid: boolean;
constructor() {
this.datePickerConfig = Object.assign(
{},
{
containerClass: "theme-dark-blue",
dateInputFormat: environment.dateFormat
}
);
}
ngOnInit() {
let theDate = this.form.get(this.fieldName).value;
if( theDate != ""){
this.tempDate = moment(theDate, environment.mysqlDateFormat).format(environment.dateFormat);
}
}
get myField()
{
return this.form.get(this.fieldName);
}
// For date
onDateModelChange(event)
{
if(this.tempDate != ""){
this.form.patchValue({
[this.fieldName]: moment(this.tempDate, environment.dateFormat).format(environment.mysqlDateFormat)
});
}else{
this.form.patchValue({
[this.fieldName]: ""
});
}
console.log(this.form.get(this.fieldName).errors);
}
}
<input type="text" class="form-control mydatepicker"
placeholder="" id="mydatepicker"
[ngClass]="{'has-error': myField.touched && myField.hasError('required')}"
[(ngModel)]="tempDate" (ngModelChange)="onDateModelChange($event)" [ngModelOptions]="{standalone: true}"
[bsConfig]="datePickerConfig" bsDatepicker>
Now, I need to display error if the user forgot to enter the date or the date input was invalid. I set the reactiveform using Validators.required like
this.form = this.formBuilder.group({ dateOfBirth: ["", Validators.required]});
But since I use ngModel it wont validated. I just wonder how to get the field validated everytime user click / entering the input (pristine or dirty)
I tried this code on my InputDateComponent but it always return null
onDateModelChange(event){
console.log(this.form.get(this.fieldName).errors);
}
If you have simple solutions for date format, I would like to know as well..
Related
I am working on an Angular project and I have an object of type Project that contains the following values:
cost: 56896 and
costHR: 27829
I want to modify the object using a form and bind the fields with ngModel to the attributes.
But the problem I am facing is that in the field, I want to display the number in a currency format (e.g. 56,896€) which is not compatible with the variable type which is float.
Can someone help me with a solution to this problem? I have tried using built-in Angular pipes and custom formatter and parser functions, but none of them seem to work as expected.
Any help would be greatly appreciated.
import { Component, OnInit } from '#angular/core';
import { CurrencyPipe } from '#angular/common';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [CurrencyPipe]
})
export class AppComponent implements OnInit {
project = {
cost: 56896,
costRH: 27829
}
constructor(private currencyPipe: CurrencyPipe) { }
ngOnInit() {
this.project.cost = this.currencyPipe.transform(this.project.cost, 'EUR', 'symbol', '1.0-0');
this.project.costRH = this.currencyPipe.transform(this.project.costRH, 'EUR', 'symbol', '1.0-0');
}
onBlur(event, projectProperty) {
this.project[projectProperty] = this.currencyPipe.parse(event.target.value);
}
}
<form>
<label for="cost">Cost</label>
<input [(ngModel)]="project.cost" (blur)="onBlur($event, 'cost')" [ngModelOptions]="{updateOn: 'blur'}" [value]="project.cost | currency:'EUR':'symbol':'1.0-0'">
<label for="costRH">Cost RH</label>
<input [(ngModel)]="project.costRH" (blur)="onBlur($event, 'costRH')" [ngModelOptions]="{updateOn: 'blur'}" [value]="project.costRH | currency:'EUR':'symbol':'1.0-0'">
</form>
What I expected:
I expected the input fields to be formatted as currency (e.g. 56,896€) and for the corresponding properties in the 'projet' object (cost and costRH) to be updated with the parsed value when the input loses focus.
What happened instead:
The value displayed in the input fields is not formatted as currency and the corresponding properties in the object are not being updated as expected.
you can use a directive like
#Directive({
selector: '[format]',
})
export class FormatDirective implements OnInit {
format = 'N0';
digitsInfo = '1.0-0';
#Input() currency = '$';
#Input() sufix = '';
#Input() decimalCharacter = null;
#Input('format') set _(value: string) {
this.format = value;
if (this.format == 'N2') this.digitsInfo = '1.2-2';
const parts = value.split(':');
if (parts.length > 1) {
this.format = value[0];
this.digitsInfo = parts[1];
}
}
#HostListener('blur', ['$event.target']) blur(target: any) {
target.value = this.parse(target.value);
}
#HostListener('focus', ['$event.target']) focus(target: any) {
target.value = this.control.value;
}
ngOnInit() {
setTimeout(() => {
this.el.nativeElement.value = this.parse(this.el.nativeElement.value);
});
}
constructor(
#Inject(LOCALE_ID) private locale: string,
private el: ElementRef,
private control: NgControl
) {}
parse(value: any) {
let newValue = value;
if (this.format == 'C2')
newValue = formatCurrency(value, this.locale, this.currency);
if (this.format == 'N2')
newValue = formatNumber(value, this.locale, this.digitsInfo);
if (this.format == 'N0')
newValue = formatNumber(value, this.locale, this.digitsInfo);
if (this.format == 'NX')
newValue = formatNumber(value, this.locale, this.digitsInfo);
if (this.decimalCharacter)
return (
newValue.replace('.', 'x').replace(',', '.').replace('x', ',') +
this.sufix
);
return newValue + this.sufix;
}
}
Works like
<input format="N0" [(ngModel)]="value"/>
<input format="N2" [(ngModel)]="value"/>
<input format="C2" [(ngModel)]="value"/>
<input format="N2" decimalCharacter='.' sufix=' €' [(ngModel)]="value"/>
The idea of the directive is that, when blur, change the "native Element", but not the value of the ngControl, when focus return the value of the ngControl
The stackblitz
I have used Numberal js for formatting numbers and it was awesome
Your desired format is this one '0,0[.]00 €'
check docs
The link to the stackblitz https://stackblitz.com/edit/angular-ivy-csqein?file=src/app/hello.component.html
When I copy-paste a value into the INPUT box it returns the correct data
INPUT - 12345678 OUTPUT - 12,345,678
but when I input values one by one it is not able to format it
the output looks like this 1,234567
Expected OUTPUT
When the input is first loaded it comes with the comma-separated digits.
I want to make it so that when the users add or delete values in the input box, the commas are updated in the very box.
Things that I have tried
Creating custom Pipe and updating the value in the component using valueChanges
You can't transform input value when you using formControlName, because when you want to transform that value, angular give you a formControl that you can use to change that value.
For example in your code, you can do this:
constructor(private fb: FormBuilder, private currencyPipe: CurrencyPipe) {}
profileForm;
ngOnInit() {
this.profileForm = this.fb.group({
name: this.currencyPipe.transform(12345678, '', '','0.0'),
});
// this.changes();
}
Note: Don't forget to provide CurrencyPipe in your module.
The code above it's the only way or solution you can do to change the input value when you use the formControlName.
Or another way you can use is remove your formControlName from your input and it will working fine.
<input
id="number"
[value]="profileForm.get('name').value | number"
maxlength="8"
/>
But the problem is you should have to do it manually to patch value from input to your formControl. You can use (input) like what I do in custom input component below.
Custom Input Component
If you like to using custom input component, then code below maybe can help you to resolve your question:
You can create app-input.ts and put this code below:
import { CurrencyPipe } from '#angular/common';
import {
Component,
forwardRef,
HostListener,
Input,
OnInit,
} from '#angular/core';
import {
ControlContainer,
ControlValueAccessor,
FormControl,
FormGroup,
NG_VALUE_ACCESSOR,
} from '#angular/forms';
#Component({
selector: 'app-input[type=currency]',
templateUrl: './currency.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CurrencyComponent),
multi: true,
},
],
})
export class CurrencyComponent implements ControlValueAccessor, OnInit {
#Input() formControlName: string;
value: any;
onChange: () => any;
onTouche: () => any;
public formGroup: FormGroup;
public formControl: FormControl;
constructor(
private controlContainer: ControlContainer,
private currencyPipe: CurrencyPipe
) {}
ngOnInit() {
console.log('Currency Component');
console.log(this.controlContainer);
this.setStateInitialization();
}
private setStateInitialization(): void {
this.formGroup = this.controlContainer.control as FormGroup;
this.formControl = this.formGroup.get(this.formControlName) as FormControl;
}
writeValue(value: any): void {
this.value = value;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouche = fn;
}
#HostListener('input', ['$event'])
private _onHostListenerInput(event: InputEvent): void {
const inputElement = event.target as HTMLInputElement;
let value: string | number = inputElement.value;
if (value) value = +inputElement.value.replace(/\D/g, '');
this.formControl.patchValue(value);
inputElement.value = value
? this.currencyPipe.transform(value, '', '', '0.0')
: '';
}
}
Now you can add app-input.html and use code below:
<input
type="text"
[value]="formControl.value ? (formControl.value | currency: '':'':'0.0') : ''"
/>
After that if you want to use this component, you can call:
<app-input type="currency" formControlName="currency"></app-input>
Or whatever name you want, you can change it.
Update:
Live Preview: https://angular-ivy-aqppd6.stackblitz.io
Live Code: https://stackblitz.com/edit/angular-ivy-aqppd6?file=src/app/app.component.ts
I hope it can help you to imagine what you can do to resolve your question.
I want to implement custom validation in Angular 11.
My component.ts is as follows:
import { Component, OnInit } from '#angular/core';
import { FormControl, PatternValidator } from '#angular/forms';
#Component({
selector: 'app-microplate',
templateUrl: './microplate.component.html',
styleUrls: ['./microplate.component.css']
})
export class MicroplateComponent {
columns = new FormControl('', isValid(???));
isValid(???) {
return some logic based on the ???;
}
}
My component.html is as follows (without any tag. Just as shown here):
<label for="columns"><b>Columns: </b></label>
<input id="columns" type="text" [formControl]="columns">
In the above code, by ??? I mean the value of the columns field, but I don't know how to send the value of the columns field to the isValid() method. I don't know how to do a custom validation even after hours of searching in google. I also want to show an error message in the component.html if the isValid() method returns false, but I don't know how to do that.
defining a custom validator is already well explained in the docs
import { AbstractControl, ValidatorFn } from "#angular/forms";
export function isValid(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } => {
// ??? <--> control.value
};
}
As written on Angular.io:
https://angular.io/guide/form-validation#adding-custom-validators-to-reactive-forms
You can insert a directive, export it and insert it into a FormControl.
this.heroForm = new FormGroup({
name: new FormControl(this.hero.name, [
Validators.required,
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
])
});
forbiddenNameValidator is:
import { AbstractControl, ValidatorFn } from "#angular/forms";
/** A hero's name can't match the given regular expression */
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } | null => {
const forbidden = nameRe.test(control.value);
return forbidden ? { forbiddenName: { value: control.value } } : null;
};
}
Below is a link with an example of implementation:
https://stackblitz.com/edit/angular-ivy-m89a31?file=src%2Fapp%2Fapp.component.ts
I mocked up a very small example of my problem here: https://github.com/lovefamilychildrenhappiness/AngularCustomComponentValidation
I have a custom component, which encapsulates an input field. The formControl associated with this input field has Validators.required (it is a required field). Inside the custom component, I have an onChange event which is fired when text is entered. I check if field is empty; if so, I add css class using ngClass. I also have set the registerOnChange of NG_VALUE_ACCESSOR, so I notify the form when the input changes. Finally, I implement NG_VALIDATORS interface to make the formControl invalid or valid.
My problem is I have a button that is clicked (it's not the submit button). When this button is clicked, I need to check if the custom component is blank or not, and if it is, change the css class and make the form invalid. I think the validate method of NG_VALIDATORS is doing that. But I need to change the css class of customComponent so background turns red. I spend severals hours on this and cannot figure it out:
// my-input.component.html
<textarea
[value]="value"
(input)="onChange($event.target.value)"
[ngClass]="{'failed-validation' : this.validationError }">
</textarea>
// my-input.component.ts
validate(control: FormControl): ValidationErrors | null {
if(!this.validationError){
return null
} else {
return { required: true };
}
}
private onChange(val) {
if(val.length > 0) {
this.value = val
this.validationError = false;
} else {
this.validationError = true;
}
// update the form
this.propagateChange(val);
}
// app.component.html
<form [formGroup]="reactiveForm">
<app-my-input formControlName="result"></app-my-input>
<input
value="Submit"
(click)="nextStep($event)"
type="button">
</form>
// app.component.ts
private nextStep(event){
// How do I dynamically change the class of the form control so I can change the style if formControl invalid when clicking the nextStep button
// pseudocode:
// if( !this.reactiveForm.controls['result'].valid ){
// this.reactiveForm.controls['result'].addClass('failed-validation');
// }
}
How can I get the css of the formControl to change in another component?
Since you using reactive form I have modified your custom form control. Here I have Use Injected NgControl Which is base class for all FormControl-based directives extend.
Try this:
import { Component, Input, forwardRef, OnInit } from "#angular/core";
import {
ControlValueAccessor,
NG_VALUE_ACCESSOR,
NgControl,
NG_VALIDATORS,
FormControl,
ValidationErrors,
Validator
} from "#angular/forms";
#Component({
selector: "app-my-input",
templateUrl: "./my-input.component.html",
styleUrls: ["./my-input.component.scss"]
})
export class MyInputComponent implements ControlValueAccessor, OnInit {
private propagateChange = (_: any) => {};
value = "";
onTouch: () => void;
constructor(public controlDir: NgControl) {
controlDir.valueAccessor = this;
}
writeValue(value) {
this.value = value;
}
registerOnChange(fn) {
this.propagateChange = fn;
}
registerOnTouched(fn) {
this.onTouch = fn;
}
onChange(value) {
this.propagateChange(value);
}
ngOnInit() {
const control = this.controlDir.control;
control.setValidators([control.validator ? control.validator : null]);
control.updateValueAndValidity();
}
}
Example
For More Information Forms Check this
I got a custom dropdown where value from selected dropdown will bind in input value in my reactive form using ngModel, also I had applied mask in my input fields using pipe, here is all relevant code :
html file
<form [formGroup]="creditCardForm" (ngSubmit)="submitCreditCardForm()">
<input
[ngModel]="selectedCard.cardNumberMasked | creditCardMask: true"
(ngModelChange)="selectedCard.cardNumberMasked = $event"
type="text"
formControlName="creditCardNumber">
</form>
ts file
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
export class MyComponent implements OnInit {
creditCardForm: FormGroup;
constructor(
private builder: FormBuilder
) {
this.creditCardForm = builder.group({
creditCardNumber: ['', Validators.required]
});
}
}
mask.pipe.ts
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'creditCardMask',
})
export class CreditCardMaskPipe implements PipeTransform {
transform(value: string, showMask: boolean): string {
if (typeof value === 'number') {
value = JSON.stringify(value);
}
if (!showMask || (value.length < 16 && value.length < 15)) {
return value;
}
return `${value.substr(0, 4)} ${'\u2022'.repeat(4)} ${'\u2022'.repeat(
4
)} ${value.substr(value.length - 4, value.length)}`;
}
}
I know using ngModel in reactive form is deprecated but I need to use it for binding value from selected dropdown, this is the error I get in my console:
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined: 512345444444444'. Current value: 'undefined: 5123 •••• •••• 4444'.
error
any suggestion on how to solve this or any better practice ? thanks
You shouldn't use ngModel along side formControlName, for using pipe you can try using [value] and then assign formControl value to it to update value every time you insert new value to input:
<form [formGroup]="creditCardForm" (ngSubmit)="submitCreditCardForm()">
<input
[value]="creditCardForm.get('creditCardNumber').value | creditCardMask: true"
type="text"
formControlName="creditCardNumber">
</form>
DEMO 🚀🚀