0

I have a custom directive in AngularJS and I want to pass a variable to it from my controller.

Controller:

angular.
    module('orderOverview').
    component('orderOverview', {
        templateUrl: 'home-page/order-overview/order-overview.template.html',
        controller: ['Orders',
            function ControllerFunction(Orders) {
        var self = this;

        // Order Info
        Orders.getOverview().$promise.then(function(data) {
          self.LineItems = data;
        });
        // Order Info
      }
    ]
  });

Directive

angular.
    module('myApp').
    directive('gvInitializeOrderStatus', function() {
    return {
      scope: {
        field: '@',
        myData: '='
      },
      link: function(scope, element) {
        console.log('field:', scope.field);
        console.log('data:', scope.myData);
      }
    }
    });

HTML

<div gv-initialize-order-status field="InquiryDone" myData="$ctrl.LineItems">
    <span class="tooltiptext">Inquiry</span>
</div>

When I load the page, field logs fine, however data is undefined.

I've tried this a lot of ways, but this is how it should work in my mind if it gives you any idea of what I'm thinking of.

At another point in the same template I pass ng-repeat data to a directive just fine, but in this case I specifically don't want to ng-repeat

ng-repeat HTML that successfully passed data

<li ng-repeat="lineItem in $ctrl.LineItems">
    <div class="status-circle" 
         ng-click="toggleCircle($event, lineItem, 'InquiryDone')"
         field="InquiryDone" item="lineItem" gv-initialize-statuses>
        <span class="tooltiptext">Inquiry</span>
    </div>
</li>

In my other directive, gv-initialize-statuses, I use the same concept in my scope object and have something like scope: { 'field': '=' } and it works just fine.

How can I accomplish this without using ng-repeat?

georgeawg
  • 48,608
  • 13
  • 72
  • 95
Colin Harrison
  • 184
  • 2
  • 18

5 Answers5

1

Two-way binding with = should be avoided

The directive needs to use $watch in the link function:

app.directive('gvInitializeOrderStatus', function() {
    return {
      scope: {
        field: '@',
        ̶m̶y̶D̶a̶t̶a̶:̶ ̶'̶=̶'̶
        myData: '<'
      },
      link: function(scope, element) {
        console.log('field:', scope.field);
        console.log('data:', scope.myData);
        scope.$watch('myData', function(value) {
            console.log('data:', scope.myData);
        });
      }
    }
});

Directives such as ng-repeat automatically use a watcher.

Also for performance reasons, two-way binding with = should be avoided. One-way binding with < is more efficient.


For more efficient code, use the $onChanges life-cycle hook in the controller:

app.directive('gvInitializeOrderStatus', function() {
    return {
      scope: {
        field: '@',
        ̶m̶y̶D̶a̶t̶a̶:̶ ̶'̶=̶'̶
        myData: '<'
      },
      bindToController: true,
      controllerAs: "$ctrl",
      controller: function() {
        console.log('field:', this.field);
        console.log('data:', this.myData);
        this.$onChanges = function(changes) {
            if (changes.myData)
                console.log('data:', changes.myData.currentValue);
            };
        });
      }
    }
});

Doing so will make the code more efficient and the migration to Angular 2+ easier.


There are different levels of watch:

The ng-repeat directive actually uses $watchCollection.

The directive may need to use the $doCheck Life-Cycle hook.

For more information, see

Community
  • 1
  • 1
georgeawg
  • 48,608
  • 13
  • 72
  • 95
  • I like the tip about the one way binding, but this actually isn't a promise issue. I was playing around and found without ng-repeat I couldn't even pass a variable such as `self.testVar = 400`. I tried both of these solutions, but myData was still undefined. – Colin Harrison Jul 27 '18 at 13:28
  • The next step is to create a [PLNKR](http://plnkr.co/edit/?p=preview) to reproduce the problem. – georgeawg Jul 27 '18 at 13:49
  • Thanks, I will set a reminder to try this later, for learning's sake. However, I found a working solution that I just put up and I'll have to keep going for now. – Colin Harrison Jul 27 '18 at 13:52
0

Do this if you just want the data in your directive

Orders.getOverview().$promise.then(function(data) {
          self.LineItems = data;
          $rootScope.$broadcast('myData', data);
        });

And in your directive just catch this event with callback function

$scope.$on('myData', function(event, args) {

    var anyThing = args;
    // do what you want to do
});
Gaurav Singh
  • 369
  • 1
  • 6
0

The problem is the $promise.

self.LineItems is not ready when the directive get active. That's why data is undefined.

Maybe ng-if could helps you:

<div ng-if="$ctrl.LineItems" gv-initialize-order-status field="InquiryDone" myData="$ctrl.LineItems">
    <span class="tooltiptext">Inquiry</span>
</div>

Hope this helps. Good luck!

Leo Brescia
  • 220
  • 2
  • 8
0

So I found an answer that works when I was reading about $compile in the docs. I realized you can get interpolated attribute values, so I removed the myData field from the scope object and instead accessed the value through the attrs object, like so.

Directive

angular.
    module('myApp').
    directive('gvInitializeOrderStatus', function() {
    return {
      scope: {
        field: '@'
      },
      link: function(scope, element, attrs) {
        console.log('field:', scope.field);
        console.log('attrs:', attrs);
        attrs.$observe('lineItems', function(value) {
          console.log(value);
        })
      }
    }
    });

HTML

<div gv-initialize-order-status field="InquiryDone" lineItems="{{$ctrl.LineItems}}">
     <span class="tooltiptext">Inquiry</span>
</div>

Notice the added {{ }} to the lineItems attribute. The attrs.$observe block lets me get notices of changes to the value, as well.

Colin Harrison
  • 184
  • 2
  • 18
  • 1
    If that works for you, then get rid of the isolate scope entirely and use `attrs.field` for the `field` attribute. Then you have an attribute directive that plays nicely with other attribute directives. – georgeawg Jul 27 '18 at 14:03
-1

in directive

angular.
module('myApp').
directive('gvInitializeOrderStatus', function() {
return {
  scope: {
    field: '@',
    ngModel: '='     // <- access data with this (ngModel to ng-model in view)
  },
  link: function(scope, element) {
    console.log('field:', scope.field);
    console.log('data:', scope.ngModel);
  }
}
});

in view

<div gv-initialize-order-status field="InquiryDone" ng-model="$ctrl.LineItems">
  <span class="tooltiptext">Inquiry</span>
</div>
  • Possible duplicate of a duplicate by the way : [this link](https://stackoverflow.com/questions/50043159/use-ng-model-in-directive-and-controller/50045176#50045176) – Jean-Philippe Bergeron Jul 26 '18 at 21:09
  • The `ng-model` attribute instantiates the [ngModelController](https://docs.angularjs.org/api/ng/type/ngModel.NgModelController). Unless you intend to turn the `
    ` element into a form control, that name should be avoided.
    – georgeawg Jul 27 '18 at 01:21