3

I'm trying to render a list of dynamic social links in my Angular app. The data comes from the database like this:

// User model
{
    ...
    socialLinks: [
       { name: 'Site1', url: 'https://site1.com/user' },
       { name: 'Site2', url: 'https://site2.com/user' },
       ...
    }
    ...
}

This is how the form group is created in the component:

public async ngOnInit(): Promise<any> {
    const userResult = await this.userService.getLoggedInUser();

    if (userResult) {
        this.userSettingsForm = this.fb.group({
            displayName: [this.user.displayName, Validators.required],
            bio: [this.user.bio, Validators.maxLength(256)],
            location: [this.user.location],
            socialLinks: this.fb.group(this.user.socialLinks)
        });
    }
}

And the template:

<form [formGroup]="userSettingsForm" (ngSubmit)="submitForm()">
    ...
    <div *ngFor="let socialLink of user.socialLinks; let index = index" formArrayName="socialLinks">
        <div class="position-relative">
            <img [src]='"/assets/icons/" + user.socialLinks[index].name + ".svg"' 
                [alt]='user.socialLinks[index].name + " page"'>
            <input class="form-control" [formControlName]='index'
                [value]='user.socialLinks[index].url'>
        </div>
    </div>
</form>

The fields are populated with the url as they should, but I'd like the form value to reflect the same structure as the data coming from the database. Currently, if I edit any of the fields, the value of the input goes from the original object to just a string. I tried subscribing to the socialLinks controls for changes and remapping the values, but that seemed redundant.

Stackblitz

Thanks in advance!

Olsi
  • 43
  • 4

1 Answers1

1

Such as socialLinks is an array - you would need to reflect it with FormArray class and items of the array would be FormGroup instances. We could achieve it with an existing form builder, simply mapping over links:

socialLinks: this.fb.array(this.user.socialLinks.map((l) => this.fb.group(l)))

after this step, we would have form array with for groups which represents structure of socialLink, so that is why we also need changes in html to cover it(from stackblitz):

<div class="position-relative" [formGroupName]='index'>
    ...
    <input class="form-control" formControlName='url'>
</div>

now each [formGroupName] would be and an index and formControlName would represent the field we want to work with

Hope this helps :)

Also, there are some handy explanations here:

https://alligator.io/angular/reactive-forms-formarray-dynamic-fields/

When to use FormGroup vs. FormArray?

P.S. consider using socialLink inside *ngFor such as it is the direct reference to user.socialLinks[index]

andriishupta
  • 497
  • 4
  • 8
  • Perfect! Yes I tried a few variations but it looks like I was missing the `fb.group()` inside the form array. Sometimes Reactive Forms can be as annoying as they are useful :) Thank you! – Olsi Jan 20 '20 at 23:26
  • Happy to help :) – andriishupta Jan 21 '20 at 09:30