Search code examples
javascriptangularjsangularjs-scopeangular-ui-bootstrap

Angular write into nested scope properties


I have multiple "input field and a button" couples in my app. The button opens up a dialog to write something into the text field.

[ input ] [ button ] ---> [ dialog ( ok ) ( cancel ) ]

I've used Bootstrap.UI.Modal ( https://angular-ui.github.io/bootstrap/#/modal ) so I have a promise to deal with it:

//html
<input ng-model="foo"/>
<button ng-click="dialog('foo')"> Open </button>

//controller
modalInstance.result.then(
    function ( selectedItem ) {
        $scope[ arg ] = selectedItem;
    }, 
    ...
);

Everything works okay. ( Demo ) The problem comes when I have to access nested properties of my scope objects:

...
<input ng-model="foo"/>
<button ng-click="dialog('foo')"> Open </button>
...
<li ng-repeat="thing in model.nested.properties.of.unknown.level">
    ...
    <input ng-model="thing.foo"/>
    <button ng-click="dialog( '???' )"> Open </button>
    ...

What I want to know is: what is the best approach to achieve that?

Until now I tried:

  • passing the scope variable into the returning callback, but it got only the value, not the reference; so the field would not be updated.

     resolve: {
         field: function() {
           return $scope[ field ];
         }
     }
    
  • passing an array of strings to recreate the scope hierarchy

     dialog( ["a","b","c"] ) --> $scope[ "a" ][ "b" ][ "c" ] = output.value;
    
  • preparing an object of callbacks like

     object = { 
       "one": function(){ $scope.a.b.c = ... }, 
       "two": function(){ $scope.d.e.f = ... }, 
       ... 
       // but this requires that I know in advance 
       // how many level I will nest into the $scope
     }
    
  • using the id of the input field, so you can write directly into the DOM (but as far as I know this is not a good approach in angularjs)

     $("#input-abc...").val( ... )
    
  • using eval (uungh...)

I think that the first solution would be the best one, but how can I pass the reference of the nested scope element to my promise callback? There are some best practices to achieve this? Any suggestion?


Solution

  • To treat nested structures, you could simple pass your container alongside field name:

    resolve: {
        container: function () { 
          return thing; // thing would come from edit() parameter
        },
        field: function () {
          return fieldName;
        }
      }
    

    And then access your data from container instead of scope. This would be the poor-man 2-way binding.

    --

    Another approach, taking where you left at your directive, would be as follows:

    step 1) as you're using a ngModel, add it to directive scope to get 2-way binding:

    scope: {
      ngModel: '='
    },
    

    step 2) add attr parameter to link

    link: function (scope, element, attr)
    

    step 3) make field resolve to data passed in view

    resolve: {
      field: function () {
        return attr.external;
      }
    

    step 4) assign new data to ngModel once modal is completed

    scope.ngModel = output.selection;
    

    step 5) change your view like this:

    <input type='text' ng-model='thing.value' external="{{thing.label}}"/> Value: {{thing.value}}
    

    Fiddle: http://plnkr.co/edit/hj6gOITk0rkHwPqSN9Tf?p=preview