AngularJS:异步初始化过滤器
AngularJS:异步初始化过滤器
我正在尝试使用异步数据初始化一个过滤器。
这个过滤器非常简单,它需要将路径转换为名称,但是为了做到这一点,它需要一个相应的数组,我需要从服务器获取。
我可以在过滤器定义中做一些事情,在返回函数之前,但是异步方面阻碍了我。
angular.module('angularApp'). filter('pathToName', function(Service){ // Do some things here return function(input){ return input+'!' } }
使用一个promise可能是可行的,但我并不清楚angular如何加载过滤器。
这个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,使用它是正确的方法吗?
首先,让我们来理解原始代码为什么不起作用。我简化了原始问题,以使其更加清晰:
angular.module('angularApp').filter('pathToName', function(Service) { return function(input) { return Service.getCorresp().then(function(response) { return response; }); }); }
基本上,该过滤器调用了一个返回承诺的异步函数,然后返回其值。在 Angular 中,过滤器期望您返回一个可以轻松打印的值,例如字符串或数字。但是,在这种情况下,即使看起来我们正在返回 getCorresp
的 response
,我们实际上正在返回一个新承诺 - 任何 then()
或 catch()
函数的返回值都是 承诺。
Angular 试图通过转换将承诺对象转换为字符串,却没有得到有意义的返回值,因此显示为空字符串。
因此,我们需要返回一个临时的 字符串 值,并以异步方式进行更改,如下所示:
HTML:
{{'WelcomeTo' | translate}} {{'GoodBye' | translate}}
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 中的更改中,有一项性能改进改变了过滤器的行为。以前的过滤器函数在每个 digest 周期中都会被调用。但自 1.3 开始,只有在值更改时才调用过滤器,在我们的最后一个示例中,它将永远不会再次调用过滤器 - 'WelcomeTo'
永远不会更改。
幸运的是,修复非常简单,您只需要在过滤器中添加以下内容:
myFilter.$stateful = true;
最后,在处理此问题时,我遇到了另一个问题 - 我需要使用过滤器获取 可能会改变 的异步值 - 具体来说,我需要获取单个语言的翻译,但一旦用户更改了语言,我就需要获取新的语言集。尽管理念相同,但实现起来有些棘手。这是那段代码:
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; });
这里有一个示例:
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的评论,必须特别小心,不要调用超过一次的服务。请查看上面代码的serviceCalled
更改以及fiddles。还可以使用Angular 1.2.1的forked fiddle并添加按键以更改值并触发digest cycles:forked fiddle。
编辑2:根据Miha Eržen的评论,这个解决方案对于Angular 1.3不再可行。然而,解决方案几乎是微不足道的,只需要使用$stateful
过滤器标志,在这里的“Stateful filters”下有文档,以及必要的forked fiddle。
请注意,这个解决方案会影响性能,因为每个digest cycle都会调用过滤器。性能下降可能可以忽略或不可忽略,具体情况取决于特定的情况。