Nicole Naumann
Nicole Naumann

Reputation: 1108

Angular Reactive Form: How to listen to changes in FormControls of a FormArray

I'm using Angular v11.2.3 and vTypeScript 4.1.5. Using reactive form controls for my projects.

I have an object "Artwork", its property imageUrls is an array of strings as its imageUrls. The JSON representation looks like this:

{
  "title": "Mona Lisa",
  "description": "Portrait painting with a mysterious smile",
  ...
  "imageUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg/1280px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg"
  ],
  ...
}

On the "Edit" view of this artwork, I want to render the images beside the form. And I want to the image section to automatically update whenever an URL of the image is edited. How to achieve this "automatical update of images" without submitting the whole form?

My current template looks like this:

<div fxFlex fxLayout fxLayoutAlign="start start">
  <!-- image container -->
  <div fxFlex="40" fxLayout="column" fxLayoutGap="10px">
    <div *ngIf="artworkForm.get('imageUrls')">
      <ng-container *ngFor="let imageUrl of getImageUrlCtrls(); index as i">
      <img [src]="imageUrl.value" alt="Picture {{i + 1}} of {{artworkForm.get('title')?.value}}"
           class="detailImage">
      </ng-container>
    </div>
  </div>

  <!-- form container -->
  <div fxFlex="60" fxLayout="column" fxLayoutAlign="center center">

    <!-- ~~~~~~~~~~~~~~~~~ form of the details ~~~~~~~~~~~~~~~~~~ -->
    <div class="detailForm">
      <form fxFlex fxLayout="column" fxLayoutAlign="center start"
        [formGroup]="artworkForm" (ngSubmit)="onSubmit()">
        ....
        <div class="row">
          <p>Image URLs:</p>
          <!-- without the type='button', the form will automatically submit when this button is clicked! -->
          <button type="button" *ngIf="editable()" mat-icon-button (click)="onAddImageUrl()">
            <mat-icon color="primary">add</mat-icon>
          </button>
        </div>

        <ng-container *ngIf="artworkForm.get('imageUrls')">
          <div class="row" fxLayoutAlign="flex-end"
               *ngFor="let imageUrl of getImageUrlCtrls(); index as i">
          <mat-form-field>
            <mat-label>image {{i + 1}} URL</mat-label>
            <input matInput [value]="imageUrl.value">
          </mat-form-field>

          <button type="button" *ngIf="editable()"
                  mat-icon-button
                  (click)="onDeleteImageUrl(i)">
            <mat-icon color="warn">delete_outline</mat-icon>
          </button>
          </div>
        </ng-container>
       ....
      </form>
    </div>
  </div>

My component looks like this:

export class ArtworkDetailComponent implements OnInit {
  
  artworkForm!: FormGroup;
  
  artwork: {[index: string]: any} = {} as Artwork;

  ....

  ngOnInit(): void {
    if (this.artworkId) {
      this.artwork = this.artworkService.getArtworkById(this.artworkId);
    }

    this.initForm();
  }

  private initForm(): void {
    ....
    const ffImageUrls = new FormArray([]);

    this.artwork.imageUrls.forEach((imageUrl: string, index: number) => {
          ffImageUrls.push(new FormControl({value: imageUrl, disabled: this.readOnly()}));
        });
    ....

    this.artworkForm = new FormGroup({
      ....
      imageUrls: ffImageUrls,
      ....
    });
  }
  
  getImageUrlCtrls(): FormControl[] {
    return (this.artworkForm.get('imageUrls') as FormArray).controls as FormControl[];
  }
}

At the moment, when I change the url in the form, the image div on the same page does not get updated. Any help will be highly appreciated!

Upvotes: 2

Views: 1137

Answers (1)

Nicole Naumann
Nicole Naumann

Reputation: 1108

I read Netanel Basal's blog Angular Reactive Forms: The Ultimate Guide to FormArray and solved my problem.

My mistake was in the html template, in this line:

<input matInput [value]="imageUrl.value">

When I changed it to:

<input matInput [formContrl]="imageUrl">

The images rendered immediately anew when I changed the URLs in the form. Yay!!!

To make things clearer, I changed the variable name imageUrl to urlCtrl.

Netanel's code will not work in the latest Angular and latest TypeScript version. Compiler will report error due to lack of type casting in the html template. So this method in the component TS code is crucial:

getImageUrlCtrls(): FormControl[] {
    return (this.artworkForm.get('imageUrls') as FormArray).controls as FormControl[];
  }

Upvotes: 1

Related Questions