@@ -663,6 +663,189 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]');
663663 expect ( input . nativeElement . getAttribute ( 'disabled' ) ) . toBe ( null ) ;
664664 } ) ;
665665 } ) ;
666+
667+ describe ( 'dynamic change of FormGroup and FormArray shapes' , ( ) => {
668+ it ( 'should handle FormControl and FormGroup swap' , ( ) => {
669+ @Component ( {
670+ template : `
671+ <form [formGroup]="form">
672+ <input formControlName="name" id="standalone-id" *ngIf="!showAsGroup">
673+ <ng-container formGroupName="name" *ngIf="showAsGroup">
674+ <input formControlName="control" id="inside-group-id">
675+ </ng-container>
676+ </form>
677+ `
678+ } )
679+ class App {
680+ showAsGroup = false ;
681+ form ! : FormGroup ;
682+
683+ useStandaloneControl ( ) {
684+ this . showAsGroup = false ;
685+ this . form = new FormGroup ( {
686+ name : new FormControl ( 'standalone' ) ,
687+ } ) ;
688+ }
689+
690+ useControlInsideGroup ( ) {
691+ this . showAsGroup = true ;
692+ this . form = new FormGroup ( {
693+ name : new FormGroup ( {
694+ control : new FormControl ( 'inside-group' ) ,
695+ } )
696+ } ) ;
697+ }
698+ }
699+
700+ const fixture = initTest ( App ) ;
701+ fixture . componentInstance . useStandaloneControl ( ) ;
702+ fixture . detectChanges ( ) ;
703+
704+ let input = fixture . nativeElement . querySelector ( 'input' ) ;
705+ expect ( input . id ) . toBe ( 'standalone-id' ) ;
706+ expect ( input . value ) . toBe ( 'standalone' ) ;
707+
708+ // Replace `FormControl` with `FormGroup` at the same location
709+ // in data model and trigger change detection.
710+ fixture . componentInstance . useControlInsideGroup ( ) ;
711+ fixture . detectChanges ( ) ;
712+
713+ input = fixture . nativeElement . querySelector ( 'input' ) ;
714+ expect ( input . id ) . toBe ( 'inside-group-id' ) ;
715+ expect ( input . value ) . toBe ( 'inside-group' ) ;
716+
717+ // Swap `FormGroup` with `FormControl` back at the same location
718+ // in data model and trigger change detection.
719+ fixture . componentInstance . useStandaloneControl ( ) ;
720+ fixture . detectChanges ( ) ;
721+
722+ input = fixture . nativeElement . querySelector ( 'input' ) ;
723+ expect ( input . id ) . toBe ( 'standalone-id' ) ;
724+ expect ( input . value ) . toBe ( 'standalone' ) ;
725+ } ) ;
726+
727+ it ( 'should handle FormControl and FormArray swap' , ( ) => {
728+ @Component ( {
729+ template : `
730+ <form [formGroup]="form">
731+ <input formControlName="name" id="standalone-id" *ngIf="!showAsArray">
732+ <ng-container formArrayName="name" *ngIf="showAsArray">
733+ <input formControlName="0" id="inside-array-id">
734+ </ng-container>
735+ </form>
736+ `
737+ } )
738+ class App {
739+ showAsArray = false ;
740+ form ! : FormGroup ;
741+
742+ useStandaloneControl ( ) {
743+ this . showAsArray = false ;
744+ this . form = new FormGroup ( {
745+ name : new FormControl ( 'standalone' ) ,
746+ } ) ;
747+ }
748+
749+ useControlInsideArray ( ) {
750+ this . showAsArray = true ;
751+ this . form = new FormGroup ( {
752+ name : new FormArray ( [
753+ new FormControl ( 'inside-array' ) //
754+ ] )
755+ } ) ;
756+ }
757+ }
758+
759+ const fixture = initTest ( App ) ;
760+ fixture . componentInstance . useStandaloneControl ( ) ;
761+ fixture . detectChanges ( ) ;
762+
763+ let input = fixture . nativeElement . querySelector ( 'input' ) ;
764+ expect ( input . id ) . toBe ( 'standalone-id' ) ;
765+ expect ( input . value ) . toBe ( 'standalone' ) ;
766+
767+ // Replace `FormControl` with `FormArray` at the same location
768+ // in data model and trigger change detection.
769+ fixture . componentInstance . useControlInsideArray ( ) ;
770+ fixture . detectChanges ( ) ;
771+
772+ input = fixture . nativeElement . querySelector ( 'input' ) ;
773+ expect ( input . id ) . toBe ( 'inside-array-id' ) ;
774+ expect ( input . value ) . toBe ( 'inside-array' ) ;
775+
776+ // Swap `FormArray` with `FormControl` back at the same location
777+ // in data model and trigger change detection.
778+ fixture . componentInstance . useStandaloneControl ( ) ;
779+ fixture . detectChanges ( ) ;
780+
781+ input = fixture . nativeElement . querySelector ( 'input' ) ;
782+ expect ( input . id ) . toBe ( 'standalone-id' ) ;
783+ expect ( input . value ) . toBe ( 'standalone' ) ;
784+ } ) ;
785+
786+ it ( 'should handle FormGroup and FormArray swap' , ( ) => {
787+ @Component ( {
788+ template : `
789+ <form [formGroup]="form">
790+ <ng-container formGroupName="name" *ngIf="!showAsArray">
791+ <input formControlName="control" id="inside-group-id">
792+ </ng-container>
793+ <ng-container formArrayName="name" *ngIf="showAsArray">
794+ <input formControlName="0" id="inside-array-id">
795+ </ng-container>
796+ </form>
797+ `
798+ } )
799+ class App {
800+ showAsArray = false ;
801+ form ! : FormGroup ;
802+
803+ useControlInsideGroup ( ) {
804+ this . showAsArray = false ;
805+ this . form = new FormGroup ( {
806+ name : new FormGroup ( {
807+ control : new FormControl ( 'inside-group' ) ,
808+ } )
809+ } ) ;
810+ }
811+
812+ useControlInsideArray ( ) {
813+ this . showAsArray = true ;
814+ this . form = new FormGroup ( {
815+ name : new FormArray ( [
816+ new FormControl ( 'inside-array' ) //
817+ ] )
818+ } ) ;
819+ }
820+ }
821+
822+ const fixture = initTest ( App ) ;
823+ fixture . componentInstance . useControlInsideGroup ( ) ;
824+ fixture . detectChanges ( ) ;
825+
826+ let input = fixture . nativeElement . querySelector ( 'input' ) ;
827+ expect ( input . id ) . toBe ( 'inside-group-id' ) ;
828+ expect ( input . value ) . toBe ( 'inside-group' ) ;
829+
830+ // Replace `FormGroup` with `FormArray` at the same location
831+ // in data model and trigger change detection.
832+ fixture . componentInstance . useControlInsideArray ( ) ;
833+ fixture . detectChanges ( ) ;
834+
835+ input = fixture . nativeElement . querySelector ( 'input' ) ;
836+ expect ( input . id ) . toBe ( 'inside-array-id' ) ;
837+ expect ( input . value ) . toBe ( 'inside-array' ) ;
838+
839+ // Swap `FormArray` with `FormGroup` back at the same location
840+ // in data model and trigger change detection.
841+ fixture . componentInstance . useControlInsideGroup ( ) ;
842+ fixture . detectChanges ( ) ;
843+
844+ input = fixture . nativeElement . querySelector ( 'input' ) ;
845+ expect ( input . id ) . toBe ( 'inside-group-id' ) ;
846+ expect ( input . value ) . toBe ( 'inside-group' ) ;
847+ } ) ;
848+ } ) ;
666849 } ) ;
667850
668851 describe ( 'user input' , ( ) => {
0 commit comments