# Controllers

## Introduction

In EJS, a **controller** is a class responsible for providing action methods to execute specific functionality within a module. &#x20;

Acting as an interface to a module, controllers provide predefined functionality which may be called from the application.

## Controller classes

A **controller** is a class that resides in a module's `Controller` namespace and has its name suffixed with **Controller**.  Each controller class may optionally include a constructor which will be passed the active [module](/ejs/1.3/guide/modules.md) instance and the application's [file loader](/ejs/1.3/guide/file-handling/unified-file-handling.md).

In many cases, a module only needs a single controller.  However, for larger modules, it is a good idea to group related functionality into multiple controllers.

{% hint style="info" %}
Enforcing suffixes on class and method names helps to protect against routing to non-controller classes or methods, inadvertently or otherwise.
{% endhint %}

{% code title="MyFirstController.js" %}

```javascript
class MyFirstController 
{
    constructor(module, loader) {}
}
```

{% endcode %}

## Action methods

An **action method** is a method on a controller class with its name suffixed with **Action**.  The application will call these methods to interact with modules from the outside.  This ensures that a module can properly dictate how it should be used while preventing coupling to the application itself.

Action methods may optionally return a value.  If the returned value is a [component](/ejs/1.3/guide/modules/components.md), it will be inserted into the active layout.

### Asynchronous action methods

An **asynchronous action method** is a method that may not return immediately as it needs to wait for its processing to complete (e.g. waiting for a component to load).  Instead of returning a value directly, asynchronous action methods return a Promise that will resolve to the return value.

#### Example: Loading resources just-in-time

{% code title="MyFirstController.js" %}

```javascript
class MyFirstController
{
    constructor(module, loader) {
        this.module = module;
        this.loader = loader;
    }
    
    async indexAction() {
        // Get the path to this module's Component namespace. 
        let pathToComponent = this.module.map().componentPath();
        return this.loader.load(pathToComponents + '/IndexPage.vue');
    }
}
```

{% endcode %}

### Synchronous action methods

A **synchronous action method** is a method that does not contain any asynchronous processing, and optionally returns a value immediately.

Synchronous action methods are most useful for preloading related resources.

{% hint style="info" %}
Always prefer [asynchronous action methods](/ejs/1.3/guide/modules/controllers.md#asynchronous-action-methods).
{% endhint %}

#### Example: Preloading resources

In this example, `MyComponent` and its dependencies will be loaded recursively when `MyFirstController` is loaded.  When `mySyncMethodAction` is called, it can return MyComponent immediately as it is already loaded.

{% hint style="warning" %}
Preloading resources should be used sparingly and primarily for optimization when necessary.  Excessive preloading can cause performance issues resulting from network or memory limits.
{% endhint %}

```javascript
const MyComponent = use('Module/MyModule/Components/MyComponent.vue');

class MyFirstController
{
    mySyncMethodAction() {
        console.log('Running my action method!');
        return MyComponent;
    }
}
```

## Using an abstract controller

When designing your controllers, it's likely you'll want to share some common functionality across many or all of them.

EJS provides a simple abstract controller with some convenience methods:

* `ElentraJS/Controller/ControllerAbstract#respond(componentName)`\
  Accepts the filename of a component, relative to the module's `Component` namespace.  Returns a Promise that will resolve to the component when it is loaded.

{% hint style="info" %}
If you extend the EJS `ControllerAbstract` class instead of using it directly, you can limit coupling to a single point in your module or application.
{% endhint %}

#### Example: Use an abstract controller via composition

{% code title="MyFirstController.js" %}

```javascript
const ControllerAbstract = use('ElentraJS/Controller/ControllerAbstract');

class MyFirstController {
    constructor(module, loader) {
        this.controller = new ControllerAbstract(module, loader);
    }
    
    async indexAction() {
        return this.controller.respond('IndexPage.vue');
    }
}
```

{% endcode %}

#### Example: Use an abstract controller via inheritance

{% hint style="info" %}
Always prefer composition over inheritance as it removes the vertical coupling across classes, enhances flexibility, and simplifies maintenance.
{% endhint %}

{% code title="MyFirstController.js" %}

```javascript
const ControllerAbstract = use('ElentraJS/Controller/ControllerAbstract');

class MyFirstController extends ControllerAbstract {
    // Tip: You may omit this constructor if the super call is its only statement.
    constructor(module, loader) {
        super(module, loader);
    }
    
    async indexAction() {
        return this.respond('IndexPage.vue');
    }
}
```

{% endcode %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.elentra.org/ejs/1.3/guide/modules/controllers.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
