2

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

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

每次发生这种情况:

  • 浏览器始终是 Chrome
  • 操作系统是 Mac 或 Windows
  • Angular 开始编译阶段,但在开始发布链接之前失败
  • Angular 在编译阶段报错,但传递给 '$exceptionHandler' 服务的 'exception' 对象始终为空。
  • 没有报告其他 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>
4

1 回答 1

0

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

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

会不会是比赛条件?

于 2015-04-09T15:51:17.310 回答