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.
Related
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);
}
I made an input box for an IP Address with Port. This input box is made of 5 inputs box within a div. This is a basic example.
<div class="form-inline" style="display: inline-block">
<input required name="block1" class="form-control" #block1="ngModel" type="text" [(ngModel)]="ipBlock1">.
<input required name="block2" class="form-control" #block2="ngModel" type="text" [(ngModel)]="ipBlock2">.
<input required name="block3" class="form-control" #block3="ngModel" type="text" [(ngModel)]="ipBlock3">.
<input required name="block4" class="form-control" #block4="ngModel" type="text" [(ngModel)]="ipBlock4">:
<input required name="block5" class="form-control" #block5="ngModel" type="text" [(ngModel)]="ipBlock5">
</div>
In ip-address-input.component.ts file I have:
#Input() ipProtocol
#Output() ipAddressCreated: EventEmitter<any> = new EventEmitter<{ipAddress: string}>();
ipBlock1: string;
ipBlock2: string;
//some logic like string concatenation (ipBlock1 + '.' + ipBlock2 + '.' + ...)
In app.component.html:
<ip-address-input [ipProtocol]="dhcpRangeStart"></ip-address-input>
<ip-address-input [ipProtocol]="dhcpRangeStop"></ip-address-input>
But when I check for example for the first IP it returns the last entered IP. How I can make this component reusable (make multiple instances of it)
Image with the actual IP Input box:
I've built a few controls similar to yours by leveraging ControlValueAccessor and NG_VALUE_ACCESSOR from angular forms. Basically, these provide you with a pattern to build your own custom and reusable form controls.
Below is an example code but you can also follow this tutorial to get your component built.
Component:
import { Component, OnInit, forwardRef } from '#angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl } from '#angular/forms';
const IP_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => IpInputComponent),
multi: true
};
#Component({
selector: 'ip-address-input',
providers: [IP_VALUE_ACCESSOR],
templateUrl: './ip-input.component.html',
styleUrls: ['./ip-input.component.css']
})
export class IpInputComponent implements OnInit, ControlValueAccessor {
ipBlock1: string;
ipBlock2: string;
ipBlock3: string;
ipBlock4: string;
ipBlock5: string;
disabled: boolean;
onChange: Function;
onTouched: Function;
get value(): string {
return ipBlock1 + '.' + ipBlock2 + '.' + ipBlock3 + '.' + ipBlock4 + ':' + ipBlock5;
}
constructor() {
this.onChange = (_: any) => {};
this.onTouched = () => {};
this.disabled = false;
}
ngOnInit() {
}
writeValue(obj: any): void {
if(obj) {
let arr = obj.split('.');
this.ipBlock1 = arr[0];
this.ipBlock2 = arr[1];
this.ipBlock3 = arr[2];
this.ipBlock4 = arr[3].split(':')[0];
this.ipBlock5 = arr[3].split(':')[1];
}
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled;
}
}
Template:
<div class="form-inline" style="display: inline-block">
<input required class="form-control" type="number" [ngModel]="ipBlock1" />.
<input required class="form-control" type="number" [ngModel]="ipBlock2" />.
<input required class="form-control" type="number" [ngModel]="ipBlock3" />.
<input required class="form-control" type="number" [ngModel]="ipBlock4" />:
<input required class="form-control" type="number" [ngModel]="ipBlock5" />
</div>
Usage:
<ip-address-input formControlName="ipAddress" (value)="ipAddress.value"></ip-address-input>
HTH
I created a dynamically adding/deleting rows using Angular reactive forms. Rows are getting added and deleted. But how you can send all these row values from Angular application to java controller.I am pasting the code below.
app.component.html
<div class="container">
<h3 class="page-header">Seasons</h3>
<button type="button" class="btn btn-primary" (click)="addTeam()">Add New Row</button><br/>
<form [formGroup] = "seasonsForm">
<div formArrayName = "teamRows">
<div *ngFor = "let team of seasonsForm.controls.teamRows.controls; let i=index" [formGroupName] = "i">
<h4>Team- #{{i+1}}</h4>
<div class="form-group">
<label>Team Name</label>
<input formControlName = "teamName" class="form-control">
</div>
<div class="form-group">
<label>Stadium</label>
<input formControlName = "stadiumName" class="form-control">
</div>
<div class="form-group">
<label>Capacity</label>
<input formControlName = "capacity" class="form-control">
</div>
<div class="form-group">
<label>Founded</label>
<input formControlName = "founded" class="form-control">
</div>
<div class="form-group">
<label>Head Coach</label>
<input formControlName = "headCoach" class="form-control">
</div>
<div class="form-group">
<label>Last Season</label>
<input formControlName = "lastSeason" class="form-control">
</div>
<button *ngIf = "seasonsForm.controls.teamRows.controls.length" (click) = "deleteTeam(i)" class="btn btn-danger">Delete Button</button>
</div>
</div>
</form>
<pre>{{ seasonsForm.value |json }}</pre>
</div>
app.component.ts
import { Component, OnInit } from '#angular/core';
import { FormGroup,FormArray,FormBuilder,Validators} from '#angular/forms';
import { Teams } from '../service/http-client.service';
#Component({
selector: 'app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
public seasonsForm: FormGroup;
public teams:Teams[];
constructor(private _fb: FormBuilder) { }
ngOnInit() {
this.seasonsForm = this._fb.group({
teamRows: this._fb.array([this.initTeamRows()])
});
}
get formArr() {
return this.seasonsForm.get('teamRows') as FormArray;
}
initTeamRows() {
return this._fb.group({
teamName: [''],
stadiumName: [''],
capacity: [''],
founded: [''],
headCoach: [''],
lastSeason: ['']
});
}
addTeam() {
this.formArr.push(this.initTeamRows());
}
deleteTeam(index: number) {
this.formArr.removeAt(index);
}
}
createTeam(): void {
this.httpClient.post<Teams>("http://localhost:8080/addTeam", seasonsForm);
.subscribe( res => {
alert("Successful");
})
};
export class Teams {
constructor(
public teamName:string,
public stadiumName:string,
public capacity:string,
public founded:string,
public headCoach:string,
public lastSeason:string,
) {}
}
I tried to send entire formGroup(seasonsForm) but it is getting failed.I am relatively new to Angular and i searched in Google but i didn't find much help. So any help in this would be appreciated.
you need to send form value on your createTeam function. If you console.log(seasonsForm), you can see there are some other attributes which is only about your form.
Also if you want you can check is form valid.
createTeam(): void {
if(seasonsForm.valid){
this.httpClient.post<Teams>("http://localhost:8080/addTeam", seasonsForm.value);
.subscribe( res => {
alert("Successful");
})
};
}
First of all if you are using NoSQL database then you can send a JSON file containing arrays. So here i decided to send a json file containing Array values to server side and then i converted the form group name to JSON.Stringify. In Server side you can retrieve that json in String format and parse it and send to DB. Here is the code below
onSubmit() {
this.httpClient.post("http://localhost:8080/addTeams",JSON.stringify(this.seasonsForm.value))
.subscribe((response: Response) => {
})
alert("Successful");
}
I have this template-driven form:
<form #form="ngForm" (submit)="submitForm()" novalidate>
<div class="form-group" [class.has-error]="linkName.invalid">
<label class="control-label" for="name">Name</label>
<input #linkName="ngModel" type="text" class="form-control" name="name" id="name" [(ngModel)]="finalData.name" required />
<div *ngIf="linkName.invalid" class="alert alert-danger">Name is required</div>
</div>
<div class="form-group">
<label for="url">Url</label>
<input type="text" class="form-control" id="url" name="url" [(ngModel)]="finalData.url" readonly/>
</div>
<div class="checkbox" *ngFor="let tag of finalData.tags; index as i" [attr.data-index]="i">
<label><input type="checkbox" name="{{ 'checkbox' + tag }}" id="{{ 'checkbox' + tag }}" (ngModel)="finalData.tags[i]"/>{{ tag }}</label>
</div>
<button type="submit" [disabled]="form.invalid" class="btn btn-primary btn-success">Save</button>
</form>
I want to POST value from checkboxes based on whether they checked or not.
This way with one-way binding (ngModel) it will POST all tags whether they checked or not. If I use two-way binding [(ngModel)] it will POST boolean values and it will change name and id based on boolean value, which I don't want.
Example:
If i check this I want POST foo instead of true
<label><input type="checkbox" name="footag" id="footag" value="foo" [(ngModel)]="finalData.tags[i]"/>foo</label>
This is component used for data binding:
import { Component, OnInit, Injectable } from "#angular/core";
import { DataService } from "../shared/dataService";
import { FinalFormData } from "../shared/finalFormData";
import { ActivatedRoute, Router } from '#angular/router';
import { Observable } from "rxjs/Observable";
import { NgForm } from "#angular/forms";
#Component({
selector: "final-form",
templateUrl: "finalForm.component.html"
})
export class FinalFormComponent implements OnInit{
constructor(private data: DataService, private route: ActivatedRoute, private router: Router) {
}
public finalData: FinalFormData;
public errorMessage = "";
public isBusy = true;
submitForm() {
this.data.postData(this.finalData)
.subscribe(data => {
this.finalData = data;
this.router.navigate(["/"]);
}, err => console.log(err));
}
ngOnInit(): void {
//let url = encodeURIComponent(this.route.snapshot.queryParams['url']);
//this.data.loadData(url)
// .subscribe(finalData => {
// this.finalData = finalData;
// });
this.finalData = this.route.snapshot.data['finalData'];
}
}
FinalFormData class:
export class FinalFormData {
name: string;
url: string;
dateCreated: Date;
tags: any[];
}
you can use the ngModelChange method to pass the value and set it to any variable you want. Refer sample code snippet below:
<label><input type="checkbox" name="footag" id="footag" value="foo" [ngModel]="finalData.tags[i]" (ngModelChange)="CheckBoxValueChange('foo',finalData)"/>foo</label>
in the component.ts file, you can access the value and set it to any variable you want:
CheckBoxValueChange(checkboxValue:string,finalData:any){
finalData.checkboxText=checkboxValue;
alert(checkboxValue);
}
I have an edit profile page. So when the user clicks on the edit profile page the users details should be displayed in the input tag fields in the HTML. But I am unable to display the details I am retrieving.
edit-profile.component.html
<form novalidate (ngSubmit)="onFormSubmit(signupForm)" #signupForm="ngForm">
<div class="form-inputs clearfix">
<div class="row">
<div class="col-md-6">
<p>
<label class="required">First Name<span>*</span></label>
<input
type="text"
[value]="accountDetails.firstName"
name="firstName"
[(ngModel)] = "user.firstName"
#firstName = "ngModel"
required><p>{{accountDetails.firstName}} </p>
<span *ngIf="firstName.invalid && (firstName.dirty || firstName.touched)" class="input-error">
<span *ngIf = "firstName.errors?.required">
First Name field can't be blank
</span>
</span>
</p></form>
edit-profile.component.ts
import { Component, OnInit } from '#angular/core';
import { ApiServiceProvider } from '../services/api.service';
#Component({
selector: 'app-edit-profile',
templateUrl: './edit-profile.component.html',
styleUrls: ['./edit-profile.component.css']
})
export class EditProfileComponent implements OnInit {
public user: any = {};
public accountDetails: any = {}
constructor(
private api: ApiServiceProvider
) { }
ngOnInit() {
let profile = JSON.parse(localStorage.getItem("profile"));
this.accountDetails = profile.user;
console.log(this.accountDetails);
}
public onFormSubmit({ value, valid }: { value: any, valid: boolean }) {
this.user = value;
this.api.put("/users/editprofile", value.userId, false)
.subscribe((data) => {
console.log(data)
localStorage.setItem("profile", JSON.stringify(data));
location.href = "/profile"
}, (err) => {
alert("Registartion failed " + err);
})
}
}
you only populate your accountDetails variable but you try to bind your user variable.
so you have:
this.accountDetails = profile.user;
but you bind your input to the user variable:
<input type="text" [(ngModel)]="user.firstName" #firstName="ngModel" required>
Either populate your user variable:
this.user = profile.user;
or bind to your account details variable:
<input type="text" [(ngModel)]="accountDetails.firstName" #firstName="ngModel" required>
Also you need to close your div clearfix, div row and div column correctly before closing the form tag. </div></div></div></form>