2

I followed Angular Reative Form guide that explains how to add a FormArray of Adrresses to a FormGroup.

Now I want to have a hero that can have different powers, selecting them from a select, or better from a dynamic array of select.

Passing from the example of Angular Docs to my desired functionality I can't make it to run.

This is my hero-form.ts

@Component({
  selector: 'app-hero-form',
  templateUrl: './hero-form.component.html',
  styleUrls: ['./hero-form.component.css']
})
export class HeroFormComponent implements OnInit, OnChanges {

  heroForm: FormGroup;
  nameChangeLog: string[] = [];
  hero: Hero = new Hero();
  allPowers: Power[] = [];

  constructor(private fb: FormBuilder, private powerService: PowerService) {
    this.createForm();
    this.logNameChange();
  }

  ngOnInit() {
    this.powerService.getAll().subscribe(powers => this.allPowers = powers);
  }

  createForm() {
    this.heroForm = this.fb.group({
      name: ['', Validators.required],
      powers: this.fb.array([]),
    });
  }

  ngOnChanges() {
    this.rebuildForm();
  }

  rebuildForm() {
    this.heroForm.reset({
      name: this.hero.name
    });
    this.setPowersControl(this.hero.powers);
  }

  setPowersControl(powers: Power[]) {
    const powersFGs = powers.map(pow => this.fb.group(pow));
    const powersFormArray = this.fb.array(powersFGs);
    this.heroForm.setControl('powers', powersFormArray);
  }


  get powers(): FormArray {
    const pows = this.heroForm.get('powers') as FormArray;
    return pows;
  }


  addPowerChoice() {
    this.powers.push(this.fb.control(new Power()));
    // this.powers.push(this.fb.group(new Power(), Validators.required));
  }

  onSubmit() {
    this.hero = this.prepareSaveHero();
    console.log('SAVING HERO', this.hero);
    // this.heroService.updateHero(this.hero).subscribe(/* error handling */);
    this.rebuildForm();
  }

  prepareSaveHero(): Hero {
    const formModel = this.heroForm.value;

    // deep copy of form model lairs
    const powersDeepCopy: Power[] = formModel.powers.map(
      (pow: Power) => Object.assign({}, pow)
    );

    // return new `Hero` object containing a combination of original hero value(s)
    // and deep copies of changed form model values
    const saveHero: Hero = {
      id: this.hero.id,
      name: formModel.name as string,
      // addresses: formModel.secretLairs // <-- bad!
      powers: powersDeepCopy
    };
    return saveHero;
  }

  revert() { this.rebuildForm(); }

  logNameChange() {
    const nameControl = this.heroForm.get('name');
    nameControl.valueChanges.forEach(
      (value: string) => this.nameChangeLog.push(value)
    );
  }

}

This is my hero-form.html

<form [formGroup]="heroForm" (ngSubmit)="onSubmit()">
    <div style="margin-bottom: 1em">
        <button type="submit" [disabled]="heroForm.pristine" class="btn btn-success">Save
        </button> &nbsp;
        <button type="button" (click)="revert()" [disabled]="heroForm.pristine" class="btn btn-danger">Revert</button>
    </div>

    <!-- Hero Detail Controls -->
    <div class="form-group">
        <label class="center-block">Name:
            <input class="form-control" formControlName="name">
        </label>
    </div>

    <div formArrayName="powers" class="well well-lg">
        <div *ngFor="let pow of powers.controls; let i=index" [formControlName]="i">
            <!-- The repeated power template -->
            <h4>Potere #{{i + 1}}</h4>
            <div style="margin-left: 1em;">

                <div class="form-group">
                    <label class="center-block">Power:
                        <select class="form-control">
                            <option *ngFor="let pow of allPowers" [value]="pow">{{pow.name}}</option>
                        </select>
                    </label>
                </div>

            </div>
            <br>
            <!-- End of the repeated address template -->
        </div>
        <button (click)="addPowerChoice()" type="button">Add a Power</button>
    </div>

</form>

<p>heroForm value: {{ heroForm.value | json}}</p>

<h4>Name change log</h4>
<div *ngFor="let name of nameChangeLog">{{name}}</div>

This is power-service that is only returning stubbed data

@Injectable({
  providedIn: 'root'
})
export class PowerService {

  constructor() {
  }

  getAll(): Observable<Power[]> {
    return of(this.getProds());
  }

  getProds(): Power[] {
    const powers = [];
    for (let i = 0; i < 10; i++) {
      const pow = new Power();
      pow.id = i+'';
      pow.name = 'Power ' + i;
      powers.push(pow);
    }
    return powers;
  }
}

And these are my data models

export class Hero {
  id: number;
  name: string;
  powers: Power[];
}

export class Power {
  id: string = '';
  name: string = '';
}

Here I have make a stackblitz example but it's not working

2
  • Have you tried moving the formControlName from the div onto the select <select class="form-control" [formControlName]="i"> ? Commented Jul 19, 2018 at 14:49
  • that was one problem, thx, but still not working Commented Jul 19, 2018 at 14:51

1 Answer 1

3

I've solved

I have moved formControlName from div onto select as suggested by Lucas Klaassen, and changed [value] to [ngValue] onto option

<form [formGroup]="heroForm" (ngSubmit)="onSubmit()">
  <div style="margin-bottom: 1em">
    <button type="submit"
            [disabled]="heroForm.pristine" class="btn btn-success">Save
    </button> &nbsp;
    <button type="button" (click)="revert()"
            [disabled]="heroForm.pristine" class="btn btn-danger">Revert
    </button>
  </div>

  <!-- Hero Detail Controls -->
  <div class="form-group">
    <label class="center-block">Name:
      <input class="form-control" formControlName="name">
    </label>
  </div>

  <div formArrayName="powers" class="well well-lg">
    <div *ngFor="let pow of powers.controls; let i=index">
      <!-- The repeated power template -->
      <h4>Potere #{{i + 1}}</h4>
      <div style="margin-left: 1em;">

        <div class="form-group">
          <label class="center-block">Power:
            <select class="form-control" [formControlName]="i">
              <option *ngFor="let pow of allPowers" [ngValue]="pow">{{pow.name}}</option>
            </select>
          </label>
        </div>

      </div>
      <br>
      <!-- End of the repeated address template -->
    </div>
    <button (click)="addPowerChoice()" type="button">Add a Power</button>
  </div>

</form>

<p>heroForm value: {{ heroForm.value | json}}</p>

<h4>Name change log</h4>
<div *ngFor="let name of nameChangeLog">{{name}}</div>

Then I have changed onSubmit() adding a Hero's constructor call as follow

onSubmit() {
    this.hero = this.prepareSaveHero();
    console.log('SAVING HERO', this.hero);
    // this.heroService.updateHero(this.hero).subscribe(/* error handling */);
    this.hero = new Hero();
    this.rebuildForm();
}

Then I have added a custom constructor to Hero class

export class Hero {
  id: number;
  name: string;
  powers: Power[];

  constructor() {
    this.id = 0;
    this.name = '';
    this.powers = [];
  }
}

Now it's working and correctly rebuilding form after submit

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.