1

In component ts executeCommand method I am making a clone of the already exsisting object like this

let newCommandArray = new Object();
      newCommandArray = this.commandArray[commandId];

After that I'm looping over the newCommandArray and doing some manipulations for the data. When i manipulate the data on the cloned object which is the newCommandArray also the original objects data which is the this.commandArray[commandId] changes which makes template unable to render the view. in item.ParamProps.options and gives me the error :

Error trying to diff '"[{\"name\":\"option1\",\"value\":\"option1\"},{\"name\":\"option2\",\"value\":\"option2\"}]"'. Only arrays and iterables are allowed in html line 51 which is the <md2-option *ngFor="let option of item.ParamProps.options">

A help to overcome this issue will be appreciated.

HTML template:

 <div *ngSwitchCase="'select'">
                  <div class="form-group" *ngIf="item.ParamProps.visible">
                    <label>{{item.ParamName}}</label><br>
                    <div class="wrapper">
                      <md2-select [(ngModel)]="item.ParamValue"
                                  [name]="item.ParamID">
                        <md2-option *ngFor="let option of item.ParamProps.options"
                                    [value]="option.value">{{option.name}}
                        </md2-option>
                      </md2-select>
                      <i class="bar"></i>
                    </div>
                  </div>
                </div>

Component TS :

export class DynamicCommandComponent implements OnInit {
  public commands: ICommandList;
  public message: string;
  public commandArray: any;
  public commandHistoryList: any;
  public filterTerm: string;
  private itemId: any;
  @ViewChild('commandHistoryModal') commandHistoryModal: any;

  constructor(private commandService: DynamicCommandService, private globalDataService: GlobalDataService) {
    this.commands = null;
    this.commandArray = {};
    this.commandHistoryList = {};
    this.filterTerm = '';
  }

  public ngOnInit() {
    this.itemId = Number(this.globalDataService.getAgentID());
    this.commandService.getCommandsSet(this.itemId).subscribe((res: ICommandList) => {
      this.commands = res;
      this.storeCommands(res);
      this.loadAllCommandStatus(this.itemId);

    });
  }


  public executeCommand(commandId: number) {
    this.commandService.getUserFeatures().subscribe((res: any) => {
      this.commandArray[commandId].userID = res.userId;
      let itemIdArray = new Array<number>();
      itemIdArray.push(this.itemId);
      this.commandArray[commandId].ItemIDs = itemIdArray;
      this.commandArray[commandId].name = UUID.UUID();
      let newCommandArray = new Object();
      newCommandArray = this.commandArray[commandId];
      newCommandArray.CommandParamList[0].ParamProps.options = JSON.stringify(newCommandArray.CommandParamList[0].ParamProps.options);
      newCommandArray.CommandParamList.forEach(element => {
        element.ParamProps.options = JSON.stringify(element.ParamProps.options);
      });
      console.log(newCommandArray); // Output => [{\"name\":\"option1\",\"value\":\"option1\"},{\"name\":\"option2\",\"value\":\"option2\"}]"

      console.log(this.commandArray[commandId]); // Output => "[{\"name\":\"option1\",\"value\":\"option1\"},{\"name\":\"option2\",\"value\":\"option2\"}]"

      this.commandService.executeCommand(newCommandArray).subscribe();
    });
  }



}
Selaka Nanayakkara
  • 3,296
  • 1
  • 22
  • 42
  • 1
    JSON.stringify , as you can see in ts return string not Object like you need, to clone a object w/o functions(or Map,Set,etc...) use -> clonedObject = JSON.parse(JSON.stringify(originalObject)) – Vash72 Jul 04 '19 at 20:26

1 Answers1

2

Your code has a few issues.

First of all, in these lines:

let newCommandArray = new Object();
newCommandArray = this.commandArray[commandId];

You are not actually cloning the object. First newCommandArray is set to an empty object {} but then you go ahead and say 'actually, forget that. NewCommandArray will point to this.commandArray[commandId]. That's why changing one changes the other – both variable names point to the same object.

If you want to actually clone the object, there are tons of ways, each with advantages and disadvantages, depending on the complexity of the object you want to clone. Two ways you can do it are:

const newCommandArray = { ...this.commandArray[commandId] };

or

const newCommandArray = JSON.parse(JSON.stringify(this.commandArray[commandID]));

By the way I'm using const, because once you've assigned that object, you don't want to reassign the variable to another object. You can happily add or change its properties without causing any errors regarding the use of const.

Then after that, there are two places where you stringify things for no reason

newCommandArray.CommandParamList[0].ParamProps.options = JSON.stringify(newCommandArray.CommandParamList[0].ParamProps.options);
...
newCommandArray.CommandParamList.forEach(element => {
  element.ParamProps.options = JSON.stringify(element.ParamProps.options);
});

Why are you stringifying these options? In any case, that's why you're getting the other issue, where ngFor gets confused. It wants to loop through an array, which it would have got if you hadn't converted it into a string!

If you want to read more about cloning objects, look here:

What is the most efficient way to deep clone an object in JavaScript?

Michael Beeson
  • 2,840
  • 2
  • 17
  • 25
  • The stringification in the both places is due to backend issue.. But if i do the cloning properly it should fix the issue right? – Selaka Nanayakkara Jul 05 '19 at 04:47
  • 1
    Afraid not – when in your template you have `let option of item.ParamProps.options` it fails because you convert ParamProps.options to a string, and the ngFor wants an array. For your backend purposes, it would be ideal to stringify the options on the backend, not on the client. If you have to stringify it on the client, do it in a new variable (ìtem.ParamProps.optionsString` for example) and tell your backend to use that variable instead, but leave your options as an array. – Michael Beeson Jul 05 '19 at 10:52
  • Hi Michael once i did the cloning correctly it worked fine.. Thanks for the help and marked as the correct answer. Thanks a lot for awesome explanation – Selaka Nanayakkara Jul 08 '19 at 07:36
  • 1
    Ah yeah, of course, I'm dumb. By cloning the object beforehand, you're not stringifying the array used in the template after all, so the cloning does indeed fix the issue. I'm happy it worked out @SelakaN – Michael Beeson Jul 08 '19 at 17:30