Anton Telesh
Anton Telesh

Reputation: 3842

Is there a way in angularjs to dynamically set ngModel?

I have a component (a directive with isolate scope) that maps a list of objects to a list of inputs that allow to modify one property of that objects.

But I want to make that component universal. So it should accept path to deeply nested property of each object in a list that should be bound to input.

For example we have a list of people, each of those have name spoken in different languages:

var people = [
  {
    age: 31,
    multilang_attributes: {
      en: {name: 'John'},
      ru: {name: 'Иван'}
    }
  },
  {
    age: 29,
    multilang_attributes: {
      en: {name: 'Peter'},
      ru: {name: 'Пётр'}
    }
  },
];

I want to apply my universal component to that list of people like the following:

<input-list
  items="people",
  item-model="multilang_attributes[locale].name"
></input-list>

I tried to create scope property with & that would allow me to execute expression in parent scope and then pass that expression to ngModel, but that does not work. You can look at this attempt in plunkr.

How that task can be approached in angular?

Upvotes: 3

Views: 274

Answers (1)

Yoshi
Yoshi

Reputation: 54649

One option would be to $parse the accessor-string and use a getter/setter for the model. For this:


  1. change the directive-html to:

    item-accessor="'multilang_attributes.' + app.locale + '.name'"

    This will make something like multilang_attributes.en.name available.


  1. change the directive-code to:

    app.directive('inputList', function () {
      return {
        restrict: 'E',
        scope: {
          items: '=',
          itemAccessor: '='
        },
        template: '<ul><li ng-repeat="item in items"><input ng-model="getModel(item)" ng-model-options="{ getterSetter: true }" /></li></ul>',
    
        controller: function ($scope, $parse) {
          $scope.getModel = function(item) {
            return function (newValue) {
              var getter = $parse($scope.itemAccessor);
    
              if (arguments.length > 0) {
                getter.assign(item, newValue);
              }
    
              return getter(item);
            };
          };
        }
      };
    });
    

External demo: http://plnkr.co/edit/VzFrBxNcsA5BarIVr6oG?p=preview

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

app.controller('AppCtrl', function AppController() {
    
  this.locales = ['en', 'fr']
  this.locale = 'en';
  
  this.people = [
    {
      age: 31,
      multilang_attributes: {
        en: {name: 'John (en)'},
        fr: {name: 'John (fr)'}
      }
    },
    {
      age: 29,
      multilang_attributes: {
        en: {name: 'Fred (en)'},
        fr: {name: 'Fred (fr)'}
      }
    },
  ];
  
});
  
app.directive('inputList', function () {
  return {
    restrict: 'E',
    scope: {
      items: '=',
      itemAccessor: '='
    },
    template: '<ul><li ng-repeat="item in items"><input ng-model="getModel(item)" ng-model-options="{ getterSetter: true }" /></li></ul>',
    
    controller: function ($scope, $parse) {
      
      $scope.getModel = function(item) {
        return function (newValue) {
          var getter = $parse($scope.itemAccessor);
          
          if (arguments.length > 0) {
            getter.assign(item, newValue);
          }
          
          return getter(item);
        };
      };
    }
  };
});
<!DOCTYPE html>
<html>
  <head>
    <script data-require="[email protected]" data-semver="1.4.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" integrity="sha512-dTfge/zgoMYpP7QbHy4gWMEGsbsdZeCXz7irItjcC3sPUFtf0kuFbDz/ixG7ArTxmDjLXDmezHubeNikyKGVyQ==" crossorigin="anonymous">
  </head>

  <body ng-app="TestApp" ng-controller="AppCtrl as app">
    <div class="container">
      <select ng-model="app.locale" ng-options="v as v for v in app.locales"></select>
      <hr>
      
      <input-list
        items="app.people"
        item-accessor="'multilang_attributes.' + app.locale + '.name'"
      ></input-list>
      
      <hr>
      <pre>{{ app.people|json }}</pre>
    </div>
  </body>
</html>


note that you should propably use proper injection-syntax and controllerAs/bindToController.

Upvotes: 2

Related Questions