Motocarota Motocarota - 3 months ago 13
AngularJS Question

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?

Answer

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