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.

7
  • Post the code that you tried please Commented Nov 8, 2013 at 16:51
  • 4
    Filters fire a LOT! There must a more bandwidth friendly method of achieving your goal. Commented Nov 8, 2013 at 16:54
  • The fiddle link is not showing because it has a [2]: in front of it. And further, on SO, you can't post a link to jsfiddle.net without also adding some code to your question. Please add some code and make sure the link is correct. Thanks. Commented Nov 8, 2013 at 16:56
  • What's the end goal here? Why are you using $http in filters in the first place? Commented Nov 8, 2013 at 17:13
  • 1
    Yes sorry I fixed the link for the fiddle. Mike I agree with you but if I use the cache for my request I will do only one request and then get the data in the cache Commented Nov 8, 2013 at 17:14

3 Answers 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.

Sign up to request clarification or add additional context in comments.

2 Comments

Hey Florian. It is a good idea to make a directive the problem is that I use a request http for get the user so in my directive the scope companyId is undefined. I need to wait that the user is load but I don't know how... That make sense??
I just fix this issue using ng-if="user.company_id" like this angular execute the directive when the user is load.
1

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

1 Comment

this is work only in a view by mentioned construction like {{company_id | companyName}}, however use this custom filter in a controller? In controller construction like console.log('ctrl Filter:', $filter('companyName')($scope.company_id)); did not work! Maybe someone could fix it also for other use, not only in a view. best regards
1

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.