3

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;
    });
}]);

3 Answers 3

2

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>

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

Comments

1

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>

2 Comments

On face, this is indeed providing the behavior that I'm after. If I'm understanding correctly, It's setting the validationResult of the parent DetailPaneController's scope to what's being broadcasted by the SecondController. I assume I could then use a template referencing validationResult1 and validationResult2 to display the information how I wanted to if I didn't want to have the interpolation inline. I see you're using a link function to accomplish this behavior, which is not something that I considered before. Am I understanding the basic mechanics of this solution correctly?
Yes, $broadcast to the $root and let any subscribed listeners get the event. In this requirement,we do this via directive, each detailsPane should only listen to what it subscribed to. Needs to be reusable and independent. The selected-item is the model that needs to be updated when the message is received, probably bound to the declared controller/view. link is powerful for this sort of DOM manipulation and when directive logic is required. I've used the validationresult1 and 2 just to mimic your setup and show that details can be passed. Not that it's required.@istrupin
0

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;
   });
 }
});

1 Comment

Hmm, I see what you're trying to do here, and I think it might be part of the issue, but implementing that did not work. It looks like now my .$on never gets hit.

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.