javascriptangularjsjsonangularjs-ng-repeatangularjs-ng-switch

Howto use ng-switch inside ng-repeat for editing JSON API data


I know this is a recurring question but unfortunately I couldn't find a proper answer to my case.

Basically I'm getting data from an JSON API endpoint which gets displayed in a table using ng-repeat. I now want to ng-switch the view to input fields for amending the data (and sending it later back to the server).

Atm, my solutions depends on having a property in the data which I don't really like. I'm sure there's a smarter way than injecting this property after having retrieved the data - any suggestions?

HTML:

<tbody>
  <tr ng-repeat="item in data" ng-switch on="item.edit" >
    <td ng-switch-default ng-bind="item.color"></td>
    <td ng-switch-when='true'>
      <input type="text" ng-model="item.color" />
    </td>
    <td ng-switch-default><button ng-click="switch(item)">edit</button></td>
    <td ng-switch-when='true'><button ng-click="send(item)">send</button></td>
  </tr>
</tbody>

JS:

var app = angular.module('myApp', []);

app.controller('MyCtrl', function($scope) {

  $scope.switch = function (item) {
    if (item.edit) {
      item.edit = false;
    } else {
      item.edit = true;
    }
  };

  $scope.send = function (item) {
    if (item.edit) {
      // data is sent...
      item.edit = false;
    } else {
      item.edit = true;
    }
  };

  $scope.data = [
      {color: 'blue', edit: false},
      {color: 'green', edit: false},
      {color: 'orange', edit: false}];
});

thanks in advance!

here's a plunker: http://plnkr.co/edit/h8ar4S43JUvjHurzLgT0?p=preview


Solution

  • If you do not want to put your flags on your data objects than you will need to use a separate object to store them. With WeakMaps you can easily associate the data object, or the element itself, with a flags object. If you are targeting older browsers you will need to find a similar way to associate the data object / or element to the flags object

    JS

    let map = new WeakMap();
    $scope.editing = function(item){
        return map.get(item).edit;
    }
    $scope.switch = function (item) {
        let flags = map.get(item);
        if (flags.edit) {
            flags.edit = false;
        } else {
            flags.edit = true;
        }
    };
    //Note you could combine switch and send into a single toggle function
    $scope.send = function (item) {
        let flags = map.get(item);
        if (flags.edit) {
            flags.edit = false;
        } else {
            flags.edit = true;
        }
    };
    $scope.data = [
      {color: 'blue'},
      {color: 'green'},
      {color: 'orange'}
    ];
    //Create an empty flags object for each data item
    for(let item of $scope.data){
       map.set(item,{});
    }
    

    HTML

    <tr ng-repeat="item in data" ng-switch on="editing(item)" >
        <td ng-switch-default ng-bind="item.color"></td>
        <td ng-switch-when='true'>
            <input type="text" ng-model="item.color" />
        </td>
        <td ng-switch-default><button ng-click="switch(item)">edit</button></td>
        <td ng-switch-when='true'><button ng-click="send(item)">send</button></td>
    </tr>
    

    Demo

    // Code goes here
    var app = angular.module('myApp', []);
    
    app.controller('MyCtrl', function($scope) {
      var map = new WeakMap();
      
      //Using fat arrow less code to write
      $scope.editing = item=>map.get(item).edit;
      
      
      //Since "switch" and "send" had similar 
      //toggling code just combined them
      //Also no need to use if statement, just use the NOT operator
      //to toggle the edit flag
      $scope.toggle = item=>{
        let flags = map.get(item);
        flags.edit = !flags.edit;
      };
    
      $scope.switch = item=>{
        $scope.toggle(item);
        //Do some switching? 
        //if not doing anything else just 
        //call toggle in the ng-click
      };
      $scope.send = item=>{
        $scope.toggle(item);
        //Do some sending
      };
    
      $scope.data = [
          {color: 'blue'},
          {color: 'green'},
          {color: 'orange'}];
          
      for(let item of $scope.data){
        map.set(item,{});
      }
    });
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
    <div ng-app="myApp" ng-controller="MyCtrl">
      <table>
        <thead>
          <tr>
            <th width="180">Column</th>
            <th>Edit</th>
          </tr>
        </thead>
        <tbody>
          <tr ng-repeat="item in data" ng-switch on="editing(item)" >
            <td ng-switch-default ng-bind="item.color"></td>
            <td ng-switch-when='true'>
              <input type="text" ng-model="item.color" />
            </td>
            <td ng-switch-default><button ng-click="switch(item)">edit</button></td>
            <td ng-switch-when='true'><button ng-click="send(item)">send</button></td>
          </tr>
        </tbody>
      </table><br>
      "$scope.data" should never change after hitting edit/send since the flag is no longer on the data item object:
      <code><pre>{{data}}</pre></code>
    </div>