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>
5
  • see where you are saying [(ngModel)]="Colors[i]" ? That is silly, just say [(ngModel)]="color".... you already have access to Colors[i], it is color...... now could you show you .ts file? Commented May 9, 2019 at 13:05
  • [(ngModel)]="color" => this is the first case I tried (first code segment) and that was not more successful Commented May 9, 2019 at 13:06
  • for the code: the only relevant thing in the code is the declaration of the Colors array: Colors: string[] = [] Commented May 9, 2019 at 13:09
  • is there any error with the browser console ? Commented May 9, 2019 at 13:19
  • nope, no error at all Commented May 9, 2019 at 13:22

4 Answers 4

8

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

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

4 Comments

A Perfect Solution: clear, correct and insightful! Thanks very much, Jonas!
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)
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. stackoverflow.com/questions/46991497/…
Here is a good discussion on the topic as well: 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.
2

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

Comments

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.

3 Comments

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!
this code is wrong: e => { formControls[e] you cant use an object as array index
@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.
0

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

2 Comments

sorry, that is just the above code (I tried to remove as much irrelevant code as possible). thank for the feedback though!
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 <mat-form-field>

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.