istrupin
istrupin

Reputation: 1533

Passing in $scope.$on name parameter as an attribute of an AngularJS directive

I'm trying to create a directive which allows me to pass in an attribute string which I then use as the "name" parameter when subscribing to events using $scope.$on. Essentially, the series of events is this:

  1. An object is broadcasted using $rootScope.$broadcast called 'validationResultMessage', in another controller for example.
  2. I have a directive which has an attribute called "subscription" to which I pass the string 'validationResultMessage'.
  3. That directive passes the value of the "subscription" attribute to its scope and subscribes to it with "$scope.$on".

The problem is, it looks like the value of the attribute is "undefined" at the time everything is evaluated, and so when I try to subscribe using $scope.$on, it actually subscribes me to "undefined" rather than "validationResultMessage"

Here is my directive:

app.directive('detailPane', function () {
return {
    restrict: 'E',
    scope: {
        selectedItem: '=',
        subscription: '@',
    },
    templateUrl: 'app/templates/DetailPane.html',  //I'm also worried that this is causing my controller to get instantiated twice
    controller: 'DetailPaneController'

  };
});

which I then use like this:

<td class="sidebar" ng-controller="DetailPaneController"  ng-style="{ 'display': sidebarDisplay }">
            <detail-pane 
                    selected-item='validationResult'
                    subscription='validationResultMessage'/>

</td>

And the controller that I'm trying to pass this attribute into:

app.controller('DetailPaneController', ['$scope', '$http', 'dataService', 'toastr', '$uibModal', '$rootScope', '$attrs', function ($scope, $http, dataService, toastr, $uibModal, $rootScope, $attrs) {
$scope.fetching = [];
$scope.validationResult = null;
$scope.sidebarDisplay = 'block';



console.log('subscription is ', $scope.subscription);
var thisSubscription = $scope.subscription;

//if I hardcode the param as 'validationResultMessage', this works
$scope.$on($scope.subscription, function (event, arg) {
    $scope.validationResult = arg;
    });
}]);

Upvotes: 3

Views: 241

Answers (3)

istrupin
istrupin

Reputation: 1533

So another way that I managed to solve this particular issue is to only use the internal DetailPaneController as defined in the directive body. Part of my problem was that I was causing the controller to be instantiated twice by having it as both the parent controller using ng-controller= in my html as well as being defined in the directive body. This way I can just use the straightforward "@" binding and everything gets resolved in the right order. I can even have another directive within my template that I can pass my validationResult into.

The new setup looks like this:

DetailPaneController:

app.controller('DetailPaneController', ['$scope', '$http', function ($scope, $http) {

$scope.$on($scope.subscription, function (event, arg) {
    $scope.validationResult = arg;
    $scope.exception = JSON.parse(arg.Exception);
    });
}]);

DetailPane Directive:

app.directive('detailPane', function () {
return {
    restrict: 'E',
    scope: {
        subscription: '@' //notice I am no longer binding to validationResult
    },
    templateUrl: 'app/templates/DetailPane.html',
    controller: 'DetailPaneController'
    };
});

Directive as used in HTML:

        <div class="sidebar" ng-style="{ 'display': sidebarDisplay }">
            <detail-pane subscription='validationResultMessage' />
        </div>

Directive Template (for good measure):

<div class="well sidebar-container">
<h3>Details</h3>
<div ng-show="validationResult == null" style="padding: 15px 0 0 15px;">
    <h5 class=""><i class="fa fa-exclamation-triangle" aria-hidden="true" /> Select a break to view</h5>

</div>
<div ng-show="validationResult != null">
    <table class="table table-striped">
        <tr ng-repeat="(key, value) in validationResult">
            <td class="sidebar-labels">{{key | someFilter}}</td>
            <td >{{value | someOtherFilter : key}}</td>
        </tr>
    </table>
    <another-directive selected-item="validationResult" endpoint="endpoint" />
</div>

Upvotes: 2

Searching
Searching

Reputation: 2279

I'm going to post my answer 1st, given that it's a bit of code, please let me know if this is the required outcome, so I can provide comments. You should be able to run the provided code snippet.

var app = angular.module('myApp', []);

app.directive('detailPane', function() {
  return {
    restrict: 'E',
    transclude: false,
    scope: {
      selectedItem: '=',
      subscription: '@'
    },
    link: function(scope, elem, attr) {
      scope.$on(scope.subscription, function(e, data) {
        scope.selectedItem = data.result;
        elem.text(data.message);
      });
    },
  };
});

app.controller('DetailPaneController', function($scope) {
  $scope.validationResult1 = "";
  $scope.validationResult2 = "";
});

app.controller('SecondController', function($rootScope, $scope, $timeout) {

  $timeout(function() {
    $rootScope.$broadcast('validationResultMessage1', {
      message: 'You fail!',
      result: 'Result from 1st fail'
    })
  }, 2000);

  $timeout(function() {
    $rootScope.$broadcast('validationResultMessage2', {
      message: 'You also fail 2!',
      result: 'Result from 2nd fail'
    })
  }, 4000);

});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<body ng-app='myApp'>
  <div ng-controller="DetailPaneController">
    <detail-pane class='hello' selected-item='validationResult1' subscription='validationResultMessage1'></detail-pane>
    <br/>
    <detail-pane class='hello' selected-item='validationResult2' subscription='validationResultMessage2'></detail-pane>

    <hr/>
    <span>{{validationResult1}}</span>
    <br/>
    <span>{{validationResult2}}</span>

  </div>
  <div ng-controller="SecondController">

  </div>
</body>

Upvotes: 1

Maciej Szpyra
Maciej Szpyra

Reputation: 312

I think you should set watcher on $scope.subscription and checking if new value is set and then start subscribing passed event.

$scope.$watch('subscription', function(nv, ov){
 //this makes sure it won't trigger at initialization
 if(nv!==ov){
   $scope.$on($scope.subscription, function (event, arg) {
      $scope.validationResult = arg;
   });
 }
});

Upvotes: 0

Related Questions