Services

In this guide we'll introduce a concept for service layer and how to implement it with TypeScript.

This is a general full stack concept which can be used for any type of software -- e.g. frontend, backend, Nginx's NJS or PostgreSQL's plv8.

What's service class?

Service is like a utility class, but it can also have internal state as a private or protected static member.

Service should also implement the Observable to notify when the internal state changes, if possible.

Code Example

In its core a service class is simply a collection of public functions in a namespace which shares common context of purpose and state.

import Observer, { ObserverCallback, ObserverDestructor } from "./fi/hg/core/Observer";

export enum InternalValueServiceEvent {
    VALUE_CHANGED= "InternalValueService:valueChanged"
}

export type InternalValueServiceDestructor = ObserverDestructor;

export class InternalValueService {

    private static _value : string = '';

    private static _observer: Observer<InternalValueServiceEvent> = new Observer<InternalValueServiceEvent>(
        "InternalValueService");

    public static getValue () : string {
        return this._value;
    }

    public static setValue (value: string) : void {
        this._value = value;
    }

    public static Event = InternalValueServiceEvent;

    public static on (
        name: InternalValueServiceEvent,
        callback: ObserverCallback<InternalValueServiceEvent>
    ): InternalValueServiceDestructor {
        return this._observer.listenEvent(name, callback);
    }

    public static destroy (): void {
        this._observer.destroy();
    }

}

export default InternalValueService;

Usage in general

InternalValueService.setValue('Hello World');

console.log( InternalValueService.getValue() );

Usage inside a ReactJS function

export function Foo () {

    const [value, setValue] = useState<string>(InternalValueService.getValue());

    useEffect( () => {
        return InternalValueService.on(InternalValueServiceEvent.VALUE_CHANGED, () => {
            setValue( InternalValueService.getValue() );
        });
    });

    function onClickCallback (e: React.MouseEvent<HTMLButtonElement>) {
        InternalValueServiceEvent.setValue('Hello world');
    }

    return <button onClick={onClickCallback}>{value}</>;

}

Usage inside a ReactJS component

While the component style if much more verbose, that's not necessary a bad thing at all.

export interface FooProps {

}

export interface FooState {
    readonly value: string;
}

export class Foo extends React.Component<FooProps, FooState> {

    private _changeListener            : ObserverDestructor | undefined;
    private readonly _onClickCallback  : VoidCallback;
    private readonly _onChangeCallback : VoidCallback;

    public constructor () {
        super();
        this._changeListener = undefined;
        this._onClickCallback = this._onClick.bind(this);
        this._onChangeCallback = this._onChange.bind(this);
    }

    public componentDidMount () {
        this._changeListener = InternalValueService.on(
            InternalValueServiceEvent.VALUE_CHANGED, 
            this._onChangeCallback
        );
    }

    public componentWillUnmount () {
        if (this._changeListener) {
            this._changeListener();
            this._changeListener = undefined;
        }
    }

    public render () {
        return <button onClick={this._onClickCallback}>{this.state.value}</>;
    }

    private _onChange () {
        this.setState({value: InternalValueService.getValue()});         
    }

    private _onClick (e: React.MouseEvent<HTMLButtonElement>) {
        if(e) {
            e.preventDefault();
            e.stopPropagation();
        }
        InternalValueServiceEvent.setValue('Hello world');
    }

}

Filesystem hierarchy

You should place services under the services and name them FooService.

Sometimes even beside the code that uses them, if that's the only place where it should be used.

Explanations