AngularJS : How use $http in a filter

前端 未结 3 1192
南笙
南笙 2020-12-18 20:51

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.

Fo

相关标签:
3条回答
  • 2020-12-18 21:28

    If you must make an http request within a filter as I also found a reason to recently, then do it as shown below.

    Note that the Angular team discourages the use of stateful filters as I quote from their documentation

    It is strongly discouraged to write filters that are stateful, because the execution of those can't be optimized by Angular, which often leads to performance issues. Many stateful filters can be converted into stateless filters just by exposing the hidden state as a model and turning it into an argument for the filter.

    If you however do need to write a stateful filter, you have to mark the filter as $stateful, which means that it will be executed one or more times during the each $digest cycle.Stateful filters

    Going by this recommendation, here is what you should do:

    1. Declare your filter so that it accepts a scope object as it's first parameter: 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. Define a variable that will be available within the scope of your template. I'll call mine filterCache and assign and empty object {} to it.

    3. Invoke the filter from inside of your template like this:

      <div ng-bind-template="{{filterCache|myFilter:firstParameter:secondParameter}}"></div>
    0 讨论(0)
  • 2020-12-18 21:36

    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.

    0 讨论(0)
  • 2020-12-18 21:49

    Or you could use a stateful filter:

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

    and use it like so: {{company_id | companyName}}

    Beware: The function companyNameFilter will be called on each digest cycle.

    Also, you would need to figure out a way to reset the cache if it grows too big.

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

    And the plunker (the link above won't display it, so here's a direct link): http://plnkr.co/edit/EK2TYI1NZevojOFDpaOG?p=preview

    0 讨论(0)
提交回复
热议问题