Recursive Dynamic Angular Form; EmitEvent from Deeper Child to Root (Parent Component)

759 views Asked by At

Hi I'm newbie and I'm creating a project with a Reactive Form; based on Recursive Component that creates Dynamic Form from JSON file. The Sources

This is an adaptation from Ionic based on Creating Dynamic Angular Forms with JSON

I Adapted the Recursive version procedures and other changes! My code is located in Stackblitz.

My component SelectsComponent located in the selects.component.ts file, has the selector selector: 'selects', I have:

  @Output() addControl = new EventEmitter<JsonFormControls>();

and the method

  public onSelectChange(event: MatSelectChange) {
    console.log(this.form.value);
    console.log('parent:' + this.parentControl || 'root');
    this.control.value = event + '';
    if (this.control.children) {
      this.recursiveConcealer(this.control.children);
      const child = this.control.children.find(
        (child) => child.value === event + ''
      );
      this.newControl(child);
      if (child.siblings) {
        for (let sibling of child.siblings) {
          this.newControl(sibling);
        }
      }
      //Emit Event to Root
      this.addControl.emit(child);
    }
  }

When the onSelectChange method is called, then emit is performed this.addControl.emit(child);

In the template selects.component.html file, I have this code:

  <ng-container *ngFor="let child of control?.children">
    <div fxFlex="100%">
      <selects
        *ngIf="child.type === 'select'"
        [control]="child"
        [parentControl]="control"
        [formBuilder]="formBuilder"
      ></selects>
    </div>
  </ng-container>

I have the JsonFormComponent located in the json-form.component.ts file, with this method:

  public onAddControl(addControlEvent: JsonFormControls) {
    this.addControl(addControlEvent);
    addControlEvent.visible = true;
  }

In it's template json-form.component.html file, with this code:

<selects
  *ngIf="control.type === 'select'"
  [control]="control"
  [visible]="true"
  (addControl)="onAddControl($event)"
  [formBuilder]="myFormBuilder"
></selects>

The problem raise when some Child-component (SelectsComponent) is not a direct son of the Root Parent-Component (JsonFormComponent), maybe is a grandson or depeer relation, as:

(level 0)JsonFormComponent -> (level 1)SelectsComponent -> (level 2)SelectsComponent -> (level 3)SelectsComponent

How re-emit from SelectsComponent child to SelectsComponentparent.

QUESTIONS:

  1. How to send or Emit Event from (level 3)SelectsComponent to (level 0)JsonFormComponent ?
  2. How to send some Event of Acknowledgment from (level 0)JsonFormComponent to (level 3)SelectsComponent?

Thanks in advance!

I appreciate answer not based on services that I know could be an alternative.

EDIT:

enter image description here

What is the Console.log(...) expected?

console.log(`parent: ${this.parentControl?this.parentControl.name:'root'} -> control.name: ${this.control.name} -> Emitter: ${e?.name}` );

The first(or root) component is company

Something like:

parent: root -> control.name: company -> {Here the last node child}.

According to the click in the previous image

1st click (first level):

parent: root -> control.name: company -> Emitter: Petitioner (C2 -> P2).

2nd click (second level):

parent: root -> control.name: company -> Emitter: Service (C2 -> P2 -> S2).

Example: 3nd click (third level):

parent: root -> control.name: company -> Emitter: Request (C2 -> P2 -> S2 -> R2).

The console.log (or future operations) only will be performed in the context of the root (that is company).

2

There are 2 answers

0
Phalgun On BEST ANSWER

Here's the updated solution in https://stackblitz.com/edit/angular-ivy-6kccps

1.How to send or Emit Event from (level 3)SelectsComponent to (level 0)JsonFormComponent ?

Listen to the addControl event emitted by child selects component in the selects.component.html

  <selects
    *ngIf="child.type === 'select'"
    [control]="child"
    [parentControl]="control"
    [formBuilder]="formBuilder"
    (addControl)="handleAddControl($event)"
  ></selects>

Emit the event

handleAddControl(data: any) {
    this.addControl.emit(data);
  }

This will send events from every instance of SelectsComponent to be bubbled up to the top JsonFormComponent

2.How to send some Event of Acknowledgment from (level 0)JsonFormComponent to (level 3)SelectsComponent?

I am not really sure what is needed in terms of sending an acknowledge. However, I assume upon receiving an acknowledge in the original instance you would be performing some action, like updating some property. It can be achieved by adding a callback to the event payload as follows: selects.component.ts

 //Emit Event to Root
public onSelectChange(event: MatSelectChange) {
...
...
...
 const data = {child,
  callback: () => {
    console.log('Executing callback for control with label', this.control.name)
  }
}   

this.addControl.emit(data);

Execute the callback in json-form.component.ts

  public onAddControl(addControlEvent: any) {
    if (addControlEvent.callback) {
      addControlEvent.callback();
    }
    this.addControl(addControlEvent);
    addControlEvent.visible = true;
  }
6
Yan Koshelev On

There are answers to your questions(I hope);

  1. You can inject SelectsComponent into himself:

    @Optional() @SkipSelf() private selectsComponent: SelectsComponent

and then subscribe on self addControl EE and push his value to injected instance:

ngOnInit(): void {
    if (this.selectsComponent) {
      this.addControl
        .subscribe(e => {
          this.selectsComponent.addControl.next(e);
        });
    }

    this.form = this.rootFormGroup.form;
  }

In this case, you are synchronizing all nested EEs;

  1. For your second question I think the solution is to pass data/subject into the Input decorator of each SelectComponent(bind to children selects Input data which you get into host component).

I have prepared an example of how your first question works, check SelectsComponent. As see each child component triggers his parent addControl: Stackblitz