0

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.

karlitos
  • 1,604
  • 3
  • 27
  • 59
  • It's hard to answer without seeing the exact HTML structure, but assuming the children aren't PROJECTED components you might be checking in the wrong life cycle. Are you checking for them in AfterViewInit? – Taylor Ackley Feb 01 '23 at 19:42
  • 1
    Another option is to create a simple marker directive, add it to the child components and then query for that marker directive – Taylor Ackley Feb 01 '23 at 19:42
  • I do not know how to "check for elements in AfterViewInit, what I tried was something like @ViewChildren('headerForm') componentsWithForms: QueryList; to the Parent component, but this results in undefined – karlitos Feb 01 '23 at 19:59
  • But are you checking in the correct life cycle? it was unclear from your code snippet. You need to check in ngAfterViewInit – Taylor Ackley Feb 01 '23 at 20:59
  • Ah, do you mean when do I check for the @ViewChildren value ? In a button-click-handler in the Parent component - long, long after everything is initialized – karlitos Feb 01 '23 at 21:05
  • I don't know if this another approach meets your requirements (and sure don't resolve your question) but... why not create the forms in the "parent" and pass as `@Input` to your components? – Eliseo Feb 03 '23 at 07:08

2 Answers2

1

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)

a stackblitz

Eliseo
  • 50,109
  • 4
  • 29
  • 67
0

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());
  • Please look at the provided description, I already query for a component, what I want to is to query for a NgForm IN this component or for its Ref – karlitos Feb 01 '23 at 20:12
  • I know this approach, I mentioned it in my original question. But it feels like lot of boilerplate compared to having direct references to the NgForms of the children. – karlitos Feb 01 '23 at 21:46