1

Could somebody help me with the following code and give me a reason why it does not work. I am creating a series of inputs from a string array and I want to bind each input value to its corresponding slot in the string array. Seems to be pretty standard, but I do not seem to grasp the issue.

I have tried the following two cases, but the Colors array (=string[]) remains empty!

<tr *ngFor="let color of Colors; let i = index;">
 <td>
  <mat-form-field>
      <input required matInput placeholder="Color ({{ i + 1}})"   [name]="'color_' + i" [(ngModel)]="color">
  </mat-form-field>
</td>
</tr>

<tr *ngFor="let color of Colors; let i = index;">
 <td>
  <mat-form-field>
   <input required matInput placeholder="Color ({{ i + 1}})"  [name]="'color_' + i" [(ngModel)]="Colors[i]">
  </mat-form-field>
</td>
</tr>
MuseOfMusic
  • 15
  • 1
  • 1
  • 5

4 Answers4

7

Strings are immutable in JavaScript, meaning we cant bind ngModel to them. You could quite easily convert your array into an array on objects with the key color and the value of your strings. This would fix your binding issue. Here is some code. I also hacked together a stackbitz to show you.

However, I would recommend Joey gough's answer though. That code feels more correct and "the angular way" of solving this. Good luck!

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  Colors = [{color: 'stringColor1'}, {color: 'stringColor2'}]
}

<tr *ngFor="let item of Colors; let i = index;">
  {{i}}
 <td>
  <input required placeholder="Color ({{ i + 1}})" [name]="'color_' + i" [(ngModel)]="item.color">
</td>
</tr>

{{Colors | json}}

See: https://stackblitz.com/edit/angular-sj623x

Jonas Johansson
  • 417
  • 3
  • 8
  • 1
    A Perfect Solution: clear, correct and insightful! Thanks very much, Jonas! – MuseOfMusic May 10 '19 at 08:50
  • Hey Jonas, I was too fast: you say 'Strings are immutable in JavaScript, meaning we cant bind ngModel to them.' But why can I then ngmodel-bind to a string as in [(ngModel)]="companyname" ? (with companyname: string) – MuseOfMusic May 15 '19 at 14:00
  • Because you are binding it to the variable (which is a property of the class). Which basically is a reference/pointer in a way. Perhaps it was a bit simplified to say strings were the issue here, sorry. "ngFor by default uses object identity to compare values, this breaks when primitive values (number, string, boolean) are used, because they change identity when modified)." - this is a higher level description of the same issue basically. https://stackoverflow.com/questions/46991497/how-properly-bind-an-array-with-ngmodel-in-angular-4 – Jonas Johansson May 17 '19 at 07:10
  • Here is a good discussion on the topic as well: https://groups.google.com/forum/#!topic/angular/QgcRBpjiHAQ They ofcourse also conclude that " String objects are immutable, one cannot "edit" the value of a String, you can only replace one with another. " and this causes problems because we can not keep a bind to a value that gets deleted and then a new one is created. – Jonas Johansson May 17 '19 at 07:51
1

Afaik ngModel requires the variable to be a property of the class.

You should try using reactive forms

@Input() colors: string[];
public formGroup: FormGroup;
constructor(private formBuilder: FormBuilder) {
}

ngOnInit() {
  const formControls = {};
  this.colors.forEach(e => {
    formControls[e]: new FormControl(e);
  }
  this.formGroup = this.formBuilder.group(formControls);
}

Then in your html something like this}

<tr *ngFor="let color of Colors; let i = index;" [formGroup]="formGroup">
 <td>
  <mat-form-field>
      <input required matInput placeholder="Color ({{ i + 1}})"   [name]="'color_' + i" [formControlName]="color">
  </mat-form-field>
</td>
</tr>

I wrote that on the fly so don't know if it works. But with some tweaking it should.

Joey Gough
  • 2,753
  • 2
  • 21
  • 42
  • Thanks for this! I know there are other ways to solve this. But this seems to be inline with the template approach and it is very nice and clean. So, that is my reason for asking. Thanks for sharing your code though! – MuseOfMusic May 09 '19 at 13:25
  • this code is wrong: `e => { formControls[e]` you cant use an object as array index – JBoy Jan 26 '22 at 09:43
  • @JBoy, `colors` is a string array. so `e` is a string. `formControls` is an object. So, `formControls[e]` is using a string a key in an object. – Joey Gough Jan 27 '22 at 10:22
1

It's possible using [(ngModel)] or ReactiveForms.

The problem if you use [(ngModel)] is that you can not iterate over the own array.

//***WRONG**, you change "Colors" in input and is iterating over Colors
//you see that your form is "unestable"
<div *ngFor="let color of Colors; let i = index;">
   <input required matInput placeholder="Color ({{ i + 1}})"  [name]="'color_' + i" [(ngModel)]="Colors[i]">
</div>

but you can use

<tr *ngFor="let color of ' '.repeat(Colors.length).split(''); let i = index;">
 <td>
  <mat-form-field>
   <input required matInput placeholder="Color ({{ i + 1}})"  
               [name]="'color_' + i" [(ngModel)]="Colors[i]">
  </mat-form-field>
</td>
</tr>
<hr/>
{{Colors|json}}

Yes is a work-around: not iterate over Colors, just over an array create on fly using String.repeat and split

' '.repeat(Colors.length).split('') //a string of same length that Colors.length

Using ReactiveForm, it's better use a FormArray

<div *ngIf="formArray" [formGroup]="formArray">
<tr *ngFor="let control of formArray.controls;let i=index">
 <td>
  <mat-form-field>
   <input matInput placeholder="Color ({{ i + 1}})"  [name]="'color_' + i" [formControl]="control">
  </mat-form-field>
</td>
</tr>
</div>
<hr/>
{{formArray?.value|json}}

You can see the stackblitz

Eliseo
  • 50,109
  • 4
  • 29
  • 67
0

It seems that you forgot to close the "tr" tag.

  • sorry, that is just the above code (I tried to remove as much irrelevant code as possible). thank for the feedback though! – MuseOfMusic May 09 '19 at 13:10
  • I think you should try to use a "p" tag instead of input and see if it shows the color data or not. I think your problem might be for using – Mohammadreza Imani May 09 '19 at 16:05