I've got some data very similar defined by an ID and a description. Sometimes, there are additional attributes (a price, for example).
My goal is to create a simple form to get the ID and the description and if necessary, the others inputs.
I would like to create a form component (SimpleFormComponent
) where:
- The HTML file contains the form tag and inputs for the ID and the description.
- The TS file includes the business logic (API calls...).
If an entity has more properties, we can extend SimpleFormComponent
to add needed inputs and change the business logic.
In a first time, I've created a SimpleFormComponent
which can be use for basic data.
Its HTML template contains the form and a <ng-content></ng-content>
tag for needed inputs :
<form [formGroup]="form" (submit)="submit()">
<h3>{{ entityName }}</h3>
<input type="text" name="code" id="code" placeholder="Code" formControlName="code">
<input type="text" name="description" id="description"
placeholder="Description" formControlName="description">
<ng-content></ng-content>
<button type="reset">Cancel</button>
<button type="submit" [disabled]="form.invalid">Save</button>
</form>
Its .ts
file contains the business logic and necessary attributes like API endpoint or entity name :
@Component({
...
})
export class SimpleFormComponent implements OnInit {
protected ENTITY_NAME: string;
protected API_ENDPOINT: string;
public form: FormGroup = this.fb.group({
code: ['', [Validators.required]],
description: [''],
});
constructor(
private route: ActivatedRoute,
private fb: FormBuilder,
private api: ApiService
) {}
ngOnInit() {
// Get ENTITY_NAME and API_ENDPOINT from route parameters
}
submit() {
const toAdd = this.doBusinessLogic();
this.api.add(toAdd).subscribe({ ... });
}
private doBusinessLogic(): any {
let result;
// Do some stuff with result and this.form
return result;
}
}
This component works well for a basic entity (only an ID and a description).
Then, I've tried to create a TestFormComponent
inherited from SimpleFormComponent
.
Its HTML template looks like this:
<app-simple-technical-data>
<input type="text" name="test" id="test" formControlName="test">
</app-simple-technical-data>
And its .ts
file like this:
@Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.scss'],
})
export class TestComponent
extends SimpleFormComponent
implements OnInit
{
test = new FormControl('');
constructor(
route: ActivatedRoute,
router: Router,
formBuilder: FormBuilder,
ability: AppAbility,
technicalDataService: TechnicalDataService
) {
super(route, router, formBuilder, ability, technicalDataService);
}
override ngOnInit(): void {
super.ngOnInit();
this.form.addControl('test', this.test);
}
override submit() {
let result: any;
// Do another stuff with result
this.api.add(result).subscribe({ ... });
}
}
However, I get the following error :
ERROR Error: NG01050: formControlName must be used with a parent formGroup directive.
You'll want to add a formGroup directive and pass it an existing FormGroup instance
(you can create one in your class).
My "test" input is displayed but I can't get its data. In addition, it calls SimpleFormComponent.submit()
instead of TestComponent.submit()
.
I don't really know why it doesn't "see" the formGroup in the parent component and how fix this error.
If my structure is incorrect, how can I change it ?
Thanks in advance for your help and advices.
Your approach is difficult to implement. You are using content projection (ng-conent) for the additional form fields. But the html of ng-content is not just simply copied into the parent component's html. It will not work that way. Also you mixed two concepts: You connected TestComponent and SimpleFormComponent by two ways: You use SimpleFormComponent as a ChildComponent AND you inherit TestComponent form SimpleFormComponent. This causes problems like ngOnInit of SimpleFormComponent will be called twice (1x by directly calling super.ngOnInit and 1x by angular because of the usage of the component in the template).
My proposal for a solution is:
This would be a clean design IMHO.
Hints: