Another Angular question... I have the following component that I use in a parent (template-driven) form. (FWIW, I am using PrimeFaces UI components.) When the user clicks the "No" radio button, I would like to autopopulate the mailing address/city/state/ZIP fields from the corresponding fields above. This would be easy in plain JavaScript, but I want to do this in the proper Angular fashion. (I suspect my data binding is WAY improper...) Anyone have any ideas? Thanks in advance for your help!
Screen capture:
Component code:
import { Component, EventEmitter, Input, OnInit, Output } from '#angular/core';
import { DropdownOptions } from 'src/assets/dropdownOptions';
import { ApplicantInformation } from 'src/app/_models/applicantInformation.model';
import { ControlContainer, NgForm } from '#angular/forms';
#Component({
selector: 'app-applicant-information',
templateUrl: './applicant-information.component.html',
styleUrls: ['./applicant-information.component.css'],
viewProviders: [{ provide: ControlContainer, useExisting: NgForm }] // used to link inputs to the parent form
})
export class ApplicantInformationComponent implements OnInit {
name: string = '';
phone = '';
address = '';
city = '';
state = 'AL';
zipCode = '';
mailAddrDifferentFromPhysical: string = 'false';
mailingAddress = '';
mailingCity = '';
mailingState = 'AL';
mailingZIPCode = '';
options: any = new DropdownOptions;
sts: string[] = [];
#Input() ngModel: any = new ApplicantInformation(
this.name,
this.phone,
this.address,
this.city,
this.state,
this.zipCode,
this.mailAddrDifferentFromPhysical,
this.mailingAddress,
this.mailingCity,
this.mailingState,
this.mailingZIPCode,
)
#Output() nameEvent: EventEmitter<any> = new EventEmitter();
onFirstColEntered(name: any) {
this.nameEvent.emit(name);
}
handleRadioClick(str: string) {
this.mailAddrDifferentFromPhysical = str;
}
constructor() {
this.sts = this.options.strAbbrs;
}
ngOnInit(): void {
}
}
Template code (partial):
<label for="address">Address</label>
<input type="text"
pInputText
name="address" id="address"
[ngModel] #address="ngModel"
required [minlength]="8" [ngModelOptions]="{ updateOn: 'blur' }" trim="blur"
placeholder="Address"
(change)="onFirstColEntered(address)">
...
<p class="lg:col-12 md:col-12">
My mailing address is different than the one above.
<p-radioButton
name="mailAddrDifferentFromPhysical" inputId="mailAddrDifferentFromPhysical"
value="true"
label="Yes"
ngModel
(onClick)="handleRadioClick('true');onFirstColEntered(mailAddrDifferentFromPhysical)"></p-radioButton>
<p-radioButton
name="mailAddrDifferentFromPhysical"
value="false"
label="No"
ngModel
(onClick)="handleRadioClick('false');onFirstColEntered(mailAddrDifferentFromPhysical)"></p-radioButton>
</p>
...
<label for="mailingAddress">Mailing Address</label>
<input type="text"
pInputText
name="mailingAddress" id="mailingAddress"
(ngModel)="address?.value" #mailingAddress="ngModel"
required [minlength]="8" [ngModelOptions]="{ updateOn: 'blur' }" trim="blur"
placeholder="Mailing Address"
(change)="onFirstColEntered(mailingAddress)">
You can simply set your mailing values to the above values when the user sets the radion button value to yes i.e. true.
You can use the method that you're already using (onClick method) in the radio button for setting the values.
For example, it should look like this:
handleRadioClick(str: string) {
this.mailAddrDifferentFromPhysical = str;
this.mailingCity = this.city;
this.mailingAddress = this.address
//and so on for the rest of the fields
}
It turns out my data binding (or lack thereof) WAS the issue. I enclosed the ngModel attribute in square brackets on the fields I wanted to copy, then referred to that value in the receiving field, i.e.:
<input type="text"
pInputText
name="mailingAddress" id="mailingAddress"
[ngModel]="address?.value" #mailingAddress="ngModel"
required [minlength]="8" [ngModelOptions]="{ updateOn: 'blur' }" trim="blur"
placeholder="Mailing Address"
(change)="onFirstColEntered(mailingAddress)">
Related
on focus, when I click on one input field I want and if it is not valid to be validation disappears, but when we move to another field the same validation appears. It means only the focus of the current field to take off.
component.ts code
createForm() {
this.contactForm = this.fb.group({
firstName: [null, [Validators.required, Validators.minLength(0),
Validators.maxLength(150)]],
lastName: [null, [Validators.required, Validators.minLength(0),
Validators.maxLength(150)]]
});
}
component.html code
<div class="col-lg-2 col-md-2 col-sm-2 pl-1">
<label class="customer-title" for="account">
First Name<span class="text-danger">*</span>
</label>
<input
name="account"
class="form-control customer-input-field"
placeholder="First Name"
[ngClass]="{'is-invalid': f.firstName.touched && f.firstName.errors }"
formControlName="firstName"/>
<div class="text-danger error-text mt-1"
*ngIf="f.firstName.touched && f.firstName.errors?.required">
First Name is required
</div>
<div
class="text-danger error-text mt-1"
*ngIf="f.firstName.errors?.maxlength">
First name must be less than 150 characters long
</div>
</div>
I dont like the idea of removing validation on focus, because you will potentially allow at least one field to be invalid when a user submits his data.
It is possible with a directive:
import { Directive, ElementRef, HostListener } from "#angular/core";
import { NgControl, ValidatorFn } from "#angular/forms";
#Directive({
selector: "[no-val-on-focus]"
})
export class NoValidationOnFocusDirective {
constructor(
private el: ElementRef<HTMLInputElement>,
private control: NgControl
) {}
private validator: ValidatorFn | null = null;
#HostListener("focus", ["$event"])
public onFocus(): void {
this.validator = this.control.control.validator;
this.control.control.clearValidators();
this.control.control.updateValueAndValidity();
}
#HostListener("blur", ["$event"])
public onBlur(): void {
this.control.control.setValidators(this.validator);
this.control.control.updateValueAndValidity();
}
}
The proceudeure is simple:
On focus we store the validation function in a private member of the directive and clear all validators on the control.
On blur we set the validator of the control to the original function.
Stackblitz example
Let's say I have this:
<input formControlName="someName" (click)="onClick()">
I want my onClick function to be generic and set the value of the corresponding FormControl (the one I clicked).
How can I pass the concerned FormControl as a parameter of onClick?
I thought I could retrieve it from the control property of FormControlDirective or FormControlName but none of them have an exportAs attribute.
I think this is what you want to achieve.
import { Directive, HostListener, Optional, Output, EventEmitter } from '#angular/core';
import { NgControl, FormControl } from '#angular/forms';
#Directive({
selector: '[appOnClickControl]' // if you want to target specific form control then use custom selector else you use can use input:
// selector: 'input' to target all input elements
})
export class TestDirective {
#Output() emitFormControl = new EventEmitter<FormControl>();
constructor(#Optional() private formControl: NgControl) {
}
/**
* If your only goal is to set value to the form control then use this
*/
#HostListener('click')
onClick() {
if (this.formControl) {
this.formControl.control.setValue('test');
}
}
/**
* If you wanna pass the form control through the function you might use this
*/
#HostListener('click')
getFormControl(): void {
this.emitFormControl.emit(this.formControl.control as FormControl);
}
}
<input
appOnClickControl // use this to initialize directive
(emitFormControl)="yourMethod($event)" // $event is the clicked form control
formControlName="test"
></input>
In your html:
<input formControlName="someName" (click)="onClick($event)">
And then define your onClick function as:
onClick(event): void {
alert(event.target.value)
}
Edit
To get FormControl:
<input formControlName="someName" (click)="onClick(Form.get('someName'))">
and
onClick(formControl): void {
formControl.setValue(someValue);
}
kinda repetitive but pretty sure you can only do this:
<input formControlName="someName" (click)="onClick('someName')">
then use someName on your form group to find the control
onClick(name: string) {
const fc = this.form.get(name);
// do whatever
}
Not exactly sure what you're after, but try if the following works for you. It uses setValue() method to set values for the form. You can also use patchvalue if you want to set only partial values of the form.
Template
<form [formGroup]='groupedform' >
<label>Name: <br>
<input type="text" formControlName='Name' required (mousedown)="onMouseDown(groupedform)"/>
</label>
<br>
<br>
<label>Email: <br>
<input type="email" formControlName='Email' required (mousedown)="setEmail(groupedform)"/>
</label>
<p>
<button type="submit" [disabled]="!groupedform.valid" (click)="updateName()">Update Name</button>
</p>
</form>
Component
export class AppComponent {
name = 'Angular';
firstname = new FormControl('');
groupedform = this.fb.group({
Name : ['', Validators.required],
Email: [],
});
constructor(private fb:FormBuilder) { }
updateName() {
console.log(this.groupedform.value);
}
onMouseDown(formControl: any) {
this.groupedform.setValue({
'Name': 'sample',
'Email': 'sample#example.com'
});
}
setEmail(formControl: any) {
this.groupedform.patchValue({
'Email': 'sample#example.com'
});
}
}
Working example: Stackblitz
You can use template variable for this
<input formControlName="someName" #temp (click)="onClick(temp)">
onClick(val) {
console.log(val);}
I am not sure what type of value you want to set, but you can use an attribute directive for this type of common operation (if it is simple).
You can create an attribute directive and set on all the form controls, the attribute directive logic will automatically handle it for you. You can definetely configure the values passed to directives.
import { Directive, HostListener, ElementRef, Input } from "#angular/core";
#Directive({
selector: '[clickMe]'
})
export class ClickDirective {
#Input('clickMe') clickValue:string;
constructor(private elementRef: ElementRef){}
#HostListener('click') onClick() {
this.elementRef.nativeElement.value = this.clickValue;
}
}
Now just use these directives with the the form controls and pass your values, you can use data binding as well.
<form [formControl]="myForm">
Firstname:<input type="text" fromControlName="firstname" [clickMe]="'first value'" />
<br>
Lastname:<input type="text" fromControlName="lastname" [clickMe]="'last value'" />
</form>
Please find the working stackblitz here:https://stackblitz.com/edit/angular-j1kwch
You could use the event passed and get the attribute name out of it:
<input type="text" class="form-control" formControlName="yourName" (blur)="yourMethod($event)" />
Then in your method get the name of the formControlName from the target:
yourMethod(event: any) {
const controlName = e.target.getAttribute('formcontrolname');
this.formGroup.get(controlName); // this is your FormControl
....
}
I am trying to add a form field with custom telephone number input control. I used the example of the phone from https://material.angular.io/components/form-field/examples.
Here is the code:
<mat-form-field>
<example-tel-input placeholder="Phone number" required></example-tel-input>
<mat-icon matSuffix>phone</mat-icon>
<mat-hint>Include area code</mat-hint>
</mat-form-field>
import {FocusMonitor} from '#angular/cdk/a11y';
import {coerceBooleanProperty} from '#angular/cdk/coercion';
import {Component, ElementRef, Input, OnDestroy} from '#angular/core';
import {FormBuilder, FormGroup} from '#angular/forms';
import {MatFormFieldControl} from '#angular/material';
import {Subject} from 'rxjs';
/** #title Form field with custom telephone number input control. */
#Component({
selector: 'form-field-custom-control-example',
templateUrl: 'form-field-custom-control-example.html',
styleUrls: ['form-field-custom-control-example.css'],
})
export class FormFieldCustomControlExample {}
/** Data structure for holding telephone number. */
export class MyTel {
constructor(public area: string, public exchange: string, public subscriber: string) {}
}
/** Custom `MatFormFieldControl` for telephone number input. */
#Component({
selector: 'example-tel-input',
templateUrl: 'example-tel-input-example.html',
styleUrls: ['example-tel-input-example.css'],
providers: [{provide: MatFormFieldControl, useExisting: MyTelInput}],
host: {
'[class.example-floating]': 'shouldLabelFloat',
'[id]': 'id',
'[attr.aria-describedby]': 'describedBy',
}
})
export class MyTelInput implements MatFormFieldControl<MyTel>, OnDestroy {
static nextId = 0;
parts: FormGroup;
stateChanges = new Subject<void>();
focused = false;
ngControl = null;
errorState = false;
controlType = 'example-tel-input';
id = `example-tel-input-${MyTelInput.nextId++}`;
describedBy = '';
get empty() {
const {value: {area, exchange, subscriber}} = this.parts;
return !area && !exchange && !subscriber;
}
get shouldLabelFloat() { return this.focused || !this.empty; }
#Input()
get placeholder(): string { return this._placeholder; }
set placeholder(value: string) {
this._placeholder = value;
this.stateChanges.next();
}
private _placeholder: string;
#Input()
get required(): boolean { return this._required; }
set required(value: boolean) {
this._required = coerceBooleanProperty(value);
this.stateChanges.next();
}
private _required = false;
#Input()
get disabled(): boolean { return this._disabled; }
set disabled(value: boolean) {
this._disabled = coerceBooleanProperty(value);
this.stateChanges.next();
}
private _disabled = false;
#Input()
get value(): MyTel | null {
const {value: {area, exchange, subscriber}} = this.parts;
if (area.length === 3 && exchange.length === 3 && subscriber.length === 4) {
return new MyTel(area, exchange, subscriber);
}
return null;
}
set value(tel: MyTel | null) {
const {area, exchange, subscriber} = tel || new MyTel('', '', '');
this.parts.setValue({area, exchange, subscriber});
this.stateChanges.next();
}
constructor(fb: FormBuilder, private fm: FocusMonitor, private elRef: ElementRef<HTMLElement>) {
this.parts = fb.group({
area: '',
exchange: '',
subscriber: '',
});
fm.monitor(elRef, true).subscribe(origin => {
this.focused = !!origin;
this.stateChanges.next();
});
}
ngOnDestroy() {
this.stateChanges.complete();
this.fm.stopMonitoring(this.elRef);
}
setDescribedByIds(ids: string[]) {
this.describedBy = ids.join(' ');
}
onContainerClick(event: MouseEvent) {
if ((event.target as Element).tagName.toLowerCase() != 'input') {
this.elRef.nativeElement.querySelector('input')!.focus();
}
}
}
example-tel-input-example.html
<div [formGroup]="parts" class="example-tel-input-container">
<input class="example-tel-input-element" formControlName="area" size="3">
<span class="example-tel-input-spacer">–</span>
<input class="example-tel-input-element" formControlName="exchange" size="3">
<span class="example-tel-input-spacer">–</span>
<input class="example-tel-input-element" formControlName="subscriber" size="4">
</div>
But I get the following error:
ERROR Error: mat-form-field must contain a MatFormFieldControl.
Need to import two module and add those in imports and exports section.
import { MatFormFieldModule } from '#angular/material/form-field';
import { MatInputModule } from '#angular/material/input';
#NgModule ({
imports: [ MatFormFieldModule, MatInputModule ],
exports: [ MatFormFieldModule, MatInputModule ]
})
And the most thing which everybody miss this '/' character. if you see the Angular Material Documentation , they also miss this (Last Checked 16 Jun 2020, don't know they even updated or not). I make an example for clarifications
<!-- Wrong -->
<mat-form-field>
<input matInput>
</mat-form-field>
<!-- Right -->
<mat-form-field>
<input matInput />
</mat-form-field>
Look at the snippet carefully. when <input begin it must close with /> but most people miss the / (backslash) character.
Make sure MatInputModule is imported and <mat-form-field> contains <input> with matInput / matSelect directives.
https://github.com/angular/material2/issues/7898
Import MatInputModule, solved my error
You need to specify your class as a provider for MatFormFieldControl
https://material.angular.io/guide/creating-a-custom-form-field-control#providing-our-component-as-a-matformfieldcontrol
#Component({
selector: 'form-field-custom-control-example',
templateUrl: 'form-field-custom-control-example.html',
styleUrls: ['form-field-custom-control-example.css'],
providers: [
{ provide: MatFormFieldControl, useExisting: FormFieldCustomControlExample }
]
})
Also, don't forget to write the name attribute in the input tag:
name="yourName"
if you are using any 'input' tag in your code along with 'mat-form-field', make sure to include 'matInput' in the input tag
if there is any *ngIf present in child tag of 'mat-form-field', specify the *ngIf condition in 'mat-form-field' tag
Another possible issue closing the input tag. If you copy code from one of the Angular examples (https://material.angular.io/components/datepicker/overview), you may end up with the code:
<mat-form-field appearance="fill">
<mat-label>Choose a date</mat-label>
<input matInput [matDatepicker]="picker">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
The input should have the close tag (slash):
<input matInput [matDatepicker]="picker" />
This will fix your issue
import {
MatFormFieldModule,
MatInputModule
} from "#angular/material";
Error says that mat-form-field should contain at least one form field from which input is taken.
Ex : matInput mat-select etc.
In your case you may add matInput tag to your input fields within example-tel-input-example.html. And also you could move mat-form-field inside example-tel-input-example component and add it against each matInput field.
<div [formGroup]="parts" class="example-tel-input-container">
<mat-form-field>
<input matInput class="example-tel-input-element" formControlName="area" size="3">
</mat-form-field>
<span class="example-tel-input-spacer">–</span>
<mat-form-field>
<input matInput class="example-tel-input-element" formControlName="exchange" size="3">
</mat-form-field>
<span class="example-tel-input-spacer">–</span>
<mat-form-field>
<input matInput class="example-tel-input-element" formControlName="subscriber" size="4">
</mat-form-field>
</div>
Note : mat-icon or mat-hint cannot be considered as form-fields
My task is to create an account information web page which consists of 4 pre-filled fields (given name, family name, username and email) and a common save button at the bottom. User can change any field by the respective field. I want save button to be enabled only if user changes any fields. Any method to track state changes? My code is as follows:
<mat-card-content>
<div class="form-group">
<mat-form-field class="simple-form-field-50">
<input matInput placeholder="Given name" name="givenName" formControlName="givenName">
</mat-form-field>
<mat-form-field class="simple-form-field-50">
<input matInput placeholder="Family name" name="familyName" formControlName="familyName">
</mat-form-field>
<br>
<mat-form-field>
<input matInput placeholder="Email" name="email" formControlName="email">
</mat-form-field>
<br>
<button
[disabled]="waiting"
class="simple-form-button"
color="primary"
mat-raised-button
type="submit"
value="submit">
Save
</button>
</div>
</mat-card-content>
My Code Output:
Since you're using a Reactive Form, you can use valueChanges on the FormGroup.
Since it is of type Observable, you can subscribe to it to set a variable of type boolean that will be used in the template to enable the button.
...
#Component({...})
export class AppComponent {
form: FormGroup;
disableButton = true;
ngOnInit() {
...
this.form.valueChanges.subscribe(changes => this.disableButton = false);
}
}
And in your template:
<form [formGroup]="form">
...
<button [disabled]="disableButton">Submit</button>
</form>
UPDATE:
If you want to disable it when values don't really change, check for the current value of the form with the previous value:
import { Component } from '#angular/core';
import { FormGroup, FormControl } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
form: FormGroup;
disableButton = true;
userValue = {
firstName: 'John',
lastName: 'Doe',
email: 'john.doe#domain.com'
}
ngOnInit() {
this.form = new FormGroup({
firstName: new FormControl(),
lastName: new FormControl(),
email: new FormControl()
});
this.form.patchValue(this.userValue);
this.form.valueChanges.subscribe(changes => this.wasFormChanged(changes));
}
private wasFormChanged(currentValue) {
const fields = ['firstName', 'lastName', 'email'];
for(let i = 0; i < fields.length; i++) {
const fieldName = fields[i];
if(this.userValue[fieldName] !== currentValue[fieldName]) {
console.log('Came inside');
this.disableButton = false;
return;
}
}
this.disableButton = true;
}
}
NOTE: StackBlitz is updated accordingly.
Here's a Working Sample StackBlitz for your ref.
onChange(targetValue : string ){
console.log(targetValue );}
<input matInput placeholder="test" name="test" formControlName="testNM" (input)="onChange($event.target.value)">
This is called Dirty Check.
You may find this SO answer very useful:
https://stackoverflow.com/a/50387044/1331040
Here is the guide for Template-Driven Forms
https://angular.io/guide/forms
Here is the guide for Reactive Forms
https://angular.io/guide/reactive-forms
And here is the difference between two concepts
https://blog.angular-university.io/introduction-to-angular-2-forms-template-driven-vs-model-driven/
Hope these help.
I would do something like this:
form: FormGroup;
disableButton = true;
originalObj: any;
ngOnInit() {
this.form = new FormGroup({
control: new FormControl('Value')
});
this.originalObj = this.form.controls['control'].value; // store the original value in one variable
this.form.valueChanges.subscribe(changes => {
if (this.originalObj == changes.control) // this if added for check the same value
{
this.disableButton = true;
}
else {
this.disableButton = false;
}
}
);
}
WORKING EXAMPLE
I'm a newbie with Typescript and I'm going mad with this piece of code.
I'm writing a very simple login form (below a sample, but consider it's just part of the whole form), but username field inside form is not tied to the login.Username data, while username field outside form works properly.
So what happens is that when I load the page I get the first field filled with data (and data changes inside variable when I type something), while the field inside form remains empty.
I'm sure it's something stupid, but I can't make it work.
{{diagnostic}}
<input class="form-control" type="text"
required placeholder="Username"
minlength="7" maxlength="8"
[(ngModel)]="login.Username"/>
<form #loginForm="ngForm" (submit)="doLogin()">
<input class="form-control" type="text"
required placeholder="Username"
minlength="7" maxlength="8"
[(ngModel)]="login.Username"/>
</form>
I also attach the component whose html is not working
import {Component, OnInit} from '#angular/core';
import {Router, NavigationExtras} from '#angular/router';
import {AuthService} from '../services/auth.service';
import {LoginInfo} from "../classes/LoginInfo";
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css'],
providers: [],
})
export class LoginComponent implements OnInit {
constructor(public authService: AuthService, public router: Router) {
}
ngOnInit() {
this.login = new LoginInfo('john', 'pwd');
this.message = '';
}
public login: LoginInfo;
public message: string;
doLogin() {
this.message = this.login.isValidInfo() ? 'OK' : "ERRORE";
alert('test');
//this.router.navigateByUrl('/scheduler');
}
get diagnostic() { return JSON.stringify(this.login); }
}
LoginInfo class
export class LoginInfo {
public Username: string;
public Password: string;
public Stato: number;
public Messaggio: string;
constructor(user: string, pwd: string) {
this.Username = user;
this.Password = pwd;
this.Stato = 0;
this.Messaggio = '';
}
public isValidInfo() {
return this.Username && this.Username != '' &&
this.Password && this.Password != '';
}
}
you are missing a name property that should be present in the form-control inside a form like name="user". So can you try with the below markup
<form #loginForm="ngForm" (submit)="doLogin()">
<input class="form-control" type="text"
required placeholder="Username"
minlength="7" maxlength="8" name="user"
[(ngModel)]="login.Username"/>
</form>