AngularJS:异步初始化过滤器

Dav*_*net 22 javascript asynchronous filter angularjs

我在尝试使用异步数据初始化过滤器时遇到了麻烦.

过滤器非常简单,它需要将路径转换为name,但为此需要一个对应的数组,我需要从服务器获取.

在返回函数之前,我可以在过滤器定义中执行操作,但异步方面可以防止这种情况发生

angular.module('angularApp').
  filter('pathToName', function(Service){
    // Do some things here

    return function(input){
      return input+'!'
    }
  }
Run Code Online (Sandbox Code Playgroud)

使用承诺可能是可行的,但我对角度载荷的过滤方式没有任何清楚的了解.这篇文章解释了如何通过服务实现这样的魔力,但是有可能对过滤器做同样的事情吗?

如果有人对如何翻译这些路径有更好的了解,我会全力以赴.

编辑:

我尝试了承诺approch,但有些事情是不对的,我没看到:

angular.module('angularApp').filter('pathToName', function($q, Service){

  var deferred = $q.defer();
  var promise = deferred.promise;

  Service.getCorresp().then(function(success){
    deferred.resolve(success.data);
  }, function(error){
    deferred.reject();
  });

  return function(input){
    return promise.then(
      function(corresp){
        if(corresp.hasOwnProperty(input))
          return corresp[input];
        else
          return input;
      }
    )
  };
});
Run Code Online (Sandbox Code Playgroud)

我对承诺并不是很满意,是否正确使用它们?

Nik*_*los 42

这是一个例子:

app.filter("testf", function($timeout) {
    var data = null, // DATA RECEIVED ASYNCHRONOUSLY AND CACHED HERE
        serviceInvoked = false;

    function realFilter(value) { // REAL FILTER LOGIC
        return ...;
    }

    return function(value) { // FILTER WRAPPER TO COPE WITH ASYNCHRONICITY
        if( data === null ) {
            if( !serviceInvoked ) {
                serviceInvoked = true;
                // CALL THE SERVICE THAT FETCHES THE DATA HERE
                callService.then(function(result) {
                    data = result;
                });
            }
            return "-"; // PLACEHOLDER WHILE LOADING, COULD BE EMPTY
        }
        else return realFilter(value);
    }
});
Run Code Online (Sandbox Code Playgroud)

这个小提琴是使用超时而不是服务的演示.


编辑:根据sgimeno的评论,必须特别注意不要多次调用该服务.请参阅serviceInvoked上面代码和小提琴中的更改.另请参阅Angular 1.2.1的分叉小提琴和一个用于更改值和触发摘要周期的按钮:分叉小提琴


编辑2:根据MihaEržen的评论,这个解决方案没有为Angular 1.3提供登录工作.该解决方案几乎是微不足道的,虽然,使用$stateful过滤器标志,记录在这里下的"状态过滤器",以及必要的分叉小提琴.

请注意,此解决方案会影响性能,因为过滤器称为每个摘要周期.根据具体情况,性能下降可以忽略不计.

  • @sgimeno此方法不会导致任何其他过滤器的摘要周期.如果未正确设置保护条件,则可能会导致对服务的额外调用,因此可能需要在上面的代码中引用标志serviceInvoked.我正在更新代码和小提琴以反映这一点.(顺便说一句,我只有一次成为Firebug控制台打印消息的受害者;如果在"data"设置为非"空"之前触发摘要,则确实会多次调用该服务.) (2认同)
  • 不幸的是,这在角度1.3中不再起作用. (2认同)

Vit*_*lyB 20

让我们开始理解为什么原始代码不起作用.我已经简化了原来的问题,以使其更清晰:

angular.module('angularApp').filter('pathToName', function(Service) {

    return function(input) {
        return Service.getCorresp().then(function(response) {
            return response;
        });
    });

}
Run Code Online (Sandbox Code Playgroud)

基本上,过滤器调用返回promise的异步函数,然后返回其值.角度过滤器要求您返回一个可以轻松打印的值,例如字符串或数字.然而,在这种情况下,即使它看起来就像我们返回responsegetCorresp,我们实际上返回一个新的承诺 -任何返回值then()catch()函数是一个承诺.

Angular正试图通过强制转换将promise对象转换为字符串,并且没有任何明智的回报并显示空字符串.


所以我们需要做的是,返回一个临时字符串值并异步更改它,如下所示:

JSFiddle

HTML:

<div ng-app="app" ng-controller="TestCtrl">
    <div>{{'WelcomeTo' | translate}}</div>
    <div>{{'GoodBye' | translate}}</div>
</div>
Run Code Online (Sandbox Code Playgroud)

使用Javascript:

app.filter("translate", function($timeout, translationService) {

    var isWaiting = false;
    var translations = null;

    function myFilter(input) {

        var translationValue = "Loading...";
        if(translations)
        {
            translationValue = translations[input];
        } else {
            if(isWaiting === false) {
                isWaiting = true;
                translationService.getTranslation(input).then(function(translationData) {
                    console.log("GetTranslation done");
                    translations = translationData;
                    isWaiting = false;
                });
            }
        }

        return translationValue;
    };

    return myFilter;
});
Run Code Online (Sandbox Code Playgroud)

每当Angular尝试执行过滤器时,它会检查是否已经提取了翻译,如果不是,则会返回"Loading ..."值.我们还使用该isWaiting值来防止多次调用服务.

上面的示例适用于Angular 1.2,但是,在Angular 1.3中的更改中,性能改进会改变过滤器的行为.以前,每个摘要周期都会调用过滤函数.但是,从1.3开始,它只调用过滤器,如果值被更改,在我们的上一个示例中,它将永远不再调用过滤器 - 'WelcomeTo'永远不会改变.

幸运的是,修复非常简单,您只需要在过滤器中添加以下内容:

JSFiddle

myFilter.$stateful = true;
Run Code Online (Sandbox Code Playgroud)

最后,在处理这个问题时,我遇到了另一个问题 - 我需要使用过滤器来获取可能发生变化的异步值- 具体来说,我需要获取单一语言的翻译,但是一旦用户更改了语言,我需要获取一个新的语言集.这样做,事实证明有点棘手,尽管概念是相同的.这是代码:

JSFiddle

var app = angular.module("app",[]);
debugger;

app.controller("TestCtrl", function($scope, translationService) {
    $scope.changeLanguage = function() {
        translationService.currentLanguage = "ru";
    }
});

app.service("translationService", function($timeout) {
    var self = this;

    var translations = {"en": {"WelcomeTo": "Welcome!!", "GoodBye": "BYE"}, 
                        "ru": {"WelcomeTo": "POZHALUSTA!!", "GoodBye": "DOSVIDANYA"} };

    this.currentLanguage = "en";
    this.getTranslation = function(placeholder) {
        return $timeout(function() {
            return translations[self.currentLanguage][placeholder];
        }, 2000);
    }
})

app.filter("translate", function($timeout, translationService) {

    // Sample object: {"en": {"WelcomeTo": {translation: "Welcome!!", processing: false } } }
    var translated = {};
    var isWaiting = false;

    myFilter.$stateful = true;
    function myFilter(input) {

        if(!translated[translationService.currentLanguage]) {
            translated[translationService.currentLanguage] = {}
        }

        var currentLanguageData = translated[translationService.currentLanguage];
        if(!currentLanguageData[input]) {
            currentLanguageData[input] = { translation: "", processing: false };
        }

        var translationData = currentLanguageData[input];
        if(!translationData.translation && translationData.processing === false)
        {
            translationData.processing = true;
            translationService.getTranslation(input).then(function(translation) {
                console.log("GetTranslation done");
                translationData.translation = translation;
                translationData.processing = false;
            });
        }

        var translation = translationData.translation;
        console.log("Translation for language: '" + translationService.currentLanguage + "'. translation = " + translation);
        return translation;
    };

    return myFilter;
});
Run Code Online (Sandbox Code Playgroud)