Components
The fundamental building blocks of the framework are components. These are very similar to components in other frameworks like React and Vue. They should represent a small portion of your UI and should be composed together to create your application.
If you haven't worked with components before, then a good place to start reading up on how we view components is the following article:
https://reactjs.org/docs/thinking-in-react.html
Our components are very similar to React components and therefore the same theory can be applied when designing applications. With that in mind, a simple component will look like:
import { component, BaseComponent, JSXElement } from "@commontimeltd/infinity-framework";
import { MyAppManager } from "client/manager";
@component({ tag: "wc-my-first-component", styles: [ require("./my-first-component.scss") ] })
export class MyFirstComponent extends BaseComponent<MyAppManager> {
async _init(): Promise<void> {}
_setupEventListeners(): void {}
componentMarkup(): JSXElement {
return <p>This is my first component</p>;
}
}
Lets break this down to explain everything.
Component Registration
@component({ tag: "wc-my-first-component", styles: [ require("./my-first-component.scss") ] })
The first line is decorator on our web component class
. If you've not seen decorators before, then here is some information to read up on. Our decorator is used to tell the framework that this class is a component.
Under the hood, our components are simply web components. Web components require a small amount of setup / registration which is handled by the decorator. The decorator is simply a function which takes one argument of an object
with the following properties:
tag
(string): This must be a hypen delimited string that becomes the DOM html node name. Valid examples are:wc-my-first-component
,my-first-component
. Invalid examples areWcMyFirstComponent
orFirstComponent
.styles
(string[]): A string that encapsulates all the CSS rules for the component. The framework is designed to userequire("./my-first-component.scss")
to import style rules from another file. You can have multiplerequire
s within the the array to import multiple stylesheet files, e.g.
@component({
tag: "wc-my-first-component",
styles: [
require("./my-first-component-stylesheet-1.scss"),
require("./my-first-component-stylesheet-2.scss"),
require("./my-first-component-stylesheet-3.scss")
]
})
Class Definition
The next line is where we define our class:
export class MyFirstComponent extends BaseComponent<MyAppManager>
There isn't anything too interesting here other than we need to give our component class a name and ensure we are extending from BaseComponent<T>
. In this instance T
is a generic in Typescript which should be the type for the manager
in your application.
We'll go into more detail about this a little later.
Note
Generally, you should not extend from another class for a component. If you need to do something where your first instinct is inheritance, then be sure to read Component Composition first.
Component Initialisation
When a component is first loaded it goes through an initlisation period. This is where the component will call certain methods to set itself up and is a handy place to do stuff like bind events and load initial state (including via HTTP requests, etc).
To do this, there is a handy little method:
async _init(): Promise<void> {}
This is called fairly early in the component lifecycle and is an ideal place to set your component up. For example, lets say we need to bind some state to the component which needs to display as soon as the component can. We could then use this:
async _init(): Promise<void> {
this.myState = "Let's set some state";
}
This method will widely be used throughout your application for tasks such as this. You may have noticed that the method is also marked as async
. Therefore, you can also use await
inside your _init
for async loading of state, e.g.
async _init(): Promise<void> {
this.myState = await this.manager.rest.getSomeDataFromAnApi();
}
Event Listeners
The framework comes with an internal event model (more details here). Components have a dedicated method to setup any listeners:
_setupEventListeners(): void {}
This is called before _init
but is designed to be very quick (therefore, you should not be setting state or do anything complicated in this method). It's a quick, generic way to set some event listeners up like so:
_setupEventListeners(): void {
this.manager.on("my-first-event", () => alert("My First Event Fired!"));
this.manager.on("my-second-event", () => alert("My Second Event Fired!"));
}
Component Markup
The last standard part of a component is the componentMarkup
function. This is where you use JSX to describe how your component looks. It's very similar to HTML but it is in Javascript and will get transpiled into a function call that is responsible for creating the approproate DOM element.
Our example:
componentMarkup(): JSXElement {
return <p>This is my first component</p>;
}
This is the simple component that will only render a paragraph tag. You can use almost any HTML tag inside the JSX markup. Most components will follow a standard rule of returning only one node with everything else encapsulated. For example:
componentMarkup(): JSXElement {
return (
<div>
<p>This is my first component</p>
</div>
);
}
You can also use any references to the class within your JSX like so:
// Somewhere in _init:
this.name = "Nick";
//... in the JSX:
<p>Hello, { this.name }</p>
The curly braces allows you to call out to your code which includes the ability to call functions. This is useful when looping:
<ul>
{
[1, 2, 3].map((x, idx) => <li>{ idx } index</li>)
}
</ul>
Finally, you can also use other components in your JSX:
<div>
<p>Hello</p>
<MySecondComponent />
</div>
The differnce here is that MySecondComponent
starts with a capital letter. By doing this, the internal JSX resolver knows that this is a class defined elsewhere. When it's a small letter, it looks for a HTML node and will complain if it can't find one.
This is a very small snippet of what's possible with JSX, you read more here.
Components Wrap Up
That's it for the basics of components. The next step is start exploring component properties and see how we can turn components into powerful blocks of functionality.