Is it possible to get native element for formControl?

AngularAngular2 Forms

Angular Problem Overview


I've got Angular reactive form. I created formControls 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();
}

Plunker Example

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' />

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionfedor.belovView Question on Stackoverflow
Solution 1 - AngularbaHIView Answer on Stackoverflow
Solution 2 - AngularyurzuiView Answer on Stackoverflow
Solution 3 - AngularKirillView Answer on Stackoverflow
Solution 4 - Angularuser2599470View Answer on Stackoverflow
Solution 5 - Angularsir_leslieView Answer on Stackoverflow
Solution 6 - AngularAlireza AhmadiView Answer on Stackoverflow