AIon
AIon

Reputation: 13081

how to access Native HTML Input element using formControl in Angular 2.0

I'm using Angular 2.0 final release.

What i want to do? - i want to show some notifications (warnings, the requirements for the input) only when the input receive focus. Or even better, when his parent (the div) has mouse hover event.

Doing this from the parent(the div) is easy. Just (focus)=showNotifications() + an ngIf - and the job is done.

But i want to encapsulate this functionality inside the show-notifications component - and make it reusable..

Given that i pass the control inside the show-notifications using @Input() - i could do both this things if i have access to the native HTML input element.You can see how in show-notifications.component.ts. Here is the code:

app.component.html:

`<div>
   <label>Name: </label>
   <input formControlName="myName">
   <show-notifications [the_control]="myName" ></show-notifications>
</div>`

app.component.ts:

export class AppComponent {
    myName = new FormControl("defaultvalue",[some validators..])
}

show-notifications.component.html:

<div class="show_requirements" *ngIf="hasfocus or parentDivHasMouseHover"> // incorect code and logic - i know, but you can see the idea..

    <p *ngFor="let requirement of thisInputRequirements">{{requirement}}<p>

</div>

show-notifications.component.ts:

export class ShowNotificationsComponent {

    @Input() the_control;
    this.thisInputRequirements = // take them form firebase.
    this.thisInputCustomErrorMessages = // take them from firebase.

    // I implemented all this and works amazing but i want to do:

    //something like this:

    ngOnInit(){
        this.nativeInputElement = this.the_control.getNativeElement() // this of course doesn't work

        // the requirements are shown only when the input receive focus
        let source = Rx.DOM.focus(this.nativeInputElement);
        source.subscribe( x => this.hasFocus = true)

        // Or even better: 
        // the requirements are shown only when the parent has mouse hover
        // or any other event / endles posibilites here..

        let parent = getParentOf(this.nativeInputElement)
        let source = Rx.DOM.mouseover(parent);
        source.subscribe( x => this.parentDivHasMouseHover = true) // this will be some toggling functionality.
    }

}

Question:

How do i access the native element given that i have the formControl (myName = the_control) object?

Is there a better way to do notifications in a separate component - and make it reusable? I already use this successfully throughout my entire app - to show errors and input requirements..

Note: I tried to pass the hole html input first using hashtag syntax ( #myInput ) and form there, inside the show-notifications component, i tried to do: myInput.myName - to acces the control but i get undefined. The controls are not present on the native input element. And I need that control for validation:)

Upvotes: 9

Views: 13291

Answers (4)

Brian Davis
Brian Davis

Reputation: 817

Fundamentally, the only way for an element to have access to its sibling is to pass a reference to it. You could give your notifications component the name of the control and the form and look it up, as you are doing, but it's an error prone process. However, I think creating a directive would be more helpful to solve your situation. You can use that directive right on the input. It allows your code to be reusable like a component, but because it is actually on the input, it can have access to the native html element. Here is an example using a currency input I wrote, to get a feel for what to do. I added a spot for the focus event to show you where to do stuff. Obviously, you'd want to change the name and erase the code with the decimal pipe, but I left it for reference.

@Directive({
  selector: '[appCurrencyInput]',
})
export class CurrencyInputDirective implements OnInit {
  @Input() runOnEveryModelChange = false;

  public reference: ElementRef<HTMLInputElement>;
  private numTimesRan = 0;
  constructor(el: ElementRef, private decimalPipe: DecimalPipe) {
    this.reference = el;
  }

  @HostListener('focus', ['$event'])
  private onFocus = (focusEvent: FocusEvent) => {
    //You could do showNotifications() here, have access to focusEvent if you want
  }

  @HostListener('ngModelChange', ['$event'])
  private onModelChange = (ngModelChangeEvent: number) => {
    this.numTimesRan++;
    if (this.runOnEveryModelChange || this.numTimesRan === 1) {
      const asNumber = this.decimalPipe.transform(ngModelChangeEvent, '1.2-2');
      this.reference.nativeElement.value = asNumber ? asNumber.replace(/,/g, '') : null;
    }
  };

  ngOnInit() {
    //here you could do something if you want, just like a component
  }
}

And you would use it like so

<input formControlName="myName" appCurrencyInput>

Upvotes: 0

Mariel Quezada
Mariel Quezada

Reputation: 101

I found something that may be helpfull if you want to get some information from the Abstractcontrol as it was a normal Html Input Element, for example if you want to add a "Event Listener" to it, in order to know when user is interacting with the element, It could be done by using the valueChanges or statusChanges, both Observables from class AbstractControl, i used it to know when a element has changed and get the option selected to do more actions before the form is submited, I leave here my code example:

getValue() {
  this.myForm.get('myControlName').valueChanges.subscribe((optionValue) => {
    console.log(optionValue);
    this.doSomething(optionValue);
  });
}

And if you want to made some animation or changes when the user hover the parent maybe you can use some simple css trick like this:

.parent #child{
  opacity: 0;
}

.parent:hover #child{
  opacity: 1;
}

You can check this documentation for more details : https://angular.io/api/forms/AbstractControl#valueChanges

Upvotes: 0

Saikiran Jamiligiri
Saikiran Jamiligiri

Reputation: 1

This is very Basic. FormContrl is not required. you should create a local variable on input element with # and pass local variables to method

<div>
<label for="firstname">First Name</label>
<input name="fname" #nameFirst />
<label for="lastname">Last Name</label>
<input name="lname" #nameLast />
</div>
<button (click)="send(nameFirst , nameLast)">Submit</button>

export class HtmlExample {

    send(firstName: HTMLInputElement, lastName: HTMLInputElement): void {
        console.log(`Hello ${firstName.value}  ${lastName.value}`);
        firstName.value = '';
        lastName.value = '';
    }

}

Upvotes: -3

martin
martin

Reputation: 96969

I think you could use # notation to pass the element to ShowNotificationsComponent:

export class ShowNotificationsComponent {
    @Input() the_control;
    @Input() elm;
    //...
}

Then in the template:

<input formControlName="myName" #elm>
<show-notifications [the_control]="myName" [elm]="elm"></show-notifications>

Upvotes: 4

Related Questions