3

I am learning AngularJS. I try to create a reusable component called .

Unfortunately I cannot prefill the fields inside element with the data obtained from JSON. I looked around SO and the web but could not solve it. Could you please let me know what am I doing wrong?

I have two controllers. One gets a list of all countries:

app.controller('MainController', ['$scope', 'Countries',
                                  function ($scope, Countries) {
  $scope.countries = Countries.query();
}]);

The other gathers a specific address:

app.controller('AddressesController', ['$scope', '$routeParams', 'Address',
  function($scope, $routeParams, Address) {

    if ($routeParams.addressId) {
      $scope.senderAddress = Address.get({addressId: $routeParams.addressId});
    } else {
      $scope.senderAddress = {"id":null, "country":null, "city":null, "street":null};
    }

    $scope.adData = {"id": 1, "country": "Poland", "city": "Warsaw", "street": "Nullowska 15"};
  }]);

The services are defined as follows, they seem to work correctly and provide correct JSONs:

myServices.factory('Countries', ['$resource',
                                function($resource) {
                                  return $resource('data/countries.json', {}, {
                                    query: {method:'GET'}
                                  })
                                }]);
myServices.factory('Address', ['$resource',
                               function($resource) {
                                 return $resource('data/:addressId.json', {}, {
                                   query: {method:'GET', params:{addressId:'addressId'}}
                                 })
                               }])

I have routing set so that it directs to AddressesController:

app.config(function ($routeProvider) {
  $routeProvider
  .when('/address', {
    templateUrl: 'partials/addresses.html',
    controller: 'AddressesController'
  })
  .when('/address/:addressId', {
    templateUrl: 'partials/addresses2.html',
    controller: 'AddressesController'
  })
});

The partial view is simple, I create 2 elements

<label> Sender </label>
<address address-data='{{senderAddress}}'></address> <!-- I tried all combinations of passing this as argument -->

<label> Receiver </label>
<address></address>

Now the directive is declared as:

app.directive("address", function () {
  return {
    restrict: "E",
    templateUrl: "/directives/address.html",
    scope: {addrData: '@senderAddress'},
    link: function(scope, element, attributes) {
      scope.adData = attributes["addressData"];
    }
  }
});

and template for it is:

<div> 

<label> {{senderAddress}} </label> <!-- senderAddress from Addresses Controller is filled correctly -->
<div>
    <label>Country</label>
    <select>
        <option value=""></option>
        <option ng-repeat="country in countries.countries" value="{{country}}">{{country}}</option>
    </select>
</div>

<div>
    <label>City {{dto.adData.city}}</label>
    <input type="text" data-ng-model="dto.adData.city"  /> <!-- this I cannot pre-fill -->
</div>

<div>
    <label>Street{{data.adData.city}}</label>
    <input type="text" data-ng-model="dto.adData.street"> <!-- this I cannot pre-fill -->
</div>

</div>

It all works well outside of directive. But I miss something regarding how to handle the scope inside a directive with data being obtained from JSON service. Is it because JSON data is a promise object when the links to the directive are created? How to handle it?

PS

I also tried observing the attributes:

link: function(scope, element, attributes) {
      //scope.dto.adData = attributes["addressData"];
      attrs.$observe('addressData', function(data) {
        if (!data)
          return;
        scope.dto.adData = data;
      })
    }

Even for statically defined data it doesn't work:

app.directive("address", function () {
  return {

    controller: function($scope) {
      $scope.dto = {};
      $scope.dto.data = {"id": 1, "country": "Poland", "city": "Warsaw", "street": "Nullowska 15"};
    },

2 Answers 2

2

Passing in the JSON like this isn't how I'd do it as it's kind of hacking in the data binding and you probably don't get two-way binding. I'd use an isolate scope.

Your directive would be used without handlebars, to link up the scope variable:

<address address-data='senderAddress'></address>

And then you'd include a scope option in the directive definition:

app.directive("address", function () {
  return {
    restrict: "E",
    templateUrl: "/directives/address.html",
    scope: {
        addressData: '='
    }
  }
});

The bare equals-sign '=' tells Angular to double-bind the parent scope variable referenced in the address-data attribute to the child scope variable addressData. This is done automatically by normalizing the name "address-data" into the JS-style "addressData." If you wanted to name the two scope variables differently, you could do innerAddressData: '=addressData' instead.

If you do it like this, you don't need a linking function at all and the binding still should work.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks a lot, that's an elegant solution. I just wonder about this Angular Best Practices excerpt: "Always let users use expressions whenever possible ng-href and ng-src are plaintext attributes that support {{}} Use $attrs.$observe() since expressions are async and could change", isn't then observing the attributes worthwile?
I believe that best practice is referring to situations where you're dealing with plaintext values. Here you're using JSON data; unless you want to include handlebars expressions inside your JSON (which is, I think, a mistake) I don't think this best practice is relevant. Observing the attributes will probably work depending on what you want to do. It's only a one-way binding, though; if you change the data within the directive scope it won't change the data in the parent scope.
0

OK, I solved it, in case anyone has similar issues, it may help to check if the scope is set to true and to check if JSON is parsed from string ;-).

app.directive("address", function () {
  return {
    restrict: "E",
    templateUrl: "/directives/address.html",
    scope: true, // creates its own local scope
    link: function(scope, element, attributes) {

      attributes.$observe('addressData', function(data) {
        if (!data)
          return;
        scope.dto = {};
        // works almost fine but in 2nd case data is also filled
        scope.dto.adData = angular.fromJson(data);

      })
    }
  }
});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.