Reddy
Reddy

Reputation: 1453

Angular 2: How to pass service config before it get created?

I have typical use-case where I have to use different end-point websocket URL to connect to different backend services.

I have written generic service in Angular 2 see below:

import {Injectable} from "@angular/core";
import {Subject} from "rxjs/subject";
import {Observable} from "rxjs/observable";
import {WebSocketSubject, WebSocketSubjectConfig} from "rxjs/observable/dom/WebSocketSubject";
import {Observer} from "rxjs/observer";

@Injectable()
export class WebSocketService<T> extends Subject<T> {
    private reconnectionObservable: Observable<number>;
    private wsSubjectConfig: WebSocketSubjectConfig;
    private socket: WebSocketSubject<any>;
    private connectionObserver: Observer<boolean>;
    public connectionStatus: Observable<boolean>;


    constructor(private config: WebServiceConfig) {
        super();

        // connection status
        this.connectionStatus = new Observable((observer) => {
            this.connectionObserver = observer;
        }).share().distinctUntilChanged();

        // config for WebSocketSubject
        // except the url, here is closeObserver and openObserver to update connection status
        this.wsSubjectConfig = {
            url: config.URL,
            closeObserver: {
                next: (e:CloseEvent) => {
                    this.socket = null;
                    this.connectionObserver.next(false);
                }
            },
            openObserver: {
                next: (e:Event) => {
                    this.connectionObserver.next(true);
                }
            }
        };
        this.connect();
        // we follow the connection status and run the reconnect while losing the connection
        this.connectionStatus.subscribe((isConnected) => {
            if (!this.reconnectionObservable && typeof(isConnected) == "boolean" && !isConnected) {
                this.reconnect();
            }
        });
    }

    connect():void {
        this.socket = new WebSocketSubject(this.wsSubjectConfig);
        this.socket.subscribe(
            (m) => {
                this.next(m); // when receiving a message, we just send it to our Subject
            },
            (error:Event) => {
                if (!this.socket) {
                    // in case of an error with a loss of connection, we restore it
                    this.reconnect();
                }
            });
    }

    reconnect():void {
        this.reconnectionObservable = Observable.interval(this.config.getRetryInterval())
            .takeWhile((v, index) => {
                return index < this.config.attempts && !this.socket
            });
        this.reconnectionObservable.subscribe(
            () => {
                this.connect();
            },
            null,
            () => {
                // if the reconnection attempts are failed, then we call complete of our Subject and status
                this.reconnectionObservable = null;
                if (!this.socket) {
                    this.complete();
                    this.connectionObserver.complete();
                }
            });
    }

    send(data:any):void {
        this.socket.next(this.config.serializer(data));
    }
}

export enum TimeUnits {
    SECONDS = 1000,
    MINUTES = SECONDS * 60,
    HOURS = MINUTES * 60,
    DAYS = HOURS * 24
}

export class WebServiceConfig {
    URL: string = 'ws://localhost:4242/ws';
    attempts: number = 10; /// number of connection attempts
    retryUnit: TimeUnits = TimeUnits.SECONDS; /// pause between connections
    retryInterval: number = 5;

    serializer: (data: any) => any = function (data: any) {
        return JSON.stringify(data);
    };
    deserializer: (e: MessageEvent) => any = function (e: MessageEvent) {
        return e;
    };

    getRetryInterval(): number {
        return this.retryUnit * this.retryInterval;
    };
}

Now the question is how to I create new instances with different configuration?

One way I think would be extend to sub-class and pass configure. Is there better way rather creating extending class?

The configuration is dynamic! what I mean it will be written to the page in server-side (we use handlebars in the backend to render templates).

Upvotes: 0

Views: 456

Answers (2)

kemsky
kemsky

Reputation: 15270

There are couple of ways to do what you want:

export class SampleConfig {

    constructor(public value:string)
    {
    }
}

@Injectable()
export class SampleService {

    constructor(config:SampleConfig)
    {
        console.debug('config', config);
    }
}

providers      : [
    ...
    {provide: SampleService, useFactory: () => new SampleService(new SampleConfig('1'))},
    ...
],

providers      : [
    ...
    {provide: SampleConfig, useValue: new SampleConfig('1')},
    SampleService,
    ...
],

Upvotes: 1

Joshua Ohana
Joshua Ohana

Reputation: 6121

I have a similar requirement in my app and just use a base service, works perfectly. What is your opposition to inheritance with a config object? Here's the example of how I have mine setup incase it's handy...

export class BaseService {

  protected options: { ... };

  constructor(...) { }

  init(options) {
    this.options = options;
    ...
  }

Then any sub classes you need can just build your options object and call init with them. This way, you only have to write an abstracted version of all of your methods once. My base service is pretty meaty, but all of the child classes are super concise.

Upvotes: 0

Related Questions