3

Here is the stackblitz for the code below. I am making a fairly complex deeply nested Angular form using FormArrays, and child components. For the most part, everything is working as expected. A group object contains a conjunctor, conditions[], and groups[]. groups[] is a nested group that can be nested infinitely containing the same object.

Currently, you have the option to "add group", "add nested group", "add condition", "delete group", and "delete condition" so that it makes a nested form object. To do this, the app is split into 3 components:

AppComponent: Holds the main form with ng-containers and *ngFor to iterate over the groups

GroupControlComponent: Holds the logic of each item inside the AppComponent and does the same thing as the AppComponent but for Conditions and Nested Groups

ConditionsForm: Holds the logic of the conditions items

And there is an ActionsButtonsBarComponent to hold the buttons to emit events, add, and remove the groups and conditions.

I am trying to make it so on every second group, there is an input for the Conjunctor. I don't want it at all on the first instance since I want the first to always be null. However, on the 2nd and thereafter instances, I want the conjunctor input to appear giving the option to be either "AND" or "OR". As I create this, I am getting the error: ERROR: Cannot find control with path: 'statement → groups → 1 → conjunctor coming from the AppComponent.

Here's how the AppComponent looks:

<form [formGroup]="_form">
    <ng-container formGroupName="statement">
        <ng-container formArrayName="groups">
            <ng-container *ngFor="let group of _groupsFormArray?.controls; index as i">
         <div *ngIf="i > 0">
           <div [formGroupName]="i">
             <input type="text" formControlName="conjunctor">
           </div>
         </div>
            <app-group-control 
             (remove)="_delete(i)"
             [formControlName]="i"
             [formLabel]="'Group '+ (i + 1) + ':'">
            </app-group-control>
            </ng-container>
        </ng-container>
    </ng-container>
</form>

As you can see, there is a div containing the ngIf logic:

<div *ngIf="i > 0">
  <div [formGroupName]="i">
    <input type="text" formControlName="conjunctor">
  </div>
</div>

This method is not working, but neither have the other methods I have tried.

So far, I have tried changing the FormGroupName many times to groups, statements, i, index, resulting in no improvement.

I have also tried tracking the instances of each ngFor from the AppComponent using @ViewChildren('templateRef') templateRefVar: QueryList<ElementRef>; and then inside the ngFor, using a template ref #templateRef. From there, I pass this templateRef.length to my child component GroupControlComponent using @Input() groupInstances , and then using the ngIf inside. This is the closest I have gotten, but the problem is, every single time the ngIf condition is met, it appears on EVERY instance of the group arrays, including the first, as well as gives me the error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: '0'. Current value: '1'.. Here is a StackBlitz of the closest I've got using this method. And here is the StackBlitz from the code as of above.

If you'd like to find more about this recursive form, you can read about that here.

Jeremy
  • 1,038
  • 11
  • 34

1 Answers1

2

Stackblitz demo

Let's replace groupInstance for showConjunctor just to make it more obvious about what it's there for.

You can do this in you app.component.hml:

<app-group-control 
  #templateRef
  (remove)="_delete(i)"
  [formControlName]="i"
  [formLabel]="'Group '+ (i + 1) + ':'"
  [showConjunctor]="!((i + 1) % 2)">
</app-group-control>

I'm considering that i, in the above snippet, is the index of the current loop in *ngFor (like in the Stackblitz demo).

Also, remove this part from the app.component.html:

<div *ngIf="i > 0">
  <div [formGroupName]="i">
    <input type="text" formControlName="conjunctor">
  </div>
</div>

[UPDATE]: Per your comments, if you want to have the conjunctor in all of the nested Groups, you can set the @Input() showConjuntor to true inside the GroupControlComponent (Stackblitz demo):

<ng-container formArrayName="groups">
  <app-group-control *ngFor="let s of _groupsFormArray?.controls; index as i"
                      (remove)="_deleteGroupFromArray(i)" 
                      [formControlName]="i"
                      [formLabel]="'Nested Group '+ (i + 1) + ':'"
                      [showConjunctor]="true">
  </app-group-control>
</ng-container>
julianobrasil
  • 8,954
  • 2
  • 33
  • 55
  • I have implemented this, but the problem is unfortunately the same: https://stackblitz.com/edit/httpsstackoverflowcoma623555906433166-vs11ym , as you can see, when you click the button for the ngIf to become true, it displays the input on every instance, not just the 2nd. The first group has nothing, but when you click "add group" a second time, both the first and the second group have an input (instead of everything except the first). There is also the error that the expression is changed after checked, but i'm not too worried about that one – Jeremy Jun 26 '20 at 13:32
  • Oh, I had misunderstand your question and focused just on the errors. Well, I'll update my answer. – julianobrasil Jun 26 '20 at 14:30
  • I think this looks great. Ps, the stackblitz link does not appear to be working – Jeremy Jun 26 '20 at 15:00
  • I am very sorry for all the questions. I thought it would be important to add the conjuntor to the nested groups as well as the main groups (every nested group would require a conjunctor). I am getting a similar error as before where "cannot find control with path"... https://stackblitz.com/edit/httpsstackoverflowcoma626665326433166-2bpqqo in the group-control-component I tried to add a conjunctor to the nested group in order to manipulate the nested group conjunctor. It does not work like the non-nested group does though.. – Jeremy Jul 07 '20 at 17:34
  • If I got right what you want, just put something like `` inside the `GroupControlComponentData` template, between `` and `` – julianobrasil Jul 07 '20 at 19:21
  • Looks like this did the trick, I just need to find a way to make it show only on nested groups rather than standard groups as well. Thank you – Jeremy Jul 07 '20 at 21:32
  • Seems like the best way to do the above would be to split out again into another component. Maybe nested-group-control? What do you think? Then inside the template, display the conditions? – Jeremy Jul 08 '20 at 04:33
  • I don't think that's necessary. I'll try to add some info to my answer to reach this use case. – julianobrasil Jul 08 '20 at 14:37