2

カスタムのソーシャル共有ウィジェットにレンダリングする AngularJS ディレクティブがあります。1 日あたり約 10,000 ページ ビューのうち、そのうちの約 1 ~ 2 回で、Angular はディレクティブのコンパイルを開始した後にエラーになります。これにより、未加工の HTML パーシャルが DOM に残り、ユーザーに表示されます。

このエラーを知ったのは、複数のユーザーから報告されたためです。再現することはできませんが、発生していることを示す情報ログを作成しました。

これが発生するたびに:

  • ブラウザは常に Chrome
  • OS は Mac または Windows
  • Angular はコンパイル フェーズを開始しますが、ポスト リンクを開始する前に失敗します
  • Angular はコンパイル フェーズ中にエラーを報告しますが、「$exceptionHandler」サービスに渡される「例外」オブジェクトは常に 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>
4

1 に答える 1

0

そのような問題はありませんでしたが、非常にまれであるため、ページをリロードできますか?

あと、ng-cloakってご存知ですか?生のものを隠すのに便利です:)

競合状態になる可能性はありますか?

于 2015-04-09T15:51:17.310 に答える