AngularJS: Как использовать $ http в фильтре

Я хотел бы сделать запрос к моему серверу в фильтре и вернуть результат моего запроса. Проблема заключается в том, что сервис $http возвращает обещание, и это проблема.

В качестве примера я использовал $timeout и promises of angular в моей скрипке: моя скрипка

В моем фильтре я использую $timeout с обещанием, но конечной целью является использование запроса http:

myApp.filter('filterHello', function ($http,$timeout,$q) {
return function (company_id) {
    console.log("in the filter");
    var deferred = $q.defer();   
    $timeout(function() {
        deferred.resolve("ca marche");
    }, 2000);                  
    return deferred.promise;
};

});

Тогда, на мой взгляд, я использую свой фильтр, который предположительно отображает "ca marche" с задержкой в ​​2 секунды, но это не работает:

<div ng-controller="MyCtrl">
   {{hello|filterHello}}
</div>

Вы можете видеть, что фильтр ничего не возвращает и что в фильтре существует бесконечный цикл из-за нулевого обещания, которое я думаю.

Если вы не понимаете, почему я хочу использовать запрос http в фильтре, ответ прост. Например, у меня есть пользователь объекта с полями: email, name, company_id.. И у меня есть другая компания-объект с полями: name, createOn,... Я хотел бы использовать фильтр, подобный этому, для отображения имени компании-пользователя:

{{user.company_id | ShowNameCompany}}

Итак, мне нужно сделать запрос http в фильтре моему контроллеру моей бэкэнда.

Надеюсь, кто-то может мне помочь.

Ответы

Ответ 1

Я думаю, что вы не должны использовать фильтры таким образом. Фильтры предназначены для преобразования входов на основе дополнительных параметров.

Проблема в том, что вы сразу же возвращаете обещание от функции фильтра. И что ничто Angular не может иметь дело с фильтром.

Поэтому мое предложение было бы таким: сначала получить результат, работать с фильтром на основе результата:

var app = angular.module("my.module");

app.controller("MyCtrl", ['$http', '$scope', function(http, scope) {
  scope.hello = "foo";
  http.get('http://my.service.com').then(function(data) {
    scope.filterParams = data;
  }, function(err) {
    scope.filterParams = undefined;
  });
}]);

app.filter("filterHello", function() {
  return function(input, params) {
    if(typeof params === "undefined") {
      return "";
    }
    //work with the params here
  };
});

и в Шаблоне:

<div ng-controller="MyCtrl">
  {{hello|filterHello:filterParams}}
</div>

Изменить: просто прочитайте свое объяснение. Для меня это будет кандидатом на директиву:

app.directive("companyName", ['$http', function(http) {
  return {
    template: "<span>{{name}}</span>",
    scope: {
      companyId: "="
    },
    link: function(scope) {
      http.get("http://my.service.com/companies/" + scope.id).then(function(result) {
        scope.name = result.name;
      }, function(err) {
        scope.name = "unknown";
      });
    }
  }
}]);

и в шаблоне:

<span company-name company-id="user.company_id"></span>

Если у вас много компаний, вы должны предварительно загружать имена (возможно, сначала отправить их с первым ответом?), так как вы будете слишком часто обманывать свой сервер запросами.

Ответ 2

Или вы можете использовать фильтр с сохранением состояния:

    angular.module("app").filter('companyName', function($http) {
        var cached = {};
        var apiUrl = 'http://my.service.com';
        function companyNameFilter(company_id) {
            if (company_id) {
                if (company_id in cached) {
                    // avoid returning a promise!
                    return typeof cached[company_id].then !== 'function' ?
                        cached[company_id] : undefined;
                } else {
                    cached[company_id] = $http({
                        method: 'GET',
                        url: apiUrl + company_id
                    }).success(function (companyName) {
                            cached[company_id] = companyName;
                        });
                }
            }
        }
        companyNameFilter.$stateful = true;
        return companyNameFilter;
})

и используйте его так: {{company_id | companyName}}

Остерегайтесь: функция companyNameFilter будет вызываться в каждый цикл дайджест.

Кроме того, вам нужно будет выяснить способ reset кеша, если он станет слишком большим.

Смотрите: https://glebbahmutov.com/blog/async-angular-filter/

И плункер (ссылка выше не отобразит его, так что здесь прямая ссылка): http://plnkr.co/edit/EK2TYI1NZevojOFDpaOG?p=preview

Ответ 3

Если вы должны сделать HTTP-запрос в фильтре, поскольку я также нашел причину в последнее время, сделайте это, как показано ниже.

Обратите внимание, что команда Angular не рекомендует использовать фильтры состояния, как я цитирую из их документации

Настоятельно не рекомендуется записывать фильтры, которые являются работоспособными, поскольку их выполнение невозможно оптимизировать с помощью Angular, что часто приводит к проблемам с производительностью. Многие фильтры состояния могут быть преобразовывается в фильтры без сохранения состояния, выставляя скрытое состояние как модель и превращая ее в аргумент для фильтра.

Если вам нужно написать фильтр с сохранением состояния, вы должны отметить фильтр как $stateful, что означает, что он будет выполнен один или больше раз в течение каждого цикла $digest. Фильтры с полномочиями

Следуя этой рекомендации, вот что вам следует сделать:

  • Объявите свой фильтр так, чтобы он принял объект области видимости в качестве первого параметра: ($ http) {

    return function (cached, type, productField) {
            var key = type + '-' + productField;
            var oDataFilter = 'Id eq \'' + productField + '\'';
            var select = 'Id,Name';
            if (typeof cached[key] == 'undefined') {
                $http({
                    url: '//<endpoint>/resource.json?$select=' + select + '&$filter=' + oDataFilter 
                    , method: 'GET'
                    , headers: {
                        Accept: 'application/json'
                    }
                }).then(function (data) {
                    cached[key] = data.data.d.length > 0 ? data.data.d[0].Name : 'Not found';
                });
                cached[key] = 'Loading...';
            }
            return cached[key];
        }
    }
    filter.$inject = ['$http'];
    app.filter('myFilter', filter);
    
  • Определите переменную, которая будет доступна в рамках вашего шаблона. Я назову свой filterCache и присваиваю и пустую объект {}.

  • Вызвать фильтр изнутри вашего шаблона следующим образом:

    <div ng-bind-template="{{filterCache|myFilter:firstParameter:secondParameter}}"></div>