16

I would like to do a request to my backend in a filter and return the result of my request. The problem is the service $http return a promise and it's the issue.

For present the issue I used a $timeout and the promises of angular in my fiddle : my fiddle

In my filter I use a $timeout with a promise but the final goal is to use a request 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;
};

});

Then in my view I use my filter who is suppose to display "ca marche" with a delay of 2 secondes but that doesn't work :

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

You can see that the filter return nothing and that there is an infinite loop in the filter because of the null promise I think.

If you don't understand why I want use a request http in a filter the answer is simple. For exemple I have an object user with the fields : email,name,company_id.. And I have an other object company with the fields : name, createOn,... I would like use the filter like this for display the name of the user's company :

{{user.company_id | ShowNameCompany}}

So, I need to do a request http in the filter to my company controller of my backend.

I hope someone can help me.

4

3 回答 3

16

I think you should not use filters that way. Filters are for transforming inputs based on optional params.

The problem here would be that you're immediately returning a promise from the filter function. And that's nothing Angular can deal with as a result from a filter.

My suggestion therefore would be this - fetch the result first, work with the filter based on the result:

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

and in the Template:

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

Edit: Just read your explanation. To me, this would be a candidate for a directive:

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

and in the template:

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

If you have a lot of companies, you should preload the names (maybe send them with the first response initially?), as you'd be bombarding your server quite a bit with requests.

于 2013-11-08T17:29:17.393 回答
1

或者您可以使用有状态过滤器:

    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 将在每个摘要周期中调用。

此外,如果缓存变得太大,您需要找到一种重置缓存的方法。

见:https ://glebbahmutov.com/blog/async-angular-filter/

并且 plunker(上面的链接不会显示它,所以这里是直接链接):http ://plnkr.co/edit/EK2TYI1NZevojOFDpaOG?p=preview

于 2016-07-02T05:05:43.900 回答
0

如果您必须在过滤器中发出 http 请求,因为我最近也找到了一个原因,请按如下所示进行。

请注意,正如我从他们的文档中引用的那样,Angular 团队不鼓励使用有状态过滤器

强烈建议不要编写有状态的过滤器,因为 Angular 无法优化这些过滤器的执行,这通常会导致性能问题。只需将隐藏状态作为模型公开并将其转换为过滤器的参数,许多有状态过滤器就可以转换为无状态过滤器。

但是,如果您确实需要编写有状态过滤器,则必须将过滤器标记为 $stateful,这意味着它将在每个 $digest 周期内执行一次或多次。有状态过滤器

按照这个建议,你应该这样做:

  1. 声明你的过滤器,让它接受一个范围对象作为它的第一个参数: function filter($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);
    
  2. 定义一个在模板范围内可用的变量。我将调用我的 filterCache并为其分配和清空对象{}

  3. 从模板内部调用过滤器,如下所示:

    <div ng-bind-template="{{filterCache|myFilter:firstParameter:secondParameter}}"></div>
于 2016-12-05T05:57:47.717 回答