指令编译时偶尔会出现AngularJS错误

时间:2015-04-09 15:10:35

标签: angularjs angularjs-directive angularjs-compile

我有一个AngularJS指令,可以呈现为自定义社交共享小部件。在每天大约10,000次页面浏览中,大约有1或2次,在开始编译指令后出现Angular错误。这使原始HTML部分保留在DOM中,对用户可见。

我才知道这个错误,因为有几个用户报告过它。我无法重现它,但我设计了一些信息性的日志记录,表明它正在发生。

每次发生这种情况:

  • 浏览器始终为Chrome
  • 操作系统是Mac或Windows
  • Angular启动编译阶段,但在启动post link
  • 之前失败
  • Angular在编译阶段报告错误,但传递给'$ exceptionHandler'服务的'exception'对象始终为null。
  • 未报告任何其他JavaScript错误

多天内某些相同IP发生此错误。

有没有人有过类似的问题?

修改

这是我的代码......

JavaScript的:

(function () {

  angular.module('common', []);

  angular.module('common')
    .filter('encodeURIComponent', function () {
      return window.encodeURIComponent;
    });

  function configure($provide) {

    // Pass all Angular errors to Loggly
    $provide.decorator("$exceptionHandler", function ($delegate) {
      return function exceptionHandlerDecorator(exception, cause) {
        $delegate(exception, cause);
        _LTracker.push({
          'error': 'angularError',
          'app': 'shareCounts',
          'err': exception,
          'element': cause
        });
      };
    });

  }

  angular.module('common')
    .config(['$provide', configure]);

  function configure($provide) {

    // Defines available share options as well as behaviors of the share popup windows
    function shareLinksConfig() {
      return {
        'facebook': {
          width: 670,
          height: 200,
          urlBase: 'https://www.facebook.com/sharer/sharer.php?',
          shareParamPre: 'u=',
          msgParamPre: '',
          mediaParamPre: '',
          addParams: ''
        },
        'twitter': {
          width: 550,
          height: 420,
          urlBase: 'https://twitter.com/intent/tweet?',
          shareParamPre: 'url=',
          msgParamPre: '&text=',
          mediaParamPre: ''
        },
        'googlePlus': {
          width: 600,
          height: 600,
          urlBase: 'https://plus.google.com/share?',
          shareParamPre: 'url=',
          msgParamPre: '',
          mediaParamPre: '',
          addParams: ''
        },
        'linkedIn': {
          width: 600,
          height: 400,
          urlBase: 'http://www.linkedin.com/shareArticle?',
          shareParamPre: 'url=',
          msgParamPre: '',
          mediaParamPre: '',
          addParams: '&mini=true'
        },
        'pinterest': {
          width: 750,
          height: 320,
          urlBase: 'https://www.pinterest.com/pin/create/button/?',
          shareParamPre: 'url=',
          msgParamPre: '&description=',
          mediaParamPre: '&media=',
          addParams: ''
        },
        'email': {
          width: 0,
          height: 0,
          urlBase: '',
          shareParamPre: '',
          msgParamPre: '',
          mediaParamPre: '',
          addParams: ''
        }
      };
    }
    $provide.factory('shareLinksConfig', shareLinksConfig);

  }

  angular.module('common')
    .config(['$provide', configure]);

  function ShareLinksController($scope, shareLinksService) {
    sendToLoggly.push("A \"ShareLinksController\" started constructing...");
    sendToLoggly.push("...and the $scope is typeof...");
    sendToLoggly.push(typeof $scope);

    var vm = this;

    vm.share = function ($event, shareVia) {
      if (shareVia !== 'email') {
        $event.preventDefault();
        // console.log($scope.mediaUrl);
        shareLinksService.openPopUp(shareVia, $scope.shareUrl, $scope.shareMsg, $scope.mediaUrl);
      }

      // Tell Google Analytics share link was clicked
      shareLinksService.pushGAEvent($scope.analyticsLocation, shareVia, $scope.shareUrl);
    };

    $scope.shareLinksShown = true; // Initialized to true, but then this gets set to false in the directive's link function if slideIn is true
    vm.toggle = function ($event) {
      $event.preventDefault();
      $scope.shareLinksShown = !$scope.shareLinksShown;
    };

    sendToLoggly.push("...and controller finished constructing.");
  }

  angular.module('common')
    .controller('ShareLinksController', ["$scope", "shareLinksService",
                ShareLinksController]);

  function fuShareLinks($http, shareLinksConfig, testRenderingService) {

    function compile() {

      sendToLoggly.push("A \"fuShareLinks\" directive started compiling...");

      testRenderingService.testShareCounts();

      return function postLink(scope) {
        sendToLoggly.push("A \"fuShareLinks\" directive started postLinking...");

        function Settings(shareVia, slideInDir, slideToggleLabel, colorized, showCounts) {
          var self = this,
            prop,
            splitArray;


          /* --------
             ShareVia
             --------
             Comma separated list of ways to share
             Accepted options are: 'facebook, twitter, googlePlus, linkedIn, pinterest, email' */

          // Copy the properties from the config and initialize to false
          self.shareVia = {};
          for (prop in shareLinksConfig) {
            if (shareLinksConfig.hasOwnProperty(prop)) {
              self.shareVia[prop] = false;
            }
          }
          if (typeof shareVia === 'string') {
            splitArray = shareVia.split(',');
          } else {
            splitArray = [];
          }
          // Check each value of splitArray, if it is in possible share options, 
          // set that option to true.
          angular.forEach(splitArray, function (value) {
            // Clean up 'value' a bit by removing spaces
            value = value.trim();
            if (value.length > 0) {
              if (self.shareVia.hasOwnProperty(value)) {
                self.shareVia[value] = true;
              }
            }
          });


          /* --------
             Slide In
             --------
             The slide-in functionality is activated by passing a value to 'slideInDir'.
             Accepted values are 'left' or 'down' (case insensitive)
             The 'slideToggleLabel' can be any string, if empty, it defaults to 'Share'. */
          self.slideIn = {
            direction: '',
            label: 'Share'
          };
          if (typeof slideInDir === 'string') {
            slideInDir = slideInDir.toUpperCase();
          }
          switch (slideInDir) {
          case 'LEFT':
            self.slideIn.direction = 'left';
            break;
          case 'DOWN':
            self.slideIn.direction = 'down';
            break;
          }
          if (typeof slideToggleLabel === 'string') {
            self.slideIn.label = slideToggleLabel;
          }


          /* ---------
             Colorized
             ---------
             'true', 'yes', or 'colorized' (case insensitive) -- results in true
             defaults to false */
          self.colorized = false;
          if (typeof colorized === 'string') {
            colorized = colorized.toUpperCase();
          }
          switch (colorized) {
          case 'TRUE':
            self.colorized = true;
            break;
          case 'YES':
            self.colorized = true;
            break;
          case 'COLORIZED':
            self.colorized = true;
            break;
          }


          /* -----------
             Show Counts
             -----------
             'true', 'yes', or 'show' (case insensitive) -- results in true
             defaults to false */
          self.showCounts = false;
          if (typeof showCounts === 'string') {
            showCounts = showCounts.toUpperCase();
          }
          switch (showCounts) {
          case 'TRUE':
            self.showCounts = true;
            break;
          case 'YES':
            self.showCounts = true;
            break;
          case 'SHOW':
            self.showCounts = true;
            break;
          }

        }

        scope.settings = new Settings(
          scope.shareVia,
          scope.slideInDir,
          scope.slideToggleLabel,
          scope.colorized,
          scope.showCounts
        );
        // Initally hide the share links, if they are set to toggle
        if (scope.settings.slideIn.direction !== '') {
          scope.shareLinksShown = false;
        }

        function ShareCounts(shareVia) {
          var self = this;

          angular.forEach(shareVia, function (value, name) {
            self[name] = 0;
          });

          $http.get(
            '/local/social-share-counts/?url=' +
              encodeURIComponent(scope.shareUrl)
          ).success(function (data) {
            /* Check for share counts in the returned data.

               Must use consistent naming for the social networks
               from shareLinksConfig properties all the way to the
               JSON data containting the counts. 

               Expected JSON format:
               {
                "twitter": {
                  "count": 42, 
                  "updated": "2015-03-25T15:13:48.355422"
                }, 
                "facebook": {
                  "count": 120, 
                  "updated": "2015-03-25T15:13:47.470778"
                }
               }
            */
            angular.forEach(shareVia, function (value, name) {
              if (data[name] && data[name]["count"]) {
                self[name] = data[name]["count"];
              }
            });
          }).error(function (data, status) {
            sendToLoggly.push("HTTP Response " + status);
          });

        }

        // If showing share counts, get the counts from the specified networks
        if (scope.settings.showCounts) {
          scope.shareCounts = new ShareCounts(scope.settings.shareVia);
        }

        sendToLoggly.push("...and directive finished postLinking.");
      };

      sendToLoggly.push("...and directive finished compiling.");
    }

    return {
      restrict: 'E',
      scope: {
        shareVia: '@',
        shareUrl: '@',
        shareMsg: '@',
        mediaUrl: '@',
        analyticsLocation: '@',
        slideInDir: '@',
        slideToggleLabel: '@',
        colorized: '@',
        showCounts: '@'
      },
      controller: 'ShareLinksController',
      controllerAs: 'shrLnksCtrl',
      templateUrl: '/angular-partials/common.share-links.html',
      compile: compile
    };

  }

  angular.module('common')
    .directive('fuShareLinks', ['$http', 'shareLinksConfig', 'testRenderingService', fuShareLinks])

    .factory('testRenderingService', function () {
      var timerId = null;
      function evalShareRender() {
        var renderError = (-1 < $('em.ng-binding')
          .text()
          .indexOf('{{'));

        if (renderError) {
          console.error('RENDER ERROR');
          _LTracker.push({
            'error': 'rendering',
            'app': 'shareCounts',
            'statusMsgs': sendToLoggly,
            'userAgent': navigator.userAgent
          });
        }
      }
      return {
        testShareCounts: function () {
          if (!timerId) {
            timerId = window.setTimeout(evalShareRender, 5000);
          }
        }
      };
    });

  function shareLinksService(shareLinksConfig) {

    function openPopUp(shareVia, shareUrl, shareMsg, mediaUrl) {
      var width,
        height,
        urlBase,
        shareParamPre,
        msgParamPre,
        mediaParamPre,
        addParams,
        popUpUrl;

      width = shareLinksConfig[shareVia].width;
      height = shareLinksConfig[shareVia].height;

      urlBase = shareLinksConfig[shareVia].urlBase;
      shareParamPre = shareLinksConfig[shareVia].shareParamPre;
      msgParamPre = shareLinksConfig[shareVia].msgParamPre;
      mediaParamPre = shareLinksConfig[shareVia].mediaParamPre;
      addParams = shareLinksConfig[shareVia].addParams;

      popUpUrl = encodeURI(urlBase);
      popUpUrl += encodeURI(shareParamPre);
      popUpUrl += encodeURIComponent(shareUrl);
      if (msgParamPre && shareMsg) {
        popUpUrl += encodeURI(msgParamPre);
        popUpUrl += encodeURIComponent(shareMsg);
      }
      if (mediaParamPre && mediaUrl) {
        popUpUrl += encodeURI(mediaParamPre);
        popUpUrl += encodeURIComponent(mediaUrl);
      }
      popUpUrl += encodeURI(addParams);

      // Open the social share window
      window.open(popUpUrl, '_blank', 'width=' + width + ',height=' + height);
    }


    function pushGAEvent(analyticsLocation, shareVia, shareUrl) {

      function capitalize(firstLetter) {
        return firstLetter.toUpperCase();
      }

      var gaEventAction = shareVia;
      gaEventAction = gaEventAction.replace(/^[a-z]/, capitalize);
      gaEventAction += ' - Clicked';

      _gaq.push([
        '_trackEvent',
        analyticsLocation + ' - SocialShare',
        gaEventAction,
        shareUrl
      ]);
    }

    return {
      openPopUp: openPopUp,
      pushGAEvent: pushGAEvent
    };

  }

  angular.module('common')
    .factory('shareLinksService', ['shareLinksConfig', shareLinksService]);

}());

HTML:

<div class="share-links-wrapper" ng-class="{ 'right': settings.slideIn.direction === 'left', 'center': settings.slideIn.direction === 'down' }" ng-cloak>
  <a href="#" class="toggle" ng-show="settings.slideIn.direction != ''" ng-click="shrLnksCtrl.toggle($event)">
    <i class="fuicon-share"></i>{{ settings.slideIn.label }}
  </a>
  <div class="share-links" ng-class="{ 'share-links-colorized': settings.colorized }" ng-show="shareLinksShown">
    <ul>
      <li ng-show="settings.shareVia.facebook">
        <a href="#" ng-click="shrLnksCtrl.share($event, 'facebook')" 
           class="fuicon-hex-facebook">
        </a>
        <em ng-show="settings.showCounts &amp;&amp; shareCounts.facebook > 0">
          {{ shareCounts.facebook }}
        </em>
      </li>
      <li ng-show="settings.shareVia.twitter">
        <a href="#" ng-click="shrLnksCtrl.share($event, 'twitter')" 
           class="fuicon-hex-twitter">
        </a>
        <em ng-show="settings.showCounts &amp;&amp; shareCounts.twitter > 0">
          {{ shareCounts.twitter }}
        </em>
      </li>
      <li ng-show="settings.shareVia.googlePlus">
        <a href="#" ng-click="shrLnksCtrl.share($event, 'googlePlus')" 
           class="fuicon-hex-googleplus">
        </a>
        <em ng-show="settings.showCounts &amp;&amp; shareCounts.googlePlus > 0">
          {{ shareCounts.googlePlus }}
        </em>
      </li>
      <li ng-show="settings.shareVia.linkedIn">
        <a href="#" ng-click="shrLnksCtrl.share($event, 'linkedIn')" 
           class="fuicon-hex-linkedin">
        </a>
        <em ng-show="settings.showCounts &amp;&amp; shareCounts.linkedIn > 0">
          {{ shareCounts.linkedIn }}
        </em>
      </li>
      <li ng-show="settings.shareVia.pinterest &amp;&amp; mediaUrl">
        <a href="#" ng-click="shrLnksCtrl.share($event, 'pinterest')"
           class="fuicon-hex-pinterest">
        </a>
        <em ng-show="settings.showCounts &amp;&amp; shareCounts.pinterest > 0">
          {{ shareCounts.pinterest }}
        </em>
      </li>
      <li ng-show="settings.shareVia.email">
        <a href="mailto:?subject={{ shareMsg | encodeURIComponent }}
                 &amp;body={{ shareUrl | encodeURIComponent }}" 
           ng-click="shrLnksCtrl.share($event, 'email')"
           class="fuicon-hex-email">
        </a>
      </li>
    </ul> 
  </div>
</div>

1 个答案:

答案 0 :(得分:0)

没有这样的问题,但是如此罕见,你可以让它重新加载页面吗?

另外,你知道ng-cloak吗?隐藏原始内容可能很有用:)

这可能是竞争条件吗?