When you want to implement a custom component for data-binding to an individual value the correct Angular way to do that is to go with the approach where the parent view specifies either [formControl] or [formControlName].
<app-form-group formControlName="name"></app-form-group>
<app-form-group formControlName="email"></app-form-group>
<app-form-group formControlName="other"></app-form-group>
In your child control you need to do the following:
First add the following provider to your @Component declaration, so Angular knows your component can work with [formControlName] / [formControl]
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CountrySelectorComponent),
multi: true
}
]
Your child component's class then needs to implement the ControlValueAccessor interface. This should be a fully working example, if not then let me know and I will change the code, I typed it into the browser directly.
@Component({
// Ensure [formControl] or [formControlName] is also specified
selector: '[formControl] app-form-group, [formControlName] app-form-group',
templateUrl: './country-selector.component.html',
styleUrls: ['./country-selector.component.scss'],
providers: [
{
// Provide the value accessor
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CountrySelectorComponent),
multi: true
}
]
})
export class CountrySelectorComponent implements ControlValueAccessor {
private value: any;
private hasHadFocus = false;
private hasNotifiedTouched = false;
private propagateChange: any = () => {};
private propogateTouched: any = () => {};
public changed(event: any) {
this.value = event.srcElement.value;
this.propagateChange(this.value);
}
/**
* Called when (focus) is triggered
*/
public focused() {
this.hasHadFocus = true;
}
/**
* Called when (blur) is triggered
*/
public blurred() {
if (this.hasHadFocus && !this.hasNotifiedTouched) {
// Only notify once, and only if lost focus after a focus
this.hasNotifiedTouched = true;
this.propogateTouched();
}
}
/**
* Called when a new value is set via code
* @param obj
*/
writeValue(obj: any): void {
this.value = obj;
}
/**
* Register a call back to call when our value changes
* @param fn
*/
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
/**
* Register a call back to call when our value is first touched
* @param fn
*/
registerOnTouched(fn: any): void {
this.propogateTouched = fn;
}
}
Your app-form-group template would look something like this
<div class="form-group">
<label class="col-md-2 control-label">{{Name}}</label>
<div class="col-md-9">
<input class="form-control" (blur)="blurred()" focus="focussed()" change="changed($event)">
</div>
- Add
[formGroup]="signupForm" to each of your app-form-group instances in your main view
- Implement OnInit in your
app-form-group control.
- Add
private controlContainer: ControlContainer to the constructor of your app-form-group component so Angular will inject it
- Add
public form: FormGroup; property to your app-form-group component.
In ngOnInit add the following code
this.form = this.containerControl.control;
In your app-form-group template you would add [formGroup] like so
<div class="form-group" [formGroup]="form">
<label class="col-md-2 control-label">{{Name}}</label>
<div class="col-md-9">
<input class="form-control" [name]="name" [formControlName]="formCtrlName">
</div>
This is the method that requires the least amount of code, and is the one I would recommend when you want to embed composite controls that edit multiple pieces of data (like an Address).
From this blog -> https://mrpmorris.blogspot.co.uk/2017/08/angular-composite-controls-formgroup-formgroupname-reactiveforms.html
ControlValueAccessoras guided here blog.thoughtram.io/angular/2016/07/27/… - Harry Ninh