Skip to content

Orchestration

Azarattum edited this page Aug 20, 2021 · 6 revisions

Putting everything together.

All components (except for models) are individual independent parts of your application. To finally create a working app you need to connect them all to each other. This is done in app.ts file. Empty application with no components looks like this:

import Application from "../common/application.abstract";

/**
 * Application class
 */
export default class App extends Application {
	/**
	 * Application constructor
	 */
	public constructor() {
		super([
			///Put your components here
		]);
	}

	/**
	 * Initializes the app
	 */
	public async initialize(): Promise<void> {
		///Put components' configurations here
		///  .initialize([Component, arg1, arg2, arg3...], [Component2, ...])
		await super.initialize();
	}
}

Registering Components

Let's say we have an Exampler controller and an Example view. To put them in you need to import the classes then write the types in super constructor as an array:

import Application from "../common/application.abstract";
import Exampler from "./controllers/exampler.controller"; //Importing controller
import Example from "./views/expample/example.view";      //Importing view

export default class App extends Application {
	public constructor() {
		super([
			Exampler,
			Example
		]); //Constructing an app with 2 component types
	}
	...

Imports are usually automatically managed by your IDE, so you shouldn't worry about them in most cases.

Order of components is important! Components of the same type will initialize in the order you put them in. So if you need one to be initialized before another, you should register them appropriately.

Defining Configurations

Now let's assume we have some arguments in our controller's intialization:

import Controller from "../../common/controller.abstract";

export default class Exampler extends Controller<never>() {
	public initialize(api: string = "https://localhost"): void {
		///Some logic...
	}
}

Now we want to change them with the application's global configuration. To do that, we specify these arguments in the application's initialization function:

public async initialize(): Promise<void> {
	await super.initialize([Exampler, "https://example.com"]);
}

super.initialize takes any number of arguments, each of them is an array where the first element is a component type and the rest are initialization arguments. Initializing multiple components you might end up with something like this:

await super.initialize(
	[Exampler, "https://example.com"],
	[EpicController, 42, "epic"],
	[SomeService, {data: 4}],
	...
);

Application Options

There are two extra options you can provide on application construction:

  • scope defines the exposing scope
  • logging whether the app should log components' initialization to the console
public constructor() {
	super([], {
		scope: globalThis, 	//Defaults to globalThis
		logging: true		//Defaults to true
	});
}

Handlers

To define how components interact with each other you should use the events system. The most convenient way to register event listeners on each component is to use handlers:

@handle(Exampler)
public onExampler(self: Exampler): void {
	self.on("somethingHappened", (how: any) => {
		//Do something
	});
}

A handler function is called for each instance of a component after its creation but before initialization. To define a handle function you use @handle(<Component>) decorator on an application's method:

export default class App extends Application {
	public constructor() { ... }

	public async initialize(): Promise<void> { ... }

	@handle(Exampler)
	public onExampler(self: Exampler): void { ... }

	@handle(SomeService)
	public onSomeService(self: SomeService): void { ... }

	@handle(EpicController)
	public onEpicController(self: EpicController): void { ... }

	...
}

Handler receives a single argument of component's instance. It is called as many time as many instances were created.

You might want to use some helper methods to communicate an event from one component to another:

  • this.getComponent - Returns the first component instance of the given type (useful for singleton (aka null realtion) components like Views, Services or Non-Relational Controllers). The second optional parameter may also be specified to find a component with a specific relation.
  • this.getComponents - Returns an array of component instances of the given type
@handle(Exampler)
public onExampler(self: Exampler): void {
	self.on("clicked", () => {
		//Close the first `Example` view instance
		this.getComponent(Example).close();

		//Get the exampler controller which has 'specific_element' as its relation
		const realtion = document.getElementById("specific_element");
		const specificExampler = this.getComponent(Exampler, realtion);

		//Call show method on every instance of `Shower` controller
		this.getComponents(Shower).forEach(shower => {
			shower.show();
		})
	})
}

Finally, you do not have to use handlers only for registering event listeners. You can use them however you need as long as you understand how they work.