[SalesForce] Javascript Remoting (Return cached result without re-query)

I started writing some code using apex functions and then I thought let's try it out with Javascript remoting.

Remoting method was called from my VF page and all worked fine, until I tried to use local variable inside the remoting method. Here is my class:

global class MyClass
{
    public static List <Account> accounts {get; set;}

    public void init()
    {
        accounts = [SELECT Id, Name FROM Account]; 
    }

    @RemoteAction
    global static Account findAccount(String accountName)
    {
        for(Account acc : accounts)
        {
             if(acc.Name == accountName)
                 return acc;
        }

        return new Account();
    }
}

This is the code in my VF page:

var accountName = 'Test';

Visualforce.remoting.Manager.invokeAction(
    '{!$RemoteAction.MyClass.findAccount}',
    accountName,
    function(result, event)
    {
        if (event.status) 
        {
            console.log(result);
        }
        else if (event.type === 'exception')
        {
            alert('Error');
        }
        else
        {
            alert('Error');
        }
    }, 
    {escape: true}
);

When I debug the "accounts" in the remoting method, the list is empty. Is there anything that I'm missing here?

I have tried making the list static too and accessing it by referencing the class name but still no luck.

Please note that this is a simple example, the actual code is a bit
different and the init method is called from the VF page through the
action parameter of the page tag.

Appreciate your help!

Best Answer

There are a couple of things.

  1. In Apex, init() methods are not called by default. Any code you want to run during the instantiation of an Object must go in the constructor, see Using Constructors. Since JavaScript Remoting requires your methods to be Static, setting Instance Variables will not do you any good.
  2. Your static method, "findAccounts" is trying to reference an instance variable, "accounts". I got a compile error when initially trying to save your class. If you want to reference the "accounts" variable from within another static method, make sure to define this as a static list.
  3. Since init() is never getting called, the list of accounts will be null. If you call this init() method within the findAccounts method, then the data will be populated. Note, that you must change init() to be a static method then, as well. As referenced above, it's generally not a best practice to query all objects into memory, as you will soon hit governor limits for the total number of records retrieved by SOQL queries of 50k query rows.

If you put all that together, this should get you at least a working example.

global class MyClass
{
    public static List <Account> accounts = new List <Account>();

    public static void init()
    {
        accounts = [SELECT Id, Name FROM Account]; 
    }

    @RemoteAction
    global static Account findAccount(String accountName)
    {
        init();
        for(Account acc : accounts)
        {
             if(acc.Name == accountName)
                 return acc;
        }

        return new Account();
    }
}

Note, that this will infact requery the database everytime findAccounts is called. A design pattern I like to use, and have seen a lot looks something like the following:

public static List <Account> accounts;
public static void setAccounts()
{
    if (accounts == null){
        accounts = new List<Account>();
        accounts = [SELECT Id, Name FROM Account]; 
    }
}

This will ensure that the query is only run once within the lifespan of the request.

You also may want to utilize a Map<String, Account> accountNameToAccountMap see Apex Maps, so that you can avoid iterating through the list every single time. But, I'll leave that as an exercise.

Related Topic