AngularJS:asynchronous初始化filter

我在尝试使用asynchronous数据初始化filter时遇到了问题。

filter非常简单,它需要将path转换为名称,但是为此需要一个对应数组,我需要从服务器获取数据。

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

angular.module('angularApp'). filter('pathToName', function(Service){ // Do some things here return function(input){ return input+'!' } } 

使用承诺可能是可行的,但我没有任何清楚的理解如何angular度加载filter。 这篇文章解释了如何通过服务实现这样的魔法,但是对于filter也可以这么做吗?

如果任何人对如何翻译这些path有更好的想法,那么我都是耳朵。

编辑:

我尝试着承诺,但是有些事情是不对的,我看不出什么:

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

我并不是很有承诺的家庭,是使用它们的正确方法吗?

这里是一个例子:

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

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


编辑:根据sgimeno的评论,必须额外注意不要多次调用服务。 请参阅serviceInvoked上面的代码和小提琴的更改。 请参阅使用Angular 1.2.1分叉提琴和更改值并触发摘要循环的button: 分叉小提琴


编辑2:根据MihaEržen的评论,这个解决scheme在Angular 1.3中没有logging器工作。 解决scheme几乎是微不足道的,使用$statefulfilter标志, 这里logging下“状态filter”,和必要的分叉小提琴 。

请注意 ,这个解决scheme会损害性能,因为filter被称为每个消化周期。 性能下降可能可以忽略不计,具体情况取决于具体情况。

让我们从理解为什么原始代码不起作用开始。 我已经简化了一些原来的问题,使之更加清晰:

 angular.module('angularApp').filter('pathToName', function(Service) { return function(input) { return Service.getCorresp().then(function(response) { return response; }); }); } 

基本上,filter会调用一个返回promise的asynchronous函数,然后返回它的值。 angular度filter期望您返回一个可以轻松打印的值,例如string或数字。 然而,在这种情况下,尽pipe看起来我们正在返回getCorrespresponse ,但实际上我们正在返回一个新的promise – 任何then()catch()函数的返回值都是一个承诺

Angular试图通过强制转换一个承诺对象到一个string,没有得到任何合理的回报,并显示一个空string。


所以我们需要做的是,返回一个临时的string值,并且像这样asynchronous地改变它:

的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尝试执行filter,它会检查翻译是否已被提取,如果不是,将返回“Loading …”值。 我们还使用isWaiting值来防止isWaiting调用服务。

上面的例子适用于Angular 1.2,然而,在Angular 1.3的变化中,有一个改进了filter行为的性能改进。 以前,每个摘要循环都会调用filter函数。 但是,从1.3开始,如果值被改变,它只会调用filter,在我们的最后一个示例中,它不会再次调用filter – 'WelcomeTo'永远不会改变。

幸运的是修复非常简单,你只需要添加到filter如下:

的jsfiddle

 myFilter.$stateful = true; 

最后,在处理这个问题的时候,我遇到了另一个问题 – 我需要使用一个filter来获取可能会改变的asynchronous值 – 具体来说,我需要为单一语言获取翻译,但是一旦用户改变了语言,我需要取一个新的语言集。 这样做,certificate有点棘手,虽然概念是相同的。 这是代码:

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