AngularJS: Setting up filter asynchronously

I'm facing a challenge when trying to set up a filter that requires asynchronous data.

The filter's purpose is simple - it needs to convert paths to names, but this task involves a mapping array that must be fetched from the server.

While I could handle this within the filter definition before returning the function, the asynchronous nature of the data retrieval process complicates things

angular.module('angularApp').
  filter('pathToName', function(Service){
    // Perform some operations here

    return function(input){
      return input+'!'
    }
  }

It may be possible to utilize a promise, although my understanding of how Angular loads filters is not crystal clear. This post provides insights on achieving similar tasks with services, but can the same approach be applied to filters?

If anyone has alternative suggestions on how to translate these paths more effectively, I'm open to ideas.

EDIT:

I attempted the promise method, but something seems amiss and I'm struggling to pinpoint the issue:

angular.module('angularApp').filter('pathToName', function($q, Service){

  var deferred = $q.defer();
  var promise = deferred.promise;

  Service.getCorresp().then(function(success){
    deferred.resolve(success.data);
  }, function(error){
    deferred.reject();
  });

  return function(input){
    return promise.then(
      function(corresp){
        if(corresp.hasOwnProperty(input))
          return corresp[input];
        else
          return input;
      }
    )
  };
});

I have limited experience with promises; is this the correct way to implement them?

Answer №1

Provided below is a sample:

app.filter("testf", function($timeout) {
    var data = null, // DATA RECEIVED ASYNCHRONOUSLY AND CACHED HERE
        serviceInvoked = false;

    function realFilter(value) { // ACTUAL FILTER LOGIC
        return ...;
    }

    return function(value) { // FILTER WRAPPER TO HANDLE ASYNCHRONY
        if( data === null ) {
            if( !serviceInvoked ) {
                serviceInvoked = true;
                // CALL THE SERVICE THAT FETCHES THE DATA HERE
                callService.then(function(result) {
                    data = result;
                });
            }
            return "-"; // LOADING PLACEHOLDER, MAY BE EMPTY
        }
        else return realFilter(value);
    }
});

This demo showcases the implementation using timeouts instead of services.


UPDATE: To prevent multiple service calls, refer to the changes made to serviceInvoked in the code above and the respective fiddles. Also, view a variation with Angular 1.2.1 and a button for value alteration and trigger digest cycles: modified demo


UPDATE 2: In response to Miha Eržen's feedback, this approach does not function with Angular 1.3. However, a simple remedy involves utilizing the $stateful filter flag, outlined here under "Stateful filters," alongside the mandatory adjustments detailed in the corresponding altered demo.

Note that employing this method could impact performance since the filter is invoked during each digest cycle. The extent of performance decline may vary based on the specific scenario.

Answer №2

To understand why the original code is not functioning, let's break it down into simpler terms:

angular.module('angularApp').filter('pathToName', function(Service) {

    return function(input) {
        return Service.getCorresp().then(function(response) {
            return response;
        });
    });

}

The problem lies in the fact that the filter is calling an asynchronous function that returns a promise and then attempts to return its value. However, filters in Angular are designed to handle synchronous data types like strings or numbers for output. In this case, even though it appears that we are returning the response of getCorresp, we are actually returning a new promise - as the return value of any then() or catch() function is a promise.

Angular struggles to convert a promise object to a string, resulting in an empty display.


To resolve this issue, we need to modify the approach by initially returning a temporary string value and updating it asynchronously, as shown below:

JSFiddle

HTML:

<div ng-app="app" ng-controller="TestCtrl">
    <div>{{'WelcomeTo' | translate}}</div>
    <div>{{'GoodBye' | translate}}</div>
</div>

Javascript:

app.filter("translate", function($timeout, translationService) {

    var isWaiting = false;
    var translations = null;

    function myFilter(input) {

        var translationValue = "Loading...";
        if(translations)
        {
            translationValue = translations[input];
        } else {
            if(isWaiting === false) {
                isWaiting = true;
                translationService.getTranslation(input).then(function(translationData) {
                    console.log("GetTranslation done");
                    translations = translationData;
                    isWaiting = false;
                });
            }
        }

        return translationValue;
    };

    return myFilter;
});

Whenever the filter is triggered, Angular checks if the translations have been fetched already; otherwise, it displays "Loading...". Additionally, the isWaiting flag prevents redundant service calls.

In Angular 1.3, there was a performance enhancement altering how filters behave. Previously, the filter function would execute every digest cycle. Since 1.3, it triggers only when the value changes, potentially causing static values like 'WelcomeTo' to remain unchanged indefinitely.

To address this, simply add the following line to the filter:

JSFiddle

myFilter.$stateful = true;

During the troubleshooting process, I encountered another challenge wherein I needed a filter to retrieve asynchronous values that might change dynamically. Specifically, I aimed to fetch translations for a language that could be updated based on user interactions. Here's the revised code snippet:

JSFiddle

var app = angular.module("app",[]);
debugger;

app.controller("TestCtrl", function($scope, translationService) {
    $scope.changeLanguage = function() {
        translationService.currentLanguage = "ru";
    }
});

app.service("translationService", function($timeout) {
    var self = this;

    var translations = {"en": {"WelcomeTo": "Welcome!!", "GoodBye": "BYE"}, 
                        "ru": {"WelcomeTo": "POZHALUSTA!!", "GoodBye": "DOSVIDANYA"} };

    this.currentLanguage = "en";
    this.getTranslation = function(placeholder) {
        return $timeout(function() {
            return translations[self.currentLanguage][placeholder];
        }, 2000);
    }
})

app.filter("translate", function($timeout, translationService) {

    var translated = {};
    var isWaiting = false;

    myFilter.$stateful = true;
    function myFilter(input) {

        if(!translated[translationService.currentLanguage]) {
            translated[translationService.currentLanguage] = {}
        }

        var currentLanguageData = translated[translationService.currentLanguage];
        if(!currentLanguageData[input]) {
            currentLanguageData[input] = { translation: "", processing: false };
        }

        var translationData = currentLanguageData[input];
        if(!translationData.translation && translationData.processing === false)
        {
            translationData.processing = true;
            translationService.getTranslation(input).then(function(translation) {
                console.log("GetTranslation done");
                translationData.translation = translation;
                translationData.processing = false;
            });
        }

        var translation = translationData.translation;
        console.log("Translation for language: '" + translationService.currentLanguage + "'. translation = " + translation);
        return translation;
    };

    return myFilter;
});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Gradual disappearance of preloader as the page loads

I'm facing an issue with setting a gif as a website preloader. Despite trying different JavaScript solutions, the preloader remains visible even after the page has finished loading. $(window).on("load", function() { $(".loading").fadeOut("slow"); ...

In Vue JS, ensure that each item is loaded only after the previous item has finished loading

Is there a way to optimize the loading of around 1000 static images, .gifs, and videos for an online slideshow presentation? Currently, all items are loading simultaneously causing viewers to wait to see the first item. How can each item be loaded after th ...

Use jQuery's change method to initiate a hidden file input

Want to create a fake file input using an anchor tag and trigger the hidden file input with jQuery? Looking for some advice on how to make this happen. Check out my current attempt here. I'm not sure if I'm on the right track with this, so any g ...

Improve page loading speed by removing JavaScript and CSS that block rendering of above-the-fold content, specifically focusing on Angular JS

Currently, I am working on improving the page speed of my MEAN stack application. My main challenge lies in eliminating render-blocking Javascript and CSS to enhance the loading time. Despite making significant progress, I have hit a roadblock with the con ...

Having trouble with typecasting in Angular 9 after receiving an HTTP response?

When initializing my component, it fetches student information from an API. Here is the ngOnInit code for component-version1: ngOnInit(): void { if(!this.student) { this.studentsService.getStudentDetail(this.id).subscribe( (response: Stu ...

Best practices for uploading an array of objects (multiple image files) using Node.js

Encountering an issue in my application where I need to upload multiple photos. The object consists of motherPhoto, fatherPhoto, spousePhoto, and siblingsPhoto: { "mothername": "kerin", "motherPhoto": "C:/fakepath/mot ...

Accept only hexadecimal color codes as user input

How can I create a variable in JavaScript that only accepts color codes such as rgba, hashcode, and rgb? I need a solution specifically in javascript. ...

What is the best way to show an array object from PHP to AJAX in JavaScript?

I am facing an issue with a function that returns an array object from PHP using MySQL. I need to call this array in an AJAX function using JavaScript, but I am unsure of how to display the PHP array object in a dynamic table or console log. Here is my PH ...

Using AngularJS to add a third-party module called ngIdle

Below is a controller that I am working with: (function () { "use strict"; var app = angular.module('Demo') .controller('MainController', ['$rootScope', '$scope', '$location', 'curUser',MainC ...

Having trouble with JSONP cross-domain AJAX requests?

Despite reviewing numerous cross-domain ajax questions, I am still struggling to pinpoint the issue with my JSONP request. My goal is simple - to retrieve the contents of an external page cross domain using JSONP. However, Firefox continues to display the ...

Guide to making a JavaScript button that triggers an iframe to open upon user clicking the button

I'm currently enhancing the comment section for posts on my website. I am looking to implement a button in javascript that, when clicked, will open an iframe window displaying comments similar to how Facebook does on their post. If there are other lan ...

Is the this.props.onChange method called within the render function?

I'm having trouble grasping the concept of the assigned onChange property in this TextField component I found in the material-ui library: <TextField style = {{"padding":"10px","width":"100%"}} type = {'number'} valu ...

How to change a POST request to a PUT request using Express and keeping the

In my express app, I have set up two routes like this: router.post('/:date', (req, res) => { // if date exists, redirect to PUT // else add to database }) router.put('/:date', (req, res) => { // update date }) W ...

Is it necessary to compile Jade templates only once?

I'm new to exploring jade in conjunction with express.js and I'm on a quest to fully understand jade. Here's my query: Express mentions caching jade in production - but how exactly does this process unfold? Given that the output is continge ...

How can I retrieve the elements that have been removed using $pull in mongoose?

Currently, I am utilizing $pull to eliminate a subdocument from an array within a document. It may be pertinent to note that the subdocuments in my case contain _id and are therefore indexed. Here is the JSON schema description: user: { _id: Strin ...

Is there a way to update specific content within a view in express.js without having to re-render the entire view?

I'm in the process of creating a calendar that allows users to click on dates, triggering a popup window displaying events for that specific date. The challenge lies in not knowing which date the user will click on prior to rendering, making it diffic ...

Angular single page application with a sleek, user-friendly navigation bar for easy browsing

I have been attempting to solve the issue at hand without much success. As a newcomer to web development, I have been working on creating a basic app using angularjs and bootstrap. My pages are fairly sparse, consisting only of a few inputs and buttons t ...

Converting Apache configuration to Nginx configuration to prevent CORS issues

We have a unique setup in our local development environment where we utilize a client application built on Angular JS that connects to services located on various servers. To avoid CORS issues during development, we have configured Apache as a proxy with ...

Can Microsoft Edge Developer tool be used to incorporate external programs or code?

This image of msedge-devtools clearly demonstrates my point. I am interested in building a webpage parser using selenium and Microsoft Edge, beyond just JavaScript. I am seeking a way to utilize the Microsoft Edge developer tools (Inspect Element Mode) t ...

How to Send Data to ASP.NET MVC Controller Using AJAX Request with jQuery

I am trying to send parameters to a controller from jQuery, but I am struggling with it. The call works fine without parameters when the URL is just /SurveySection/EditLocalization. Shouldn't it be something like this: /SurveySection/EditLocalization? ...