I have following app structure:
--------------------------------------------
| Parent |
| ------------------------------------------ |
|| Header ||
|| <form #headerForm="ngForm"> ||
|| ... ||
|| </form> ||
| ------------------------------------------ |
| ------------------------------------------ |
|| Content ||
|| <form #contentForm="ngForm"> ||
|| ... ||
|| ||
|| </form> ||
|| ||
| ------------------------------------------ |
--------------------------------------------
I would like to reset the states of the forms in the header and content from the Parent component. I tried to use @ViewChild/@ViewChildren from the parent component, but I do not know how to access the childs form refs.
So far I was forced to get a reference of the Header and Content components in the Parent and call a resetForm method:
export class ParentComponent {
@ViewChildren('componentWithForm') componentsWithForms: QueryList<unknown>;
...
this.componentsWithForms.forEach(component => (component as any).resetFormState());
@Component({
selector: 'app-header',
...
providers: [{ provide: 'componentWithForm', useExisting: forwardRef(() => HeaderComponent) }],
})
export class HeaderComponent {
@ViewChild('headerForm') headerForm: NgForm;
...
resetFormState() {
this.headerForm.reset();
}
Although this technically helps it leads to some ugly TypeScript casting - which I could work around creating a base-class ComponentWithForm, inheriting the Header/Content from it and setting the provider like
@Component({
selector: 'app-header',
...
providers: [{ provide: ComponentWithForm, useExisting: forwardRef(() => HeaderComponent) }],
})
export class HeaderComponent extends ComponentWithForm {
but I would actually like to get a direct reference to headerForm/contentForm which would save lot of hassle.
CodePudding user response:
It think you could use a better solution like @Input()/@Output()
for parent <-> child component communication. But if u want to use @ViewChild
, It will be best to start at official docs:
https://angular.io/guide/lifecycle-hooks#responding-to-view-changes
export class AfterViewComponent implements AfterViewChecked, AfterViewInit {
private prevHero = '';
// Query for a VIEW child of type `ChildViewComponent`
@ViewChild(ChildViewComponent) viewChild!: ChildViewComponent;
ngAfterViewInit() {
// viewChild is set after the view has been initialized
this.logIt('AfterViewInit');
this.doSomething();
}
// ...
}
EDITED:
You can make a base class for all your components with form that contains resetFormState() {}
method implementation:
@Directive()
export class YourFormDirective {
resetFormState() { //... }
}
export class HeaderComponent extends YourFormDirective {}
export class ContentComponent extends YourFormDirective {}
And then in parent, query for WithFormDirective
:
@ViewChildren(WithFormDirective) componentsWithForms!: QueryList<WithFormDirective>;
So you can call it without type casting:
this.componentsWithForms.forEach(component => component.resetFormState());
CodePudding user response:
I know that it's not a great change, but If each component with a form you have
providers:[{provide:'FORM', useExisting:forwardRef(() => YourComponent)}]
and
@ViewChild(NgForm) form
A directive like
@Directive({
selector: '[haveform]'
})
export class HaveFormDirective implements AfterViewInit {
form:NgForm
constructor(@Host() @Inject('FORM') public component:any, ){ }
ngAfterViewInit()
{
this.form=this.component.form
}
}
Allow you write
<component haveform></component>
And you can reach the "form" like
@ViewChildren(HaveFormDirective) forms:QueryList<HaveFormDirective>
this.forms.forEach(x=>console.log(x.form.value)