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>