62

I am having issues getting my directive to render its content only after my promise has been resolved. I thought then() was supposed to do this but it doesn't seem to be working..

Here is my controller:

// Generated by CoffeeScript 1.6.3
(function() {
  var sprangularControllers;

  sprangularControllers = angular.module('sprangularControllers', ['sprangularServices', 'ngRoute']);

  sprangularControllers.controller('productsController', [
    '$scope', '$route', '$routeParams', 'Product', 'Taxonomy', function($scope, $route, $routeParams, Product, Taxonomy) {
      Taxonomy.taxonomies_with_meta().$promise.then(function(response) {
        return $scope.taxonomies = response.taxonomies;
      });
      return Product.find($routeParams.id).$promise.then(function(response) {
        return $scope.currentProduct = response;
      });
    }
  ]);

}).call(this);

My directive:

// Generated by CoffeeScript 1.6.3
(function() {
  var sprangularDirectives;

  sprangularDirectives = angular.module('sprangularDirectives', []);

  sprangularDirectives.directive('productDirective', function() {
    return {
      scope: {
        product: '='
      },
      templateUrl: 'partials/product/_product.html',
      link: function(scope, el, attrs) {
        console.log(scope);
        console.log(scope.product);
        return el.text(scope.product.name);
      }
    };
  });

}).call(this);

Scope returns okay, and when I check it in dev tools scope.product is not undefined however I am presuming that is because by the time I check it the promise has been resolved?

console.log(scope.product) however, returns undefined..

Melbourne2991
  • 11,707
  • 12
  • 44
  • 82

4 Answers4

82

As stated in an official thread about this issue (quickly closed as "won't fix because it would make directives wait"), a workaround is to wrap your directive in a ng-if :

<div ng-if="myPromiseParam">
  <my-directive param="myPromiseParam">
</div>
Jérôme Beau
  • 10,608
  • 5
  • 48
  • 52
43

Because your value is asynchronously populated, you'll want to add a watch function that updates your bound element.

  link: function(scope, el, attrs) {
    scope.$watch('product', function(newVal) {
        if(newVal) { el.text(scope.product.name);}
    }, true);
  }

You could also move a lot of complexity into a directive controller and use the link function for manipulating the DOM only.

The true third parameter to $watch causes a deep watch, since you're binding this directive to a model.

Here are a couple of links with good examples:
http://www.ng-newsletter.com/posts/directives.html
http://seanhess.github.io/2013/10/14/angularjs-directive-design.html

Joseph Yaduvanshi
  • 20,241
  • 5
  • 61
  • 69
  • Thanks Jim, does that mean that its simply the isolate scope that isn't being updated? Using a watch did cross my mind but I didnt see any examples online, so I presumed it wasn't the 'angular way'...hoping that's not the case, will give this a go and report back. – Melbourne2991 Jan 17 '14 at 04:26
  • I don't understand your question. Directives are all just nested scopes within your controller's scope. If you don't explicitly define a scope, angular creates one. It also creates watches for you in the background (like when you do `{{project.name}}`), so I'd say the watch is the most angular way. Also, [Mastering Web Application Development with AngularJS | Packt Publishing](http://bit.ly/1deKEKU) is a good book. – Joseph Yaduvanshi Jan 17 '14 at 17:55
  • 1
    If anyone comes by this. That $watch could also be in the parent controller as well. Since the directive is bound to the same scope value. Also if you have a watch on an object that gets a new object dot notated value then you should check if it exists in the watch as well. – mjwrazor Mar 24 '17 at 19:02
24

I know this is an older question, but thought I would try my hand at providing an updated answer.

When using a router, both ui-router and ngRouter have resolve methods that will resolve promises on url changes before switching to that route and rendering things on the page.

ngRouter Resolve tutorial
ui-router Resolve docs

Another option, instead of using $watch is to use angulars $q promise library. More specifically, the $q.when() method. This takes promises and values. IF it's a promise it will fire a .then() when the promise resolves. If its a value, it wraps it in a promise and immediately resolves it.

link: function(scope, el, attrs){
    $q.when(scope.product).then(function(product){
        scope.product = product;
        el.text(scope.product.name);
    });
}

Or there a couple way you can not show anything just with html.

<product-directive product='object'>
    <!-- Will hide span until product.name exists -->
    <span ng-show='product.name'>{{ product.name }}</span> 

    <!-- Will show default text until key exists -->
    {{ product.name || 'Nothing to see here' }}

    <!-- Will show nothing until key exists -->
    <span ng-bind='product.name'></span>
</product-directive>    
migpok35
  • 646
  • 7
  • 14
0

use $watch on your variable in your directive to get the updated value of your variable.

you can also make use of $q to resolve the promise.