AngularJS:异步初始化过滤器

Posted

技术标签:

【中文标题】AngularJS:异步初始化过滤器【英文标题】:AngularJS : Asynchronously initialize filter 【发布时间】:2013-10-03 12:11:17 【问题描述】:

我在尝试使用异步数据初始化过滤器时遇到问题。

过滤器非常简单,它需要将路径转换为名称,但要做到这一点,它需要一个对应数组,我需要从服务器获取。

我可以在返回函数之前在过滤器定义中做一些事情,但是异步方面阻止了这一点

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

    return function(input)
      return input+'!'
    
  

使用承诺可能是可行的,但我对角度负载过滤器的方式没有任何清楚的了解。 这个post 解释了如何通过服务实现这种魔力,但过滤器是否可以这样做?

如果有人对如何翻译这些路径有更好的想法,我会全力以赴。

编辑:

我尝试了 Promise 方法,但有些地方不对劲,我看不到是什么:

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;
      
    )
  ;
);

我对 Promise 不是很熟悉,它是正确的使用方式吗?

【问题讨论】:

现在过滤器获取对应数据,并将其放入过滤器内的 var 中。这场战争然后被用来翻译东西。问题是它并不完美,如果服务器响应需要很长时间,过滤器还没有对应的数据,在这种情况下它无论如何都不会翻译。 你可以返回一个函数的promise:docs.angularjs.org/api/ng.$q 【参考方案1】:

这是一个例子:

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);
    
);

这个fiddle 是一个使用超时而不是服务的演示。


编辑:根据 sgimeno 的评论,必须格外小心,不要多次调用该服务。请参阅上面代码和小提琴中的serviceInvoked 更改。另请参阅 Angular 1.2.1 的分叉小提琴和更改值和触发摘要循环的按钮:forked fiddle


编辑 2:根据 Miha Eržen 的评论,此解决方案对 Angular 1.3 没有记录器工作。不过,解决方案几乎是微不足道的,使用 $stateful 过滤器标志,在“有状态过滤器”下记录 here,以及必要的 forked fiddle。

请注意,此解决方案会损害性能,因为每个摘要循环都会调用过滤器。性能下降可以忽略不计,具体取决于具体情况。

【讨论】:

如果你想在模板中使用过滤器,我认为这不是最好的方法,过滤器可以在渲染期间多次调用,它们会触发大量的摘要循环......不是吗? @sgimeno 此方法不会导致比任何其他过滤器更多的摘要循环。如果未正确设置保护条件,可能会导致对服务的额外调用,因此可能需要在上面的代码中使用标志 serviceInvoked。我正在更新代码和小提琴以反映这一点。 (顺便说一句,我只成为 Firebug 控制台打印消息的受害者一次;如果在 data 设置为非null 之前触发摘要,则该服务确实会被多次调用。) 你是对的,服务被多次调用,你必须确保1次执行。我的问题是我的 http 服务在 $rootScope 中设置了属性,这导致了摘要循环并触发了过滤器。自我注意:永远不要从服务层更新任何范围。 不幸的是,这在 Angular 1.3 中不再起作用。 @MihaEržen 确实,检查必要的修改以使其在“EDIT 2”下的 1.3 中工作【参考方案2】:

让我们从了解为什么原始代码不起作用开始。我已经稍微简化了原始问题以使其更清楚:

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

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


基本上,过滤器调用一个异步函数来返回承诺,然后返回它的值。角度过滤器希望您返回一个可以轻松打印的值,例如字符串或数字。然而,在这种情况下,即使我们似乎返回了 getCorrespresponse,我们实际上返回了一个 新的承诺 - 返回值任何then()catch() 函数都是promise

Angular 正在尝试通过强制转换将一个 Promise 对象转换为一个字符串,但没有得到任何合理的回报并显示一个空字符串。


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

JSFiddle

html

<div ng-app="app" ng-controller="TestCtrl">
    <div>'WelcomeTo' | translate</div>
    <div>'GoodBye' | translate</div>
</div>

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;
);

每次 Angular 尝试执行过滤器时,它都会检查翻译是否已经被提取,如果没有,它会返回“正在加载...”值。我们还使用isWaiting 值来防止多次调用该服务。

上面的例子适用于 Angular 1.2,然而,在 Angular 1.3 的变化中,有一个性能改进,改变了过滤器的行为。以前,每个摘要周期都会调用过滤器函数。然而,从 1.3 开始,它仅在值更改时调用过滤器,在我们的上一个示例中,它永远不会再次调用过滤器 - 'WelcomeTo' 永远不会改变。

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

JSFiddle

myFilter.$stateful = true;

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

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;
);

【讨论】:

您的方法存在一个大问题,即如果您在 ng-repeat 中初始化过滤器 50 次,它将调用服务 50 次而不是调用一次,然后将信息解析为过滤器 @SimonPertersen 这不是应该发生的。当服务获取翻译时,isWaiting 将是 true,因此以下所有过滤器将简单地返回 Loading... @VitalyB,根据code.angularjs.org/1.3.7/docs/guide/filter#stateful-filters,强烈建议不要这样做。有什么办法可以做到这一点吗? @GouravGarg 我不熟悉过滤器的任何替代方案。您可以使用常规绑定和控制器来代替过滤器......但我认为这几乎是一样的。

以上是关于AngularJS:异步初始化过滤器的主要内容,如果未能解决你的问题,请参考以下文章

angularjs 路由 异步加载js

AngularJS复习-----内置过滤器和内置服务

从angularjs初始化materializecss下拉列表时出错

AngularJS 应用程序的引导/初始化流程

AngularJS 初始化

angularJS 初始化