Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2 - formControlName inside component

I want to create a custom input component that I can use with the FormBuilder API. How do I add formControlName inside a component?

Template:

<label class="custom-input__label"           *ngIf="label">         {{ label }} </label> <input class="custom-input__input"         placeholder="{{ placeholder }}"        name="title" /> <span class="custom-input__message"        *ngIf="message">         {{ message }} </span> 

Component:

import {     Component,     Input,     ViewEncapsulation } from '@angular/core';  @Component({     moduleId: module.id,     selector: 'custom-input',     host: {         '[class.custom-input]': 'true'     },     templateUrl: 'input.component.html',     styleUrls: ['input.component.css'],     encapsulation: ViewEncapsulation.None, }) export class InputComponent {     @Input() label: string;     @Input() message: string;     @Input() placeholder: string; } 

Usage:

<custom-input label="Title"             formControlName="title" // Pass this to input inside the component> </custom-input> 
like image 248
Rit_XPD Avatar asked Sep 23 '16 12:09

Rit_XPD


People also ask

Can we give formControlName to div?

<div> is a readonly element, usually there's no point in binding form controls to it. You can also create your own custom components (or directives) that can be bound to form controls—if they implement ControlValueAccessor interface.

What is the difference between formControlName and FormControl?

[formControl] assigns a reference to the FormControl instance you created to the FormControlDirective . formControlName assigns a string for the forms module to look up the control by name. If you create variables for the controls, you also don't need the .

Can we use formControlName without FormGroup?

Without a parent FormGroup, [formControl]="name" worked earlier because that directive can stand alone, that is, it works without being in a FormGroup. With a parent FormGroup, the name input needs the syntax formControlName=name in order to be associated with the correct FormControl in the class.


2 Answers

You should not be adding formControlName attribute to the input field in the template of your custom component. You should be adding the formControlName on the custom-input element itself as per the best practice.

Here what you can use in your custom-input component is the controlValueAccessor interface to make your custom-input have the value updated whenever there is an event of input field in the template of your custom input changed or blurred.

It provides a connection (to update values or other needs) between the form control behavior of your custom input and the UI you are providing for that custom form control.

Below is the code of a custom input component in TypeScript.

import { Component, Input, forwardRef, AfterViewInit, trigger, state, animate, transition, style, HostListener, OnChanges, ViewEncapsulation, ViewChild, ElementRef } from '@angular/core'; import { NG_VALUE_ACCESSOR, ControlValueAccessor, FormControl } from '@angular/forms';  export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {     provide: NG_VALUE_ACCESSOR,     useExisting: forwardRef(() => InputComponent),     multi: true };  @Component({   selector: 'inv-input',   templateUrl:'./input-text.component.html',     styleUrls: ['./input-text.component.css'],     encapsulation: ViewEncapsulation.None,   providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR],     animations:[trigger(         'visibilityChanged',[             state('true',style({'height':'*','padding-top':'4px'})),             state('false',style({height:'0px','padding-top':'0px'})),             transition('*=>*',animate('200ms'))         ]     )] })  export class InputComponent implements ControlValueAccessor, AfterViewInit, OnChanges {      // Input field type eg:text,password     @Input()  type = "text";       // ID attribute for the field and for attribute for the label     @Input()  idd = "";       // The field name text . used to set placeholder also if no pH (placeholder) input is given     @Input()  text = "";       // placeholder input     @Input()  pH:string;       //current form control input. helpful in validating and accessing form control     @Input() c:FormControl = new FormControl();       // set true if we need not show the asterisk in red color     @Input() optional : boolean = false;      //@Input() v:boolean = true; // validation input. if false we will not show error message.      // errors for the form control will be stored in this array     errors:Array<any> = ['This field is required'];       // get reference to the input element     @ViewChild('input')  inputRef:ElementRef;        constructor() {      }      ngOnChanges(){      }      //Lifecycle hook. angular.io for more info     ngAfterViewInit(){          // set placeholder default value when no input given to pH property               if(this.pH === undefined){             this.pH = "Enter "+this.text;          }          // RESET the custom input form control UI when the form control is RESET         this.c.valueChanges.subscribe(             () => {                 // check condition if the form control is RESET                 if (this.c.value == "" || this.c.value == null || this.c.value == undefined) {                     this.innerValue = "";                           this.inputRef.nativeElement.value = "";                                  }             }         );     }     //The internal data model for form control value access     private innerValue: any = '';      // event fired when input value is changed . later propagated up to the form control using the custom value accessor interface     onChange(e:Event, value:any){         //set changed value         this.innerValue = value;         // propagate value into form control using control value accessor interface         this.propagateChange(this.innerValue);          //reset errors          this.errors = [];         //setting, resetting error messages into an array (to loop) and adding the validation messages to show below the field area         for (var key in this.c.errors) {             if (this.c.errors.hasOwnProperty(key)) {                 if(key === "required"){                     this.errors.push("This field is required");                 }else{                     this.errors.push(this.c.errors[key]);                 }                           }         }     }        //get accessor     get value(): any {         return this.innerValue;     };      //set accessor including call the onchange callback     set value(v: any) {         if (v !== this.innerValue) {             this.innerValue = v;         }     }      //propagate changes into the custom form control     propagateChange = (_: any) => { }      //From ControlValueAccessor interface     writeValue(value: any) {         this.innerValue = value;     }      //From ControlValueAccessor interface     registerOnChange(fn: any) {         this.propagateChange = fn;     }      //From ControlValueAccessor interface     registerOnTouched(fn: any) {      } } 

Below is the template HTML for the custom input component

<div class="fg">       <!--Label text-->       <label [attr.for]="idd">{{text}}<sup *ngIf="!optional">*</sup></label>       <!--Input form control element with on change event listener helpful to propagate changes -->       <input type="{{type}}" #input id="{{idd}}" placeholder="{{pH}}" (blur)="onChange($event, input.value)">       <!--Loop through errors-->       <div style="height:0px;" [@visibilityChanged]="!c.pristine && !c.valid" class="error">             <p *ngFor="let error of errors">{{error}}</p>       </div> </div> 

Below is custom input component which can be used in a fromGroup or individually

<inv-input formControlName="title" [c]="newQueryForm.controls.title" [optional]="true" idd="title" placeholder="Type Title to search"           text="Title"></inv-input> 

In this fashion if you implement your custom form controls you can apply your custom validator directives easily and accumulate the errors on that form control to display your errors.

One can imitate the same style to develop custom select component, radio button group, checkbox, textarea, fileupload etc in the above fashion with minor changes as per what the form control's behavior demands.

like image 197
web-master Avatar answered Oct 11 '22 19:10

web-master


Angular 8 and 9: Use viewProvider in you custom component. Working example:

@Component({     selector: 'app-input',     templateUrl: './input.component.html',     styleUrls: ['./input.component.scss'],     viewProviders: [         {             provide: ControlContainer,             useExisting: FormGroupDirective         }     ] }) 

Now, when you assign formControlName, your component will attach itself to the parent form.

<input matInput formControlName="{{name}}"> 

or

<input matInput [formControlName]='name'> 
like image 34
darko99 Avatar answered Oct 11 '22 19:10

darko99