23

非同期データでフィルターを初期化しようとすると問題が発生します。

フィルターは非常に単純で、パスを名前に変換する必要がありますが、そのためには、サーバーから取得する必要がある対応配列が必要です。

関数を返す前にフィルター定義で何かを行うことはできますが、非同期の側面によりそれが妨げられます

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

    return function(input){
      return input+'!'
    }
  }

プロミスを使用することは実行可能かもしれませんが、角度がフィルターをロードする方法について明確に理解していません。この投稿では、サービスでそのような魔法を実現する方法を説明していますが、フィルターでも同じことができるのでしょうか?

そして、これらのパスを翻訳する方法について誰かがより良いアイデアを持っているなら、私はすべて耳を傾けます.

編集:

私は約束のアプローチを試みましたが、何かが正しくなく、何がわからないのですか:

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

私は約束にあまり慣れていませんが、それは正しい使用方法ですか?

4

2 に答える 2

43

次に例を示します。

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 の forked fiddle と、値を変更してダイジェスト サイクルをトリガーするボタンも参照してください: forked fiddle


編集 2: Miha Eržen のコメントによると、このソリューションは Angular 1.3 のロガー作業を行いません。ただし、この解決策は、「ステートフル フィルター」の下に記載されているフィルター フラグと、必要なフォーク$statefulされたfiddleを使用することで、ほとんど簡単です。

フィルタは各ダイジェスト サイクルと呼ばれるため、このソリューションではパフォーマンスが低下することに注意してください。パフォーマンスの低下は、特定のケースに応じて、無視できる場合もあれば、無視できる場合もあります。

于 2013-09-27T13:11:32.700 に答える
20

元のコードが機能しない理由を理解することから始めましょう。より明確にするために、元の質問を少し単純化しました。

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

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

}

基本的に、フィルターは promise を返す async 関数を呼び出してから、その値を返します。angular のフィルターは、文字列や数値など、簡単に印刷できる値を返すことを期待しています。responseただし、この場合、 ofgetCorrespを返しているように見えますが、実際には新しい promise をthen()返しています。いずれかまたはcatch()関数の戻り値はpromiseです。

Angular は、キャストによって promise オブジェクトを文字列に変換しようとしていますが、見返りとして何も得られず、空の文字列が表示されます。


したがって、次のように、一時的な文字列値を返し、非同期に変更する必要があります。

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 がフィルターを実行しようとするたびに、翻訳が既にフェッチされているかどうかを確認し、そうでない場合は "Loading..." 値を返します。また、この値を使用して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;
});
于 2015-01-04T22:53:32.583 に答える