1

I'm building an Angular pop-up system for multiple purposes. The way it works is that I have a directive called bitPopup which three variables get passed on to (type, action and data) as shown below:

index.html

<bit-popup type="popup.type" action="popup.action" data="popup.data"></bit-popup>

popup.js

app.directive('bitPopup', function () {
  return {
    restrict: 'E',
    template: html,
    scope: {
      type: '=',
      action: '=',
      data: '='
    },
    [***]
  }
}

The popup controller then loads a different directive based on the type:

popup.html (The HTML template shown above)

<div class="pop-up" ng-class="{visible: visible}" ng-switch="type">
  <bit-false-positive-popup ng-switch-when="falsePositive" type="type" action="action" data="data"></bit-false-positive-popup>
</div>

false_positives.js (Containing the bitFalsePositivePopup directive)

[...]
scope: {
  type: '=',
  action: '=',
  data: '='
}
[...]

And then the html template for the bitFalsePositivePopup directive displays some properties from data.

Now the way I'm triggering a pop-up works like this:

  1. From a template inside a directive containing the bitPopup directive i'll change $scope.popup's type, action and data.
  2. I'll do $scope.$broadcast('showPopup');
  3. The bitPopup directive will react because of $scope.$on('showPopup', [...]}); and makes the pop-up visible.

Now this really weird thing occurs where it works on the first try (the pop-up opens with the correct data information), but after the first try it will display the data from the previous try.

Now what's even weirder is that I tried logging the information on the first try and what I found out is that:

  • $scope.popup at index.html just before calling $scope.$broadcast('showPopup'); displays the right information.
  • $scope.data at the bitPopup directive displays null
  • $scope.data at the bitFalsePositivePopup directive displays the right information.

On the second try:

  • $scope.popup at index.html is correct again.
  • $scope.data at the bitPopup directive displays the information from the previous attempt
  • The same holds for the bitFalsePositivePopup directive.

Another weird thing is that when I use $scope.$apply() it does work correctly, only it displays the $apply already in progress error. I know I shouldn't use $scope.$apply() in this case, because it's all Angular events. But how is it possible that the passed scope is always a step behind?

Am I doing something wrong to begin with?

EDIT:

Because of amahfouz's answer I decided to post some more code for clarification. I left out some unimportant details for more clear reading.

index.html

<div class="falsePositives" ng-controller="falsePositives">
  <i class="fa fa-minus color-red" ng-click="triggerPopup('falsePositive', 'delete', {detection: getDetection(row.detection, row.source), source: row.source, triggers: row.triggers, hash: row.hash, date: row.date})"></i>
  <i class="fa fa-pencil" ng-click="triggerPopup('falsePositive', 'edit', {detection: getDetection(row.detection, row.source), source: row.source, triggers: row.triggers, hash: row.hash, date: row.date})"></i>

  <bit-popup type="popup.type" action="popup.action" data="popup.data"></bit-popup>
</div>

index.js

var app = require('ui/modules').get('apps/falsePositives');

app.controller('falsePositives', function ($scope, $http, keyTools, bitbrainTools, stringTools) {
  function init() {
    $scope.getDetection = getDetection;

    $scope.popup = {
      type: null,
      action: null,
      data: null
    };
  }

  function getDetection(hash, source) {
    return {
      'ids': 'BitSensor/HTTP/CSRF',
      'name': 'CSRF Detection',
      'description': 'Cross domain POST, usually CSRF attack',
      'type': [
        'csrf'
      ],
      'severity': 1,
      'certainty': 1,
      'successful': false,
      'input': ['s'],
      'errors': []
    };
  }

  $scope.triggerPopup = function (type, action, data) {
    $scope.popup = {
      type: angular.copy(type),
      action: angular.copy(action),
      data: angular.copy(data)
    };

    test();

    $scope.$broadcast('showPopup');
  };

  function test() {
    console.log('$scope.popup: ', $scope.popup);
  }
}

popup.html

<div class="pop-up-back" ng-click="hidePopup()" ng-class="{visible: visible}"></div>
<div class="pop-up" ng-class="{visible: visible}" ng-switch="type">
  <bit-false-positive-popup ng-switch-when="falsePositive" type="type" action="action" data="data"></bit-false-positive-popup>
</div>

popup.js

var app = require('ui/modules').get('apps/bitsensor/popup');

app.directive('bitPopup', function () {
  return {
    restrict: 'E',
    template: html,
    scope: {
      type: '=',
      action: '=',
      data: '='
    },
    controller: function ($scope) {
      $scope.visible = false;

      $scope.$on('showPopup', function () {
        console.log('$scope.data: ', $scope.data);
        $scope.visible = true;
      });

      $scope.$on('hidePopup', function () {
        hidePopup();
      });

      function hidePopup() {
        $scope.visible = false;
      }

      $scope.hidePopup = hidePopup;
    }
  };
});

false_positives.js

var app = require('ui/modules').get('apps/bitsensor/falsePositives');

app.directive('bitFalsePositivePopup', function () {
  return {
    restrict: 'E',
    template: html,
    scope: {
      type: '=',
      action: '=',
      data: '='
    },
    controller: function ($scope, objectTools, bitbrainTools, keyTools) {

      function init() {
        console.log('$scope.data @ fp: ', $scope.data);
      }

      function hidePopup() {
        $scope.data = null;
        $scope.$emit('hidePopup');
      }

      $scope.$on('showPopup', function () {
        init();
      });

      init();

      $scope.hidePopup = hidePopup;
    }
  }
}
Community
  • 1
  • 1
Luud Janssen
  • 362
  • 1
  • 3
  • 12

2 Answers2

1

Without the rest of the code I can only guess: You either need to use a promise when displaying the popup or use the $apply service to make the change to the popup visibility.

amahfouz
  • 2,328
  • 2
  • 16
  • 21
  • Thanks for your reply! I added some more code to the original post, maybe that helps to clarify the cause of the problem. – Luud Janssen Dec 30 '15 at 20:07
  • I looked at the documentation for [Angular's promise](https://docs.angularjs.org/api/ng/service/$q), but it feels weird to make the pop-up a asynchronous service. Also as mentioned in the original post, using $apply gives me the `$apply already in progress` error. – Luud Janssen Dec 31 '15 at 01:51
  • I know, but I had a similar problem with the drop down service and the promise solved it. Anyways, worth a try. – amahfouz Dec 31 '15 at 03:03
  • Using promise sadly didn't do it for me. Any other suggestions? – Luud Janssen Dec 31 '15 at 14:50
  • Let me take a closer look, but it would help if post a plunk to isolate the problem. – amahfouz Jan 01 '16 at 18:17
0

surround your $broadcast event in $timeout like follow:

$timeout(function() {
  $broadcast('eventName');
});

It will wait for $scope update and then will trigger the event.

vivex
  • 2,496
  • 1
  • 25
  • 30