Controller
This document provides an in-depth explanation of the controller abstractions, which include the Controller interface and the BaseController abstract class. These components are essential for managing the logic and behavior of specific parts of an application.
Table of Contents
Overview
The controller abstractions are designed to facilitate the creation of controllers in an application. Controllers are responsible for:
- Managing application logic.
- Coordinating updates to the user interface (UI).
- Handling events between
modelsandviews.
Key Components
- Controller Interface: Defines the structure for creating controllers.
- BaseController Abstract Class: Provides a foundation for implementing controllers with default behavior.
Controller Interface
The Controller interface defines the contract for any controller implementation. It ensures that all controllers provide methods for:
- Registering update callbacks.
- Executing the main logic.
Methods
onUpdate(callback: (element: HTMLElement) => void): void
- Description: Registers a callback function to handle updates to the controller's rendered element.
- Parameters:
callback: A function that receives anHTMLElementto be rendered or updated.
- Usage:typescript
const controller = new MyController(model, view); controller.onUpdate((element) => { document.body.innerHTML = ''; document.body.appendChild(element); });
run(): void
Description: Executes the main logic of the controller. This method must be implemented by concrete classes to define specific behavior.
Usage:
typescriptconst controller = new MyController(model, view); controller.onUpdate((element) => { document.body.innerHTML = ''; document.body.appendChild(element); }); controller.run();
BaseController Abstract Class
The BaseController class provides a default implementation of the Controller interface. It simplifies the process of creating controllers by handling common functionality.
Properties
protected render: (element: HTMLElement) => void
Description: A function used to render or update the controller's element. This is set via the
onUpdatemethod.Usage:
typescriptclass MyController extends BaseController { // ... // The render method is commonly used in the run method implementation public run(): void { this.model.fetchData().then((data) => { const element = this.view.render(data); this.render(element); }); } }
Methods
onUpdate(callback: (element: HTMLElement) => void): void
- Description: Registers a callback function to handle updates to the controller's rendered element.
- Parameters:
callback: A function that receives anHTMLElementto be rendered or updated.
abstract run(): void
- Description: Executes the main logic of the controller. Subclasses must implement this method to define specific behavior.
Example Usage
Below is an example of how to create a custom controller by extending the BaseController class.
import { MyView } from './my-view';
import { MyModel } from './my-model';
class MyController extends BaseController {
private async handleClick() {
const { name } = await this.model.fetchData();
const message = this.model.handle('greet', name);
alert(message);
}
public constructor(private model: MyModel, private view: MyView) {
super();
this.view.onEvent('click', () => this.handleClick());
}
public run(): void {
this.model.fetchData().then((data) => {
const element = this.view.render(data);
this.render(element);
});
}
}
// Usage
const model = new MyModel();
const view = new MyView();
const controller = new MyController(model, view);
controller.onUpdate((element) => {
document.body.innerHTML = '';
document.body.appendChild(element);
});
controller.run();Key Benefits
- Encapsulation: Controllers encapsulate logic, making the application easier to maintain.
- Reusability: The
BaseControllerclass provides reusable functionality, reducing boilerplate code. - Flexibility: The
Controllerinterface allows for custom implementations tailored to specific needs.
Use interfaces in the controller for better abstraction
By depending on interfaces for models and views, controllers can support multiple views, making them interchangeable. Below is an example:
import type { View, Model } from './core';
import { MyModel } from './my-model';
import { MyView1, MyView2 } from './my-view';
type MyModelLike = Model<
{ id: number; name: string },
{ greet: (name: string) => string }
>;
type MyViewLike = View<{ name: string }, { click: () => void }>;
class MyController extends BaseController {
private async handleClick() {
const { name } = await this.model.fetchData();
const message = this.model.handle('greet', name);
alert(message);
}
public constructor(private model: MyModelLike, private view: MyViewLike) {
super();
this.view.onEvent('click', () => this.handleClick());
}
public run(): void {
this.model.fetchData().then((data) => {
const element = this.view.render(data);
this.render(element);
});
}
}
const model = new MyModel();
const view1 = new MyView1();
const view2 = new MyView2();
const controller = new MyController(model, view1);
controller.onUpdate((element) => {
document.body.innerHTML = '';
document.body.appendChild(element);
});
controller.run();This approach enhances flexibility by allowing the controller to work with any view or model that adheres to the defined interfaces. Now if you want to change the view now only pass it other in the constructor:
const view1 = new MyView1();
const view2 = new MyView2();
const controller = new MyController(model, view1);
const controller = new MyController(model, view2);