Is it possible to get native element for formControl?
AngularAngular2 FormsAngular Problem Overview
I've got Angular reactive form. I created formControl
s and assigned it to input fields by[formControl]=...
. As I understand it creates nativeElement <-> formControl
link.
My question: is it possible to get nativeElement
for formControl
? I want to do something like myFormControl.nativeElement.focus()
Angular Solutions
Solution 1 - Angular
The code below does not work with pure ngModel binding, so I did a lot of experiments. Latest, also confirmed by Maximillian Schwarzmuller should be the one:
@Directive({
selector: '[ngModel], [formControl]', // or 'input, select, textarea' - but then your controls won't be handled and also checking for undefined would be necessary
})
export class NativeElementInjectorDirective {
constructor(private el: ElementRef, private control : NgControl, @Optional() private model : NgModel) {
if (!! model)
(<any>model.control).nativeElement = el.nativeElement;
else
(<any>control).nativeElement = el.nativeElement;
}
}
So if this directive is provided and exported in the main module, it will attach a custom nativeElement property to all FormControl.
It's a shame it's not coming out-of-the box...
Solution 2 - Angular
I can share one terrible solution but it works for me.
In reactive forms we can use either
1) FormControlDirective
ts
myControl = new FormControl('')
template
<input type="text" [formControl]="myControl">
or
2) FormControlName
ts
myForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.myForm = this.fb.group({
foo: ''
});
}
template
<form [formGroup]="myForm">
<input type="text" formControlName="foo">
</form>
So for these directives i could write some patch like
1) FormControlDirective
const originFormControlNgOnChanges = FormControlDirective.prototype.ngOnChanges;
FormControlDirective.prototype.ngOnChanges = function() {
this.form.nativeElement = this.valueAccessor._elementRef.nativeElement;
return originFormControlNgOnChanges.apply(this, arguments);
};
2) FormControlName
const originFormControlNameNgOnChanges = FormControlName.prototype.ngOnChanges;
FormControlName.prototype.ngOnChanges = function() {
const result = originFormControlNameNgOnChanges.apply(this, arguments);
this.control.nativeElement = this.valueAccessor._elementRef.nativeElement;
return result;
};
After that we can easily access to native element having FormControl
instance
1) FormControlDirective
focusToFormControl() {
(<any>this.myControl).nativeElement.focus();
}
2) FormControlName
focusToFormControlName(name) {
(<any>this.myForm.get(name)).nativeElement.focus();
}
Solution 3 - Angular
Added minor fix to baHI answer(moved logic to OnInit). Error mentioned in comments is probably connected to changes in forms. This answer is for "@angular/forms": "~7.1.0",
@Directive({
selector: '[ngModel]'
})
export class NativeElementInjectorDirective implements OnInit {
constructor (private el: ElementRef, private control : NgControl) {}
ngOnInit(){
(this.control.control as any).nativeElement = this.el.nativeElement;
}
}
Solution 4 - Angular
Hello to whoever reads this! I hope you are having as much fun as I am playing with Angular :)
I spent a little time with a related issue that lead me here. The "injector" directive solution seems to me like a workaround, and an unpredictable way of approaching the problem.
I suspect the Angular team may have their reasons why this isn't "out-of-the-box." Perhaps they don't want to tie a FormControl to an exact DOM element since these two can outlive one another or get removed (in the case of the native element), or get cloned (which would not copy the "extended" assignment and, thus, effectively loosing any assignment from the directive targeting the original DOM element and a previous clone of the FormControl object), etc.
So after some thought, I went with this solution:
// Get the first invalid formControlName to scroll to:
const firstInvalidField =
Object.keys(form.controls).find(field => form.get(field)?.invalid);
// My DOM element query selector string
const selector = `[formControlName=${firstInvalidField}]`;
console.log(
document.querySelector( ${selector} )`
);
Now I'm guaranteed that I will have the reference to the DOM Element.
Solution 5 - Angular
Create an input field on template:
<input matInput formControlName="cardnumber" #cardnumber >
Get the viewchild of it in class:
@ViewChild("cardnumber") cardnumberField: FormControl;
Init it:
this.cardnumberField = new FormControl(null, []);
And do with it whatever you want:
(<any> this.cardnumberField).nativeElement.value = "";
(<any> this.cardnumberField).nativeElement.focus();
Solution 6 - Angular
Yes, you have to write Directive with [formControl], [formControlName]
selector.
Full example:
import { Directive, ElementRef } from "@angular/core";
import { NgControl } from '@angular/forms';
@Directive({
selector: '[formControl], [formControlName]'
})
export class ControlErrorsDirective {
get control() {
return this.controlDir.control;
}
constructor(
private controlDir: NgControl,
private host: ElementRef<HTMLFormElement>) {
}
ngOnInit() {
console.log(this.host.nativeElement);
}
}
and in your html just use formControlName
like this:<input formControlName='name' />