Skip to content

Setting up a new component

Jan Pánek edited this page Aug 19, 2021 · 9 revisions

In this section, we will go through the process of creating and setting up a new component.

Creating the logic

The first step of creting a new component is creating a new typescript class that will handle the base operations and logic and will be placed in src/models folder.

In this example, we will go through creation of the "HTTP Server" component. Lets start with the class and contructor.

The WebServer.ts file:

export class WebServer extends EndpointOperator implements IDataOperator { // Webserver is working with endpoints -> extend EndpointOperator
    inputPort: Port;
    options: WebServerOptions;
    connectionTable: { [id:string]: Connection } = {};

    constructor() {
        super();
        this.inputPort = new Port(this, false, true); // pass in (parent, isOutput, hasMultipleConnections)
        this.options = new WebServerOptions();  // Create new options
        this.options.title = "Web Server";
       
        this.options.endpoints = [
            new Endpoint("/",[HTTPMethod.GET])  // Create one default endpoint   
        ]
    }
...

This is the basic setup of properties and constructor. WebServer has connectable endpoint, so it must extend the EndpointOperator class. We are then creating the input port and passing some parameters, the port class looks like this:

export class Port {
    constructor(parent: IDataOperator, // Parent object of the port
                isOutput: boolean = false, // Whether the port is output or input
                hasMultipleConnections: boolean = false) { // Whether the port can take multiple connections or only one
...

The next will be the receiveData method that is implemented from IDataOperator, it takes object of type RequestData as a parameter. The RequestData class contains informations needed for sending and receiving data.

   async receiveData(data: RequestData) {
        let targetEndpoint = this.getTargetEndpoint(data); // Get the endpoint that the request is meant to hit
        if(targetEndpoint == null)
            return;

        this.connectionTable[data.requestId] = data.origin; // Assign requestId to connection which it came from
        this.fireReceiveData(data); // Fire the receiveData event

        await this.sendData(this.getResponse(data)); // Return response to given request
    }

That was the process of receiving data througt the input port. At the end, we call sendData method, that is also implemented via the IDataOperator. In this method, we take in the response and obtain the connection to which to send it. We then use the input port to return the response.

    async sendData(response: RequestData) {
        let targetConnection = this.connectionTable[response.responseId] // Get the connection
        if(targetConnection == null)
            throw new Error("Target connection can not be null"); // The connection can not be null 
        this.connectionTable[response.responseId] = null; // Reset request id
        await this.inputPort.sendData(response, targetConnection); // Send response to given connection
    }

That was the core of every component. The WebServer class doesn't have any special options, but you can create options which can decide how and to who data will be sent etc.

There are more methods implemented from interface, it is getAvailableEndpoints that return endpoints that are available for connection, and destroy method, that says what will happen on when the component is destroyed

    getAvailableEndpoints(): Endpoint[]{
        return this.options.endpoints; // Return endpoints stored in options
    }
    destroy(){
        this.inputPort.removeConnections(); // Disconnect connections on destroy
    }

Creating the UI

As said in previous section, Angular component consists of 3 files, the HTML template, TypeScript class and Scss styles

HTML Template

The first element to create is the main component div. It has a template with #conn referce to dynamically craete elements like title and ports. Inside of it is also an image container. Events are binded to image container since it's the area that is visible to user.

The webserver.component.html file:

<div #anchorRef
[style.left.px]="LogicWebServer.options.X"
[style.top.px]="LogicWebServer.options.Y">
    <template #conn></template>               <!-- Template for inserting dynamically created components-->
    <div class="img-container" (mousedown)="handleMousedown( $event )"       <!-- Handle event for mousedown -->
                               (click)="handleClick()">                      <!-- Handle event for click -->
        <img src="./assets/webserver.svg">
    </div>  
</div>

Every component can also have an options and actions section. Since our example is a static HTTP web server we will add only one option, that is endpoint which's route is by default set to "/".

<div style="display:none">  <!-- Dont display the element, we are only interested in the #options reference -->
    <div #options>
        <div class="options-endpoints-container">
            <div class="options-endpoints-top">
                <span class="property-name">Endpoints:</span>
            </div>
            <div class="options-endpoints">
                <div class="options-endpoint" *ngFor="let endpoint of this.LogicWebServer.options.endpoints">
                    <mat-expansion-panel>           <!-- Using material components -->
                        <mat-expansion-panel-header>
                            <mat-panel-title>
                                <input disabled class="endpoint-url" [(ngModel)]="endpoint.url" >   <!-- Double bind the url of endpoint -->
                            </mat-panel-title>
                        </mat-expansion-panel-header>
                        <div class="options-endpoint-menu-top">
                            <mat-form-field class="endpoint-method" appearance="fill">
                                <mat-select disabled [(ngModel)]="endpoint.supportedMethods" multiple>   <!-- Double bind supported methods -->
                                    <!-- Iterate through HTTPMethodKeys list that contains values of HTTPMethod enum -->
                                    <mat-option *ngFor="let method of HTTPMethodKeys" [value]="method">{{ HTTPMethod[method] }}</mat-option> 
                                </mat-select>
                            </mat-form-field>
                        </div>
                    </mat-expansion-panel>
                </div>
            </div>
        </div>
    </div>
</div>

Typescript class

Every class of a component should extend the OperatorComponent class. It takes care of some function that are common across all components and also provides properties that can be accessed in template.

The webserver.component.ts file:

@Component({
	selector: 'webserver',
	templateUrl: './webserver.component.html',
	styleUrls: ['./webserver.component.scss']
})
export class WebserverComponent extends OperatorComponent implements OnInit {

	public LogicWebServer : WebServer = new WebServer(); // This is the property that holds the logic WebServer
...

We also need to setup few more functions, that is getColor, that will return the color of the component, and getLogicComponent that will return the logic WebSever,

	public getLogicComponent(){
		return this.LogicWebServer; // Return the logic component
	}
        public static getColor(){
		return new WebServer().color; // Return the color of WebServer
        }

Systemizer components use manual change detection via ChangeDetectorRef.detectChanges(). When a component is selected, the change detection turns to automatic. If you want to use data binding on a component outside of its selection, keep that in mind!

Scss styles

If you want to add styles to your component, you can add them to the file dedicated only for given Angular component. These are the basic things that should be in your styles file for a new component:

:host{
    position: relative;
}

After that, you can add anything you want.

Add component to menu

The last thing is to add the component where needed, the first place is the ComponentMenuComponent that is in /src/board/componentmenu.

constructor(private placingService: PlacingService) 
{
    ...
    this.allCategories.push(new Category("Server-side",[
        // MenuItem takes in the Angular component class, title of the component, small tag, image path, html template for description (few sentences about the component)
	new MenuItem(WebserverComponent, "Static HTTP Web Server", "HTTP","./assets/webserver.svg", '<p>Web server component simulates static HTTP web server with one default endpoint.</p><p>Use the input port to receive data and send response back.</p>')
		]));
    ...

Next, you need to include the component in methods that save/load components

In /board/board.component.ts:

componentTypes = { 
	...
	WebServer: WebserverComponent
        ...
}

And in /app/saving.service.ts:

import { WebServer } from 'src/models/WebServer'
...
types = {
	...
        WebServer
        ...
}

That's all what's needed to create a new component. The next section talks about services in Systemizer.

Clone this wiki locally