Reputation: 1453
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
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
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