Making a date field reactive in Angular - html

I'm trying to make a datepicker component from ngx-bootstrap a custom date-field, so that I can globalize some functionality and configs. But I can't seem to be able to catch the value of the Date object in the date input field.
My date-field.ts (I'm re-using some setup from a text-field. So bear with me if you see some remnants of the text field component. But I'm sure that my main problem is that my component doesn't know it's a date field)
import { Component, OnInit, forwardRef, Input } from '#angular/core';
import { FormControl, ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
#Component({
selector: 'date-field',
templateUrl: './date-field.component.html',
styleUrls: ['./date-field.component.scss'],
providers:[
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => DateFieldComponent),
}
]
})
export class DateFieldComponent implements OnInit, ControlValueAccessor {
public dateField = new FormControl("")
private onChange: (name: string) => void;
private onTouched: () => void
#Input() name: string;
#Input() label: string;
#Input() required: boolean;
datepickerConfig = {
dateInputFormat: 'ddd, MMMM Do YYYY',
isAnimated: true,
adaptivePosition: true,
returnFocusToInput: true,
containerClass: 'theme-dark-blue'
}
constructor() { }
ngOnInit() { }
writeValue(obj: any): void {
const date = Date
this.dateField.setValue(new Date());
console.log(date);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
if (isDisabled) {
this.dateField.disable();
} else {
this.dateField.enable();
}
}
doInput() {
this.onChange(this.dateField.value)
}
doBlur() {
this.onTouched();
}
}
The template HTML:
<label
*ngIf="label"
for="{{name}}"
class="col-auto col-form-label {{required ? 'required' : '' }}">
{{label}}
</label>
<div class="col-expand relative">
<input
type="text"
class="form-control date-field"
#dp="bsDatepicker"
[formControl]="dateField"
(input)="doInput()"
(blur)="doBlur()"
ngModel
bsDatepicker
[bsConfig]="datepickerConfig"
required="{{required}}">
</div>
Using it in parent forms like this:
<date-field
name="dateChartered"
label="Date local union chartered"
formControlName="dateChartered"
required="true">
</date-field>
<p><strong>Date chartered is:</strong> {{dateChartered}}</p>

Assuming in your parent component you're correctly initializing FormGroup in controller and using it correctly in template, you have two main errors in your component.
First, as Belle Zaid says, you should remove ngModel from you custom datepicker's <input>.
Second, you are binding doInput() to (input), but it will fire only if you type in your input field, same for (change). You should bind to (bsValueChange) that's an output event exposed by BsDatepicker and it's safer, unless you plan to update value on user's input.
The resulting template will look like this:
<label *ngIf="label"
for="my-custom-datepicker"
class="col-auto col-form-label {{required ? 'required' : '' }}">
{{label}}
</label>
<div class="col-expand relative">
<input id="my-custom-datepicker"
type="text"
class="form-control date-field"
required="{{required}}"
bsDatepicker
[formControl]="dateField"
[bsConfig]="datepickerConfig"
(blur)="doBlur()"
(bsValueChange)="doInput()">
</div>
Once done the two changes you will notice an error in your console:
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was
checked. Previous value for 'ng-pristine': 'true'. Current value: 'false'.. Find more
at https://angular.io/errors/NG0100
This is due to the fact that calling this.dateField.setValue(obj) in writeValue will also trigger (bsValueChange) and, thus, doInput(). To overcome this issue, you can edit your code like the following:
private componentInit = false;
// ...
doInput() {
if (!this.componentInit) {
this.componentInit = true;
return;
}
this.onChange(this.dateField.value);
}

Related

Not able to set default value of input (Angular)

I have a web page that uses angular form. This page is basically a user data so it shows the name, phone, mail, etc of the user.The data are obtained using a mongoDB database. I want it to show by default the value already set in placeholder and I want for the user to be possible to change it as well. If they don't type anything in the field then the value that should be passed is the placeholder. For example the value {{this.cliente.nome}} when it is in a different field than the input one but when I try to use it as default value of input it doesn't work. It says "Cannot read property 'nome' of undefined" when the other one works. The data comes correct from the mongoDB but it isn't working. This is my code:
import { Component, OnInit } from '#angular/core';
import { UserService } from '../user.service';
import { User } from 'src/user';
import { FormGroup, FormControl, FormArray, FormBuilder } from "#angular/forms";
import { Router } from '#angular/router';
#Component({
selector: 'app-cliente-dados',
templateUrl: './cliente-dados.component.html',
styleUrls: ['./cliente-dados.component.css']
})
export class ClienteDadosComponent implements OnInit {
cliente: User;
updateForm: FormGroup;
constructor(
private userService: UserService,
private formBuilder: FormBuilder,
public router: Router
) {
this.updateForm = this.formBuilder.group({
nomeUpdate: this.formBuilder.control("")
})
}
ngOnInit(): void {
this.getCliente();
}
getCliente(){
this.userService.getUser(localStorage.getItem('userAtual')).subscribe(user => {
this.cliente = user[0];
});
}
updateCliente(updateData){
console.log("update");
}
}
<form [formGroup]="updateForm" (ngSubmit)="updateCliente(updateForm.value)">
<div class="data">
<img id="iconePerfil" src="assets/Imagens/Icones/perfil.png">
<div id="informacaoRegisto">
<div class="container">
<label id="nome"><strong>{{this.cliente.nome}} <!--This one shows the value--></strong></label>
<input class="input" type=" text " [(value)]="this.cliente.nome" formControlName="nomeUpdate"> <!--Here the value isn't shown-->
<span class="border"></span>
</div>
</div>
</div>
<button type="submit" class="submit">Atualizar Dados</button>
</form>
Representation of the issue: top and bottom values are shown(not input field)
Middle one not shown(input field with default value)
You are using FormBuilder and as such the value property of input is not used. The initial value is initialized by the FormControl
Remove the following from the constructor
this.updateForm = this.formBuilder.group({
nomeUpdate: this.formBuilder.control("")
})
Modify getCliente()
getCliente(){
this.userService.getUser(localStorage.getItem('userAtual'))
.pipe(first()).subscribe(user => {
this.cliente = user[0];
this.updateForm = this.formBuilder.group({
nomeUpdate: this.formBuilder.control(this.cliente.nome)
})
});
}
Have added .pipe(first()) so only first value received is used.
Hope it helps!

Angular 6 input range validation

I have to validate one of the input element in my ngForm
How to validate range while submit that form
For example user should enter the price between $1000 to $2000
Please give the suggestions to proceed
Thanks
Try this
<form [formGroup]="myForm">
<label for="priceRange">Price Range: </label>
<input type="number" id="priceRange" formControlName="priceRange">
<div *ngIf="f.priceRange.errors">
Invalid Price Range
</div>
in component.ts
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '#angular/forms';
myForm = new FormGroup({});
get f() {
return this.myForm.controls;
}
constructor(private formBuilder: FormBuilder){
this.myForm = formBuilder.group({
priceRange: ['', [Validators.min(1000), Validators.max(2000)]]
});
}
Try this working Example
link
<h3>Reactive Forms Validator Example</h3>
<br>
<label>We have created two validators using slightly different approaches that validate if the input is equal to "A" or not.</label>
<br><br>
<label>Doesn't work because the method is not executed which will return the custom validator function.</label><br>
<input type="text" [formControl]="wrongOnlyA">
<br><br>
<label>Works because the method is executed.</label><br>
<input type="text" [formControl]="correctOnlyA">
<br><br>
<label>Works because the validator function is directly exported.</label><br>
<input type="text" [formControl]="onlyB">
.ts
import { Component, OnInit } from '#angular/core';
import { FormGroup } from "#angular/forms"
import { RxFormBuilder } from '#rxweb/reactive-form-validators';
import { FormBuilderConfiguration } from '#rxweb/reactive-form-validators';
import { EmployeeInfo } from '../employee-info.model';
#Component({
selector: 'app-employee-info-add',
templateUrl: './employee-info-add.component.html'
})
export class EmployeeInfoAddComponent implements OnInit {
employeeInfoFormGroup: FormGroup
constructor(
private formBuilder: RxFormBuilder
) { }
ngOnInit() {
let employeeInfo = new EmployeeInfo();
let formBuilderConfiguration = new FormBuilderConfiguration();
formBuilderConfiguration.validations = {
age : {
range : {minimumNumber:18,maximumNumber:60,}
},
experience : {
range : {minimumNumber:2,maximumNumber:20,conditionalExpression:'x => x.age >=25',}
},
salary : {
range : {minimumNumber:1000,maximumNumber:200000,message:'Your Salary should be between 10000 to 200000.',}
},
};
this.employeeInfoFormGroup = this.formBuilder.formGroup(employeeInfo,formBuilderConfiguration);
}
}

Input properties in angular directives

I'm having a problem with input properties in angular directive used in my reactive form (Side question here: Is it a good practice to use directives in reactive forms?). I want to create universal directive for cross field validation that would take as an input two strings each for the name of a field to validate. Here's some code to illustrate what I mean.
// Directive
import { Directive, Input, OnInit } from '#angular/core';
import { Validator, AbstractControl, NG_VALIDATORS } from '#angular/forms';
#Directive({
selector: '[appConfirmEqualValidator]',
providers: [
{
provide: NG_VALIDATORS,
useClass: ConfirmEqualValidatorDirective,
multi: true,
}
],
})
export class ConfirmEqualValidatorDirective implements OnInit, Validator {
#Input() confEqSecControlName: string // = 'password' ; would work if uncommented
#Input() confEqFirstControlName: string // = 'confirmPassword' ;
constructor() {
}
ngOnInit(): void {
}
validate(controlGroup: AbstractControl): { [key: string]: any; } {
console.log(this.confEqFirstControlName + ' ' + this.confEqSecControlName);
let firstControl = controlGroup.get(this.confEqFirstControlName);
let secControl = controlGroup.get(this.confEqSecControlName);
console.log(firstControl.value + ' fc ' + secControl.value + ' sec');
if (firstControl && secControl && ( firstControl.value !== secControl.value )) {
return { 'notEqual': true};
} else {
return null;
}
}
registerOnValidatorChange?(fn: () => void): void {
throw new Error("Method not implemented.");
}
}
// HTML
<!-- Password Group -->
<div formGroupName="passwordGroup" appConfirmEqualValidator [confEqFirstControlName]="'password'" [confEqSecControlName]="'confirmPassword'">
<!-- Password -->
<div class="form-group">
<label class="control-label" for="passwordId">Password</label>
<div>
<input class="form-control" formControlName="password" type="text" id="passwordId" placeholder="Password (required)" />
<div class="text-danger" *ngIf="signUpForm.get('passwordGroup.password').errors">Danger</div>
</div>
</div>
<!-- Confirm Password -->
<div class="form-group">
<label class="control-label" for="confirmPasswordId">Confirm Password</label>
<div>
<input class="form-control" formControlName="confirmPassword" type="text" id="confirmPasswordId" placeholder="Confirm Password (required)"/>
</div>
</div>
<div class="text-danger" *ngIf="signUpForm.get('passwordGroup').errors?.notEqual">Not Equal</div>
</div>
pastebin for clarity
I'm new to angular but from what I've read in the docs this should work just fine. Well It isn't. The values are not being bound to properties at all, and the console logs 'undefined'. Everything works just fine when I specify default values, but well then with hard-coded values I'd have to make one directive/function for each set of fields. I'd be very grateful for some help, thanks.

Binding ngModel to complex data

For some time I have been researching if, and how to bind complex model to ngModel. There are articles showing how it can be done for simple data (e.g. string) such as this. But what I want to do is more complex. Let's say that I have a class:
export class MyCoordinates {
longitude: number;
latitude: number;
}
Now I am going to use it in multiple places around the application, so I want to encapsulate it into a component:
<coordinates-form></coordinates-form>
I would also like to pass this model to the component using ngModel to take advantage of things like angular forms but was unsuccessful thus far. Here is an example:
<form #myForm="ngForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="name">Name</label>
<input type="text" [(ngModel)]="model.name" name="name">
</div>
<div class="form-group">
<label for="coordinates">Coordinates</label>
<coordinates-form [(ngModel)]="model.coordinates" name="coordinates"></coordinates-form>
</div>
<button type="submit" class="btn btn-success">Submit</button>
</form>
Is actually possible to do it this way or is my approach simply wrong? For now I have settled on using component with normal input and emitting event on change but I feel like it will get messy pretty fast.
import {
Component,
Optional,
Inject,
Input,
ViewChild,
} from '#angular/core';
import {
NgModel,
NG_VALUE_ACCESSOR,
} from '#angular/forms';
import { ValueAccessorBase } from '../Base/value-accessor';
import { MyCoordinates } from "app/Models/Coordinates";
#Component({
selector: 'coordinates-form',
template: `
<div>
<label>longitude</label>
<input
type="number"
[(ngModel)]="value.longitude"
/>
<label>latitude</label>
<input
type="number"
[(ngModel)]="value.latitude"
/>
</div>
`,
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: CoordinatesFormComponent,
multi: true,
}],
})
export class CoordinatesFormComponent extends ValueAccessorBase<MyCoordinates> {
#ViewChild(NgModel) model: NgModel;
constructor() {
super();
}
}
ValueAccessorBase:
import {ControlValueAccessor} from '#angular/forms';
export abstract class ValueAccessorBase<T> implements ControlValueAccessor {
private innerValue: T;
private changed = new Array<(value: T) => void>();
private touched = new Array<() => void>();
get value(): T {
return this.innerValue;
}
set value(value: T) {
if (this.innerValue !== value) {
this.innerValue = value;
this.changed.forEach(f => f(value));
}
}
writeValue(value: T) {
this.innerValue = value;
}
registerOnChange(fn: (value: T) => void) {
this.changed.push(fn);
}
registerOnTouched(fn: () => void) {
this.touched.push(fn);
}
touch() {
this.touched.forEach(f => f());
}
}
Usage:
<form #form="ngForm" (ngSubmit)="onSubmit(form.value)">
<coordinates-form
required
hexadecimal
name="coordinatesModel"
[(ngModel)]="coordinatesModel">
</coordinates-form>
<button type="Submit">Submit</button>
</form>
The error I am getting Cannot read property 'longitude' of undefined. For simple model, like string or number it works without a problem.
The value property is undefined at first.
To solve this issue you need to change your binding like:
[ngModel]="value?.longitude" (ngModelChange)="value.longitude = $event"
and change it for latitude as well
[ngModel]="value?.latitude" (ngModelChange)="value.latitude = $event"
Update
Just noticed you're running onChange event within settor so you need to change reference:
[ngModel]="value?.longitude" (ngModelChange)="handleInput('longitude', $event)"
[ngModel]="value?.latitude" (ngModelChange)="handleInput('latitude', $event)"
handleInput(prop, value) {
this.value[prop] = value;
this.value = { ...this.value };
}
Updated Plunker
Plunker Example with google map
Update 2
When you deal with custom form control you need to implement this interface:
export interface ControlValueAccessor {
/**
* Write a new value to the element.
*/
writeValue(obj: any): void;
/**
* Set the function to be called when the control receives a change event.
*/
registerOnChange(fn: any): void;
/**
* Set the function to be called when the control receives a touch event.
*/
registerOnTouched(fn: any): void;
/**
* This function is called when the control status changes to or from "DISABLED".
* Depending on the value, it will enable or disable the appropriate DOM element.
*
* #param isDisabled
*/
setDisabledState?(isDisabled: boolean): void;
}
Here is a minimal implementation:
export abstract class ValueAccessorBase<T> implements ControlValueAccessor {
// view => control
onChange = (value: T) => {};
onTouched = () => {};
writeValue(value: T) {
// control -> view
}
registerOnChange(fn: (_: any) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
}
It will work for any type of value. Just implement it for your case.
You do not need here array like (see update Plunker)
private changed = new Array<(value: T) => void>();
When component gets new value it will run writeValue where you need to update some value that will be used in your custom template. In your example you are updating value property which is used together with ngModel in template.
In my example i am drawing new marker.
DefaultValueAccessor just updates value property https://github.com/angular/angular/blob/4.2.0-rc.0/packages/forms/src/directives/default_value_accessor.ts#L76
Datepicker in angular2 material is setting inner value as you do https://github.com/angular/material2/blob/123d7eced4b4f808fc03c945504d68280752d533/src/lib/datepicker/datepicker-input.ts#L202
When you need to propagate changes to AbstractControl you have to call onChange method which you registered in registerOnChange.
I wrote this.value = { ...this.value }; because it is just
this.value = Object.assign({}, this.value)
it will call setter where you call onChange method
Another way is calling onChange directly that is usually used
this.onChange(this.value);
Your example https://plnkr.co/edit/Q11HXhWKrndrA8Tjr6KH?p=preview
DefaultValueAccessor https://github.com/angular/angular/blob/4.2.0-rc.0/packages/forms/src/directives/default_value_accessor.ts#L88
Material2 https://github.com/angular/material2/blob/123d7eced4b4f808fc03c945504d68280752d533/src/lib/datepicker/datepicker-input.ts#L173
You can do anything you like inside custom component. It can have any template and any nested components. But you have to implement logic for ControlValueAccessor to do it working with angular form.
If you open some library such angular2 material or primeng you can find a lot of example how to implement such controls

Angular 2 dialog popup with text field input

I have a simple popup that shows a message with OK button to release it, what I want is to add to this popup a text field that the user will need to type something in it and click OK to submit his answer.
currently it looks like this:
#Component({
selector: 'dialog-component',
template: `<h2>{{title}}</h2>
<p>{{message}}</p>
<button md-button (click)="dialog.close()">OK</button>`
})
export class DialogComponent {
public title: string;
public message: string;
constructor( public dialog: MdDialogRef<DialogComponent>) { }
}
and im using it like:
public showDialog(message: MessageBoxMessage) {
if (typeof message !== 'undefined') {
let dialogRef: MdDialogRef<DialogComponent> = this._dialog.open(DialogComponent);
dialogRef.componentInstance.title = message.title;
dialogRef.componentInstance.message = message.content;
}
}
how can I change it to have a popup with text-field and ok button tht pass me the value of the text-field?
You can create EventEmitter in your dialog:
#Component({
selector: 'dialog-component',
template: `<h2>{{title}}</h2>
<p>{{message}}</p>
<mat-form-field class="example-full-width">
<input matInput placeholder="Favorite food" #input>
</mat-form-field>
<button mat-button (click)="onOk.emit(input.value); dialog.close()">OK</button>`
})
export class DialogComponent {
public title: string;
public message: string;
onOk = new EventEmitter();
constructor( public dialog: MatDialogRef<ErrorDialogComponent>) { }
}
then subscribe to it in parent component
dialogRef.componentInstance.onOk.subscribe(result => {
this.resultFromDialog = result;
})
Plunker Example
Another way is passing value to MatDialog.close method
(click)="dialog.close(input.value)"
....
dialogRef.afterClosed().subscribe(result => {
this.resultFromDialog = result;
});
Plunker Example
You can bind the answer to a model like this :
#Component({
selector: 'dialog-component',
template: `<h2>{{title}}</h2>
<p>{{message}}</p>
<input type="text" name="data" [(ngModel)]="data">
<button md-button (click)="dialog.close()">OK</button>`
})
export class DialogComponent {
public title: string;
public message: string;
data: string;
constructor( public dialog: MdDialogRef<ErrorDialogComponent>) { }
}
And then bind the (click) to a function that sends your data.