从指令到服务的角度队列行动

时间:2017-03-31 22:09:43

标签: javascript angularjs

我正在努力更好地理解如何正确编写Angular代码,并编写自己的自定义模块以更好地理解事物应该如何工作。

我有一个由图像和指令组成的HTML标记,即

<img ng-if="!post.tooBig" mydirective resize="0" ng-src="/img/@{{post.Content}}">

我的指示在这里:

.directive('mydirective', [
           '$animate','$mediaStack',
  function($animate,$mediaStack) {
  return {
    restrict: 'A',
    compile: function(tElement, tAttrs) {
        return loader;
    }
  };

    function loader(scope, element, attrs) {
      var source = attrs.ngSrc;
      var tooLoadBig = parseInt(attrs.resize);
      if(tooLoadBig){
        var bigImage = new Image();
        bigImage.src = source.replace("small.",".");
      }
      }
}])

这个想法是这样的:如果图像的文件名附加了small,我知道它是一个缩略图。我想在后台加载它的大版本(相同的文件没有附加小),以便它可以用于灯箱。

这样可以正常工作,但问题是因为我正在compile中完成所有工作,当我设置bigImage.src = source.replace("small.",".");它会立即触发时,如果我有很多small 1}}页面上的图像,由于正在进行的所有加载,它会导致页面变慢。

因此我想使用$q来制作它,以便一次加载一张图片。

所以移动

 var bigImage = new Image();
 bigImage.src = source.replace("small.",".");

兑现承诺。在指令中执行此操作是最佳做法吗?我的理解是这样做是没有意义的,我应该使用服务,但我不知道该怎么做。我可以更多地玩它,但我希望有更多经验的人可以指导我这样的最佳实践和类似工作流程的代码示例,谢谢。

编辑:

指令:

.directive('mydirective', [
           '$animate','$mediaStack',
  function($animate,$mediaStack) {
  return {
    restrict: 'A',
    compile: function(tElement, tAttrs) {
        return loader;
    }
  };

    function loader(scope, element, attrs) {
      var source = attrs.ngSrc;
      var tooLoadBig = parseInt(attrs.resize);
      if(tooLoadBig){
        /*var bigImage = new Image();
        bigImage.src = source.replace("small.",".");*/
        $mediaStack.load(source);
      }
      }
}])

我的服务:

.factory('$mediaStack', [
             '$animate', '$timeout', '$document', '$compile', '$rootScope',
             '$q',
             '$injector',
    function($animate ,  $timeout ,  $document ,  $compile ,  $rootScope ,
              $q,
              $injector) {
      var OPENED_MEDIA_CLASS = 'media-open';
      var theImages = [];
      $mediaStack = {};
      $mediaStack.load = function(source){
          theImages.push(source);
      };
      $mediaStack.loadRequest   = function(theImages) {

                          deferred.resolve(function(){
                          var bigImage = new Image();
                          bigImage.src = theImages[0].replace("small.",".");
                          });
                          return deferred.promise;
                        }

                        /*var promise = asyncGreet('Robin Hood');
                        promise.then(function(greeting) {
                          alert('Success: ' + greeting);
                        }, function(reason) {
                          alert('Failed: ' + reason);
                        }, function(update) {
                          alert('Got notification: ' + update);
                        });*/
                        //above from docs
      }
      return $mediaStack;
    }])

这是因为它将图像URL添加到服务中的数组中,所以我拥有数组中的所有图像,如何正确使用$q基于该数组。我开始了,但大多数$mediaStack.loadRequest都基于$q文档,我不确定如何在这种情况下有效地使用它。

编辑2:

我的服务原样:

    .factory('$mediaStack', [
                 '$animate', '$timeout', '$document', '$compile', '$rootScope',
                 '$q',
                 '$injector',
        function($animate ,  $timeout ,  $document ,  $compile ,  $rootScope ,
                  $q,
                  $injector) {
           var OPENED_MEDIA_CLASS = 'media-open';
      var theImages = [];
      var theLoadedImages = [];
      var thePromises = [];
      var loading = false;
      $mediaStack = {};
      $mediaStack.load = function(source){
          theImages.push(source);
          var mainDeferred = $q.defer();
         if(loading)
         {
             thePromises.push(mainDeferred.promise);
             console.log(thePromises);
             $mediaStack.myRaceFn(thePromises).then(function() { 
             console.log("Fire!");
             loading=true;
            $mediaStack.loadRequest(theImages[0]).then(function(bigImage){ 
          console.log(bigImage);
            theImages.shift();
            theLoadedImages.push(bigImage);
            loading = false;
          mainDeferred.resolve(); 
        });
      });
         }
         if(!loading)
         {
            loading = true;
            $mediaStack.loadRequest(theImages[0]).then(function(bigImage){
            console.log(bigImage);
            theImages.shift();
            theLoadedImages.push(bigImage);
            loading = false;
            mainDeferred.resolve();         
            });

         }
           }
      $mediaStack.loadRequest = function(source){
                          var deferred = $q.defer();
                          var bigImage = new Image();
                          bigImage.src = source.replace("small.",".");
                          bigImage.onload = function() {
                          deferred.resolve(bigImage);
                          }
                           return deferred.promise;
      }
      //.race implementation in old angular
      $mediaStack.myRaceFn = function (promises){
   return $q(function(resolve, reject) { 
     promises.forEach(function(promise) {
       console.log(promise);
       promise.then(resolve, reject);
     });
   });
}
//.race implementation in old angular
      return $mediaStack;
        }])

我非常接近,我看到了一系列的承诺,但是我再也没有能够解雇它们了,所以我有10个图像,我得到了9个承诺的数组。我怎么解雇他们?

我预计会发生在这里:

$mediaStack.myRaceFn(thePromises).then(function() { 
console.log("Fire!");
loading=true;
$mediaStack.loadRequest(theImages[0]).then(function(bigImage){ 

但我从来没有得到console.log()

相反,我在$mediaStack.myRaceFn()中获得了控制台消息,向我展示了每个承诺(最后9个),但他们只是坐在那里?我错过了什么。

我想我可能太早解决了mainDeferred ......

2 个答案:

答案 0 :(得分:1)

我认为,你的预感很好。您需要一项服务来处理图像请求。请记住,浏览器仅限于将多个并发请求发送到单个服务器/代理(Max parallel http connections in a browser?

理论上,您可以创建一个承诺并通过双向绑定公开它,甚至可以使用属性驱动加载。类似的东西:

HTML:

<img mydirective="booleanValue" />

mydirective

$scope.$watch('mydirective', function (newVal, oldVal) {
  if (newVal === true) {
    //perform the load
  }
});

但我认为,服务会更好。您可以从该单点决定加载的内容和时间。它不会局限于UI组件,您不仅可以从指令上下文加载更大的图像。 因此,在服务中,基本代码将是:

&#13;
&#13;
module.factory('$mediaStack', ['$q', function($q) { //first of all you don't need all of these dependencies, keep the list short
  var imgDict = {}; //Image dictionary where we're going to keep promises and loaded images
  var loadingNow = 0;

  function loadImage(img) {
    //Send request and return promise
    var deferred = $q.defer();
    var bigImage = new Image();

    bigImage.onload = function() {
      deferred.resolve(bigImage);
    }

    bigImage.src = source.replace("small.", ".");

    return deferred.promise;
  }

  var $mediaStack = function(img) {
    var deferred = $q.defer(); //the main deferred task

    if (loadingNow > 4) { //if we're loading 5 images or more, defer the task untill one of the Images is loaded
      var promises = []; //an array of promises, list of images which are loading now
      Object.keys(imgDict).forEach(function(key) {
        if (!(imgDict[key] instanceof Element)) { //Check if the item from dictionary is a promise or loaded image
          promises.push(imgDict[key]); //if it's a promise, add it to the list
        }
      });
      $q.race(promises).then(function() { //use the list, to create a race: when one of the promises resolves (image is loaded) we can fire our function again (we probably have less than 5 Images loading at the same time)
        $mediaStack(img).then(function(data) { //call the function again
          deferred.resolve(data); //as we return a promise form that function we have to resolve main deferred object
        });
      });
    }

    if (!(img in imgDict)) { //when the image is not loaded yet
      loadingNow++; //increase the number of images being loaded
      imgDict[img] = loadImage(img).then(function(imgDOM) { //and load the image
        imgDict[img] = imgDOM;
        deferred.resolve(imgDOM); //once it's loaded resolve the main deferred task
        loadingNow--;
      });
    } else {
      deferred.resolve(imgDict[img]);
    }

    return deferred.promise; //promise to the main deferred task
  }

  return $mediaStack;
}]);
&#13;
&#13;
&#13;

承诺和延期任务背后的一般理念: 延迟对象是一个延迟任务,它有一个Promise参数 - 一个回调的入口点,一旦延期任务完成就会被调用。

检查$ q docs以获取更多信息:angular $q

如何编写服务:https://docs.angularjs.org/guide/services

什么是承诺? The general idea and native API

希望它有所帮助。

答案 1 :(得分:1)

将Oskar标记为已被接受,因为它基本上允许我找到解决方案。

以下是我实际实施的内容:

    .factory('$mediaStack', [
             '$animate', '$timeout', '$document', '$compile', '$rootScope',
             '$q',
             '$injector',
    function($animate ,  $timeout ,  $document ,  $compile ,  $rootScope ,
              $q,
              $injector) {
      var OPENED_MEDIA_CLASS = 'media-open';
      var theImages = [];
      var theLoadedImages = [];
      var thePromises = [];
      var loading = false;
      $mediaStack = {};
      $mediaStack.load = function(source){
          if(source)
          {
          theImages.push(source);
          }
          var mainDeferred = $q.defer();
         if(loading)
         {
             thePromises.push(mainDeferred.promise);
             console.log(thePromises);
             $mediaStack.myRaceFn(thePromises).then(function() { 
             console.log("Fire!");
             loading=true;
            $mediaStack.loadRequest(theImages[0]).then(function(bigImage){ 
          console.log(bigImage);
            theImages.shift();
            theLoadedImages.push(bigImage);
            loading = false;
          //mainDeferred.resolve(); 
        });
      });
         }
         if(!loading)
         {
            loading = true;
            $mediaStack.loadRequest(theImages[0]).then(function(bigImage){
            console.log(bigImage);
            theImages.shift();
            theLoadedImages.push(bigImage);
            loading = false;
            if(theImages.length>0)
            {
            $mediaStack.load();     
            };          
            });

         }
           }
      $mediaStack.loadRequest = function(source){
                          var deferred = $q.defer();
                          var bigImage = new Image();
                          bigImage.src = source.replace("small.",".");
                          bigImage.onload = function() {
                          deferred.resolve(bigImage);
                          }
                           return deferred.promise;
      }
      //.race implementation in old angular
      $mediaStack.myRaceFn = function (promises){
   return $q(function(resolve, reject) { 
     promises.forEach(function(promise) {
       console.log(promise);
       promise.then(resolve, reject);
     });
   });
}
//.race implementation in old angular
      return $mediaStack;
    }])