I have a lot of form-groups with labels and inputs.I wanted to make ng-template which will be reusable
So i had
<div class="form-group">
<label class="form-control-label" for="address">Address</label>
<input [disabled]="isTheLoggedInUserAdmin" id="address" class="form-control form-
control-alternative"
[(ngModel)]="sharedService.tempUser.address" name="address" type="text">
</div>
and with ng-template it is converted to
<ng-template [ngTemplateOutlet]="formGroup"
[ngTemplateOutletContext]="{data: {inputId:'address', label:'Address', ngModel:
sharedService.tempUser.address }}"
></ng-template>
<ng-template #formGroup let-data="data">
<div class="form-group">
<label class="form-control-label" [for]="data.inputId">{{data.label}}</label>
<input [disabled]="isTheLoggedInUserAdmin" [id]="data.inputId" class="form-control form-control-
alternative"
[(ngModel)]="sharedService.tempUser.address" [name]="data.inputId" type="text">
</div>
</ng-template>
so i am passing here inputId, label name automatically and for now the ngModel is hardcoded it is pointing to sharedService.tempUser.address
But my ng-template needs to be dynamic so with ng-template call i should pass argument like label for example
- tthe argument shpuld point to different ngModel variables in my typescript files
But when i do that
<div class="form-group">
<label class="form-control-label" [for]="data.inputId">{{data.label}}</label>
<input [disabled]="isTheLoggedInUserAdmin" [id]="data.inputId" class="form-control form-control-alternative"
[(ngModel)]="data.ngModel" [name]="data.inputId" type="text">
</div>
now data.ngModel
is sended from the ng-template call - which is sharedService.tempUser.address
, i get the actual value from sharedService.tempUser.address
but *THE PROBLEM IS THAT ngModel does not work here`
when i type something it is not updated
How can i solve this ?
You might want to write an implementation of ControlValueAccessor
as an alternative. It's an Angular official way to create reusable component compatible with Form API. A minimum example will be something like this:
my-custom-control.component.html
<div class="form-group">
<label class="form-control-label" for="{{data.field}}">{{data.label}}</label>
<input [disabled]="disabled" id="{{data.field}}" class="form-control form-control-alternative"
[value]="value" name="{{data.field}}" type="text"
(change)="onChange($event)" (blur)="onBlur($event)">
</div>
my-custom-control.component.ts
/**
import things....
**/
@Component({
selector: 'MyControl',
templateUrl: './my-custom-control.component.html',
styleUrls: ['./my-custom-control.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyControlComponent),
multi: true
}]
})
export class MyControlComponent implements OnInit, ControlValueAccessor {
@Input() value: any;
@Input() data: { 'field': string, 'label': string } = { field: "default", label: "default" };
@Input() disabled: boolean = false;
setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
}
propagateChange = (_: any) => { };
propagateTouch = () => { };
registerOnChange(fn) {
this.propagateChange = fn;
}
registerOnTouched(fn) {
this.propagateTouch = fn;
}
constructor() {
}
ngOnInit() {
}
// value changed from ui
onChange(event: any){
this.value = event.target.value;
// tell angular that the control value is changed
this.propagateChange(this.value);
}
// received value from form api
writeValue(newFormValue: any) {
this.value = newFormValue;
}
// tell angular that this form control is touched
onBlur(event: any) {
this.propagateTouch();
}
}
After that, you could reuse this component like:
<!-- With FormGroup -->
<form [formGroup]="myForm">
<MyControl formControlName="address" [disabled]="isTheLoggedInUserAdmin" [data]="{ field: 'address', label: 'Address' }">
<MyControl formControlName="title" [disabled]="isTheLoggedInUserAdmin" [data]="{ field: 'title', label: 'Title' }">
<MyControl formControlName="surname" [disabled]="isTheLoggedInUserAdmin" [data]="{ field: 'surname', label: 'Surname' }">
<!-- other controls... -->
</form>
<!-- Without FormGroup -->
<div>
<MyControl [(ngModel)]="myModel.address" [ngModelOptions]="{standalone: true}" [disabled]="isTheLoggedInUserAdmin" [data]="{ field: 'address', label: 'Address' }">
<MyControl [(ngModel)]="myModel.title" [ngModelOptions]="{standalone: true}" [disabled]="isTheLoggedInUserAdmin" [data]="{ field: 'title', label: 'Title' }">
<MyControl [(ngModel)]="myModel.surname" [ngModelOptions]="{standalone: true}" [disabled]="isTheLoggedInUserAdmin" [data]="{ field: 'surname', label: 'Surname' }">
<!-- other controls... -->
</div>
which can also be easily written into an ngFor
if you have a model description array.
Edit: Add a sample scss styling.
my-custom-control.component.scss
@import "variable.scss";
:host(.ng-dirty.ng-invalid, .ng-touched.ng-invalid) input {
border-color: $c-alert !important;
border-width: 2px;
border-style: solid;
color: $c-alert;
}