web-dev-qa-db-ja.com

AngularJS:非同期でフィルターを初期化する

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

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

関数を返す前に、フィルター定義で何かを行うことができましたが、非同期の側面がそれを防ぎます

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

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

Promiseを使用することは実行可能かもしれませんが、angularがフィルターをロードする方法について明確な理解がありません。これは post です。サービスでそのような魔法を達成する方法を説明していますが、フィルターに対して同じことを行うことは可能ですか?

そして、もし誰かがそれらの道をどのように翻訳するかについてより良い考えを持っているなら、私はすべて耳にしています。

編集:

私は約束のアプローチを試してみましたが、何かが正しくなく、何を見ることができませんでした:

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

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

22
Davounet

次に例を示します。

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のコメントに従って、サービスを2回以上呼び出さないように特別な注意を払う必要があります。上記のコードのserviceInvokedの変更とフィドルを参照してください。 Angular 1.2.1と値を変更してダイジェストサイクルをトリガーするボタンを使用したフォークフィドルも参照: forked fiddle


編集2:MihaErženのコメントによると、このソリューションは、Angular 1.3。)の場合、Logner作業を行いません。ただし、このソリューションは、$statefulフィルターフラグ、「ステートフルフィルター」の下に記載されている here 、および必要な フォークされたフィドル

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

42

元のコードが機能しない理由を理解することから始めましょう。元の質問を少し簡略化して、わかりやすくしました。

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

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

}
_

基本的に、フィルターはpromiseを返す非同期関数を呼び出し、その値を返します。 angular=のフィルターは、文字列や数値など、簡単に出力できる値を返すことを期待しています。ただし、この場合、response of getCorrespactuallyreturn anew promise-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はフィルターを実行しようとするたびに、翻訳が既にフェッチされているかどうかをチェックし、そうでない場合は「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;
});
_
20
VitalyB