How to avoid two switch? - javascript

I have a class with two private methods:
export class LayerEditor {
public layerManager: LayerManager;
constructor() {
this.layerManager = new LayerManager(this);
}
private executeCommand() {
switch (this.layerManager.editableLayer.type) {
case LayerType.common:
this.commandManager.set(new SelectExistCommand(this.reonMap));
break;
case LayerType.point:
this.commandManager.set(new SelectExistPointCommand(this.reonMap));
break;
}
}
private createTools(): EditorTools {
switch (this.layerManager.editableLayer.type) {
case LayerType.common:
return new CommonTools(new CommonToolsFactory());
case LayerType.point:
return new PointTools(new PointToolsFactory());
}
}
}
How to avoid two switches no joining two methods in one switch? How to apply flexibility of OOP?
Class LayerManager manages a layers:
class LayerManager {
public selectedLayer: TreeNode;
public editableLayer: Layer;
public polygonsArea: string;
private readonly layers: TreeNode[];
constructor(private layerEditor: LayerEditor) {
this.layers = this.layerEditor.r.layersManager.editableLayers;
}
getLayers() {
return this.layers;
}
selectLayer(layerId: string) {
this.selectedLayer = { ...this.layerEditor.r.layersManager.getLayerConfig(layerId), enabled: true };
this.layerEditor.r.state.changeTreeNode(this.selectedLayer);
}
createLayerObject() {
this.editableLayer = FactoryLayer.create(this.selectedLayer.id, this.selectedLayer.type, this.layerEditor.r);
}
}
Command manager class makes works with commands. This class works as dispatcher and stores all commands.
class CommandManager {
public currentCommand: Command;
protected commands: Command[] = [];
protected undoCommand: Command;
constructor(public layerEditor: LayerEditor) {}
set(command: Command): void {
if (command === this.currentCommand) return;
this.currentCommand = command;
this.commands.push(this.currentCommand);
}
execute(): void {
this.currentCommand.execute();
}
undo(): void {
this.currentCommand.undo();
}
redo(): void {
this.currentCommand.redo();
}
cancel(): void {
while (this.commands.length) {
this.currentCommand = this.commands.pop();
this.undo();
}
}
complete(): void {
this.currentCommand.complete();
}
}
If you need anything else I will post

Essentially what you want to do is move all split decisions into one place. Instead of having many pairs of classes like PointTools, PointToolsFactory, SelectExistPointCommand, etc. you have one single PointConfig. You want your two configurations to match the same interface as much as possible. They both create a command and some tools. Other classes should be able to use the returned tools interchangeably without caring if it is PointTools or CommonTools. That way you can just call this.config.createTools().
This is the Factory Method Pattern and the C# Example should be readable as it's extremely similar to TypeScript. I've seen more of your code than what is posted here so I know that you were trying to do this already with a class FactoryLayer. But you want to define it so that the factory is the only switch that you need.
It should be something roughly along these lines:
interface LayerConfig {
id: string;
type: SemanticLayerType;
fields: Record<string, FieldConfig>;
createTools(): Button[];
createSelectCommand(map: ReonMap): Command;
}
class PointConfig implements LayerConfig {
readonly type = SemanticLayerType.Point;
readonly fields = {}; // fill this in
constructor(public readonly id: string) {}
createTools() {
return []; // fill this in
}
createSelectCommand(map: ReonMap): Command {
// fill this in
}
}
class PolygonConfig implements LayerConfig {
readonly type = SemanticLayerType.Polygon;
readonly fields = {}; // fill this in
constructor(public readonly id: string) {}
createTools() {
return []; // fill this in
}
createSelectCommand(map: ReonMap): Command {
// fill this in
}
}
class Layer {
tools: Button[];
constructor(public readonly config: LayerConfig) {
this.tools = config.createTools();
}
// other stuff
}
export class FactoryLayer {
static create(layerId: string, layerType: LayerType): Layer {
if (layerType === LayerType.common) {
return new Layer(new PolygonConfig(layerId));
}
if (layerType === LayerType.point) {
return new Layer(new PointConfig(layerId));
}
throw new Error("Unknown Layer Type " + layerType);
}
}
export class LayerEditor {
public layerManager: LayerManager;
constructor(private map: ReonMap, private commandManager: CommandManager) {
this.layerManager = new LayerManager(map); //this);
}
private getCurrentLayer(): Layer {
if (!this.layerManager.editableLayer) {
throw new Error("No Layer Selected");
}
return this.layerManager.editableLayer;
}
private executeCommand(): void {
// you could probably clean this up but you get the idea
this.commandManager.execute(
this.getCurrentLayer().config.createSelectCommand(this.map)
);
}
public getTools(): Button[] {
return this.getCurrentLayer().tools;
}
}

I have just tried to bring common things together. And trying to leverage Typescript features.
In both the private method you have this.layerManager.editableLayer.type common, now by adding an optional parameter and making use of returning multiple datatype. Below is the updated code.
If need you can default parameter as type = 'execute' and whenever needed pass parameter as create
private checkTool( type?: string): EditorTools | void {
const layerManagerType = this.layerManager.editableLayer.type
switch(layerManagerType) {
case LayerType.common:
return (type === 'execute') ?
this.commandManager.set(new SelectExistCommand(this.reonMap))
: new CommonTools(new CommonToolsFactory());
break;
case LayerType.point:
return ( type === 'execute') ?
this.commandManager.set(new SelectExistPointCommand(this.reonMap))
: new PointTools(new PointToolsFactory());
}
}

Related

How to make the static member type of a class point to the type of the subclass

class SingletonBaseClass {
static singleton: SingletonBaseClass | null = null;
static instance() {
if (!this.singleton) {
this.singleton = new this();
}
return this.singleton;
}
static destroy() {
this.singleton = null;
}
}
class Demo extends SingletonBaseClass {
hello() { }
}
Demo.instance().hello();
Above is the code, Demo.instance() will be considered an instance of SingletonBaseClass instead of Demo, so Demo.instance().hello() will report a TypeScript error.
Is there any way to make the return type of Demo.instance() be SingletonBaseClass?
you could just cast it:
(Demo.instance() as Demo).hello();

TypeScript: Recursively creating complex objects

Using Angular & TypeScript, we can use generics and all the Compile-goodness to assure some sort of type-safety. But if we are using for example the HTTP-Service, we don't get a specific objec but just parsed JSON. For example, we have some generic methods doing that:
public get<T>(relativeUrl: string): Promise<T> {
const completeUrlPromise = this.createCompleteUrl(relativeUrl);
const requestOptions = this.createRequestOptions(ContentType.ApplicationJson, true);
return completeUrlPromise.then(completeUrl => {
return this.processResponse<T>(this.http.get(completeUrl, requestOptions));
});
}
private processResponse<T>(response: Observable<Response>): Promise<T> {
const mappedResult = response.map(this.extractData);
const result = mappedResult.toPromise();
return result;
}
private extractData(res: Response): any {
let body;
if (!Array.isArray(res)) {
if (res.text()) {
body = res.json();
}
} else {
body = res;
}
if (!JsObjUtilities.isNullOrUndefined(body)) {
return body;
}
return {};
}
Ultimately, the generic type is useless this way, since we just get the JSON. If the generic object has methods or properties not in the JSON, they are lost.
To avoid this, we added the possibility to pass a constructor-function to truly create the object:
private processResponse<T>(response: Observable<Response>, ctor: IParameterlessConstructor<T> | null = null): Promise<T> {
let mappedResult = response.map(this.extractData);
if (ctor) {
mappedResult = mappedResult.map(f => {
const newObj = JsObjFactory.create(f, ctor);
return newObj;
});
}
const result = mappedResult.toPromise();
return result;
}
And the JsObjFactory looking like this:
export class JsObjFactory {
public static create<T>(source: any, ctorFn: IParameterlessConstructor<T>): T {
const result = new ctorFn();
this.mapDefinedProperties(source, result);
return result;
}
private static mapDefinedProperties<T>(source: Object, target: T): void {
const properties = Object.getOwnPropertyNames(target);
properties.forEach(propKey => {
if (source.hasOwnProperty(propKey)) {
target[propKey] = source[propKey];
}
});
}
}
This works well for shallow objects, but doesn't work, if a property is also a complex type with a constructor. As there are no types at runtime, the best bet I have currently is to kindahow parse the properties, check if classes exist and then create them. But this seems to be very error-prone and cumbersome.
Since I'm always certain, I'm not the only person with this issues, are there solutions, or TypeScript/JavaScript features I'm not aware off, which would help here?
I don't personally do it like this, but it may be what you're looking for.
Example:
Customer.ts
export interface ICustomer {
Id: number;
Name: string;
Orders: IOrder[];
...
}
export class Customer implements ICustomer {
public Id: number;
public Name: string;
public Orders: IOrder[];
constructor(customer: Partial<ICustomer>) {
this.Id = customer.Id || 0;
this.Name = customer.Name || '';
this.Orders = [];
customer.Orders.forEach((order: IOrder) => this.Orders.push(new Order(order)));
}
//some functions
}
Order.ts
export interface IOrder {
Id: number;
Weight: number;
Shipmentdate: string;
}
export class Order implements IOrder {
public Id: number;
public Weight: number;
public Shipmentdate: string;
constructor(order: Partial<IOrder>) {
this.Id = order.Id || 0;
this.Weight = order.Weight || 0;
this.Shipmentdate = order.Shipmentdate || '';
}
//some functions
}
This would make the Object (in this case Customer) responsible for instantiating it's known complex types that you pass in. And Order in turn could have its complex types that it instantiates.

listen to object property changes inside array in a service

So basically what I want to achieve is watching/listening objects changing inside of array within an injectable service using setters and getters to manipulate it's data
eg
#Injectable()
export class StorageService {
protected items: Array<any> = [];
constructor(private storage: Storage) {
this.storage.ready().then(() => {
StorageService.getGetters().forEach((get) => {
this.storage.get(get).then(res => this.items[get] = res);
});
});
}
public static getGetters(): string[] {
return Object.keys(this.prototype).filter(name => {
return typeof Object.getOwnPropertyDescriptor(this.prototype, name)["get"] === "function"
});
}
get Storage() {
return this.storage;
};
ClearStorage() {
this.storage.clear();
}
protected Setter(key: string, value: any): void {
this.items[key] = value;
this.storage.set(key, value);
}
protected Getter(key: string): any {
return this.items[key];
}
set User(value: User) {
this.Setter('User', value);
}
get User(): User {
return this.Getter('User');
}
}
where User interface is :
export interface User {
id: number;
role_id: number;
name: string;
email?: string;
}
now in any component or service/provider I can DI my StorageService so I can access the User getter.
so:
storage.User.name = 'testing';
now the name is changed , but I have no way to track that , so I can update my storage!
to update my storage I would do:
storage.User.name = 'testing';
storage.User = storage.User;
which is working , but I need a way to listen to any changes happens to the object properties, so I can update my storage...
I searched alot , and all I can find is watching components #Input() , which is not working in my case.
Hopefully I made my point clear.

Dynamically calling a static method

In a class there are several static methods and the method to be called will be decided on the run time. How could I call this method dynamically?
export default class channel {
// METHOD THAT WILL DYNAMICALLY CALL OTHER STATIC METHODS
private static methodMap = {
'channel-create' : 'create',
'channel-user-count' : 'userCount',
'channel-close' : 'close'
};
public static do(commandType:string,params: any) {
if(channel.methodMap.hasOwnProperty(commandType)) {
// GET NAME OF THE METHOD
let method = channel.methodMap[commandType];
// CALL METHOD ON THE FLY
//return channel.call(method,params);
// channel.userCount(params);
}
}
/**
* Adds channel to available channel list
*/
private static create(channelName:string) {
}
/**
* Returns count of users in the channel
*/
private static userCount(channelName:string) {
}
}
You can dynamically invoke a method by using Classname['methodName'](param). As in your case, you can invoke create method as Channel['create']('MyChannel')
Here is the working example: Typescript Playground
class Channel {
private static methodMap = {
'channel-create' : 'create',
'channel-user-count' : 'userCount',
'channel-close' : 'close'
};
private static create(channelName:string) {
alert('Called with ' + channelName);
}
private static userCount(channelName:string) {
alert('Usercount called with ' + channelName);
}
public static do(commandType: string, params: any) {
if(Channel.methodMap.hasOwnProperty(commandType)) {
let method = Channel.methodMap[commandType];
return Channel[method](params);
}
}
}
Channel.do('channel-create', 'MyChannel');
Channel.do('channel-user-count', 1000);
Edit: Even though the above approach works, As #Ryan mentioned in his answer, providing functions directly in map is much cleaner.
private static methodMap: MethodMap = {
'channel-create': Channel.create,
'channel-user-count': Channel.userCount,
'channel-close': Channel.close,
};
Store the functions directly in the map:
type MethodMap = { [name: string]: (any) => void };
private static methodMap: MethodMap = {
'channel-create': Channel.create,
'channel-user-count': Channel.userCount,
'channel-close': Channel.close,
};
public static do(commandType: string, params: any) {
if (channel.methodMap.hasOwnProperty(commandType)) {
const method = channel.methodMap[commandType];
method(params);
}
}
To add to the answer by #HardikModha, you can also get the compiler to check the commandType against the possible values:
public static do(commandType: keyof typeof Channel.methodMap, params: any) {
if(Channel.methodMap.hasOwnProperty(commandType)) {
let method = Channel.methodMap[commandType];
return Channel[method](params);
}
}
...
Channel.do('channel-create', 'MyChannel'); // fine
Channel.do('channel-created', 'MyChannel'); // error
(code in playground)

Get properties of class in typescript

I've the following class:
export class Test {
private _rowsCount: string;
public get RowsCount(): string {
return this._rowsCount;
};
public set RowsCount(value: string) {
this._rowsCount = value;
};
private _rowsCount2: string;
public get RowsCount2(): string {
return this._rowsCount2;
};
public set RowsCount2(value: string) {
this._rowsCount2 = value;
};
}
I need to iterate over the properties in a specific class, I tried the following:
Object.keys(this).forEach((key)=> {
console.log(key);
});
But the problem that this iterate just over the private fields, I tried also the following I got all the methods and properties:
for (var property in this) {
if (this.hasOwnProperty(property)) {
console.log(property);
}
}
Does anyone have a solution?
Thanks!
If you need to only get the getters/setters, then you'll need to do something like:
class Test {
...
public static getGetters(): string[] {
return Object.keys(this.prototype).filter(name => {
return typeof Object.getOwnPropertyDescriptor(this.prototype, name)["get"] === "function"
});
}
public static getSetters(): string[] {
return Object.keys(this.prototype).filter(name => {
return typeof Object.getOwnPropertyDescriptor(this.prototype, name)["set"] === "function"
});
}
}
Test.getGetters(); // ["RowsCount", "RowsCount2"]
Test.getSetters(); // ["RowsCount", "RowsCount2"]
(code in playground)
You can put the static methods in a base class, and then when you extend it the subclass will have those static methods as well:
class Base {
public static getGetters(): string[] {
return Object.keys(this.prototype).filter(name => {
return typeof Object.getOwnPropertyDescriptor(this.prototype, name)["get"] === "function"
});
}
public static getSetters(): string[] {
return Object.keys(this.prototype).filter(name => {
return typeof Object.getOwnPropertyDescriptor(this.prototype, name)["set"] === "function"
});
}
}
class Test extends Base {
...
}
Test.getGetters(); // work the same
(code in playground)
If you want these methods to be instance methods then you can do this:
class Base {
public getGetters(): string[] {
return Object.keys(this.constructor.prototype).filter(name => {
return typeof Object.getOwnPropertyDescriptor(this.constructor.prototype, name)["get"] === "function"
});
}
public getSetters(): string[] {
return Object.keys(this.constructor.prototype).filter(name => {
return typeof Object.getOwnPropertyDescriptor(this.constructor.prototype, name)["set"] === "function"
});
}
}
The change is that instead of using this.prototype you're using this.constructor.prototype.
Then you simply:
let a = new Test();
a.getGetters(); // ["RowsCount", "RowsCount2"]
(code in playground)
Edit
Based on a comment by #Twois, who pointed out that it won't work when targetting es6, here's a version that will work:
class Base {
public static getGetters(): string[] {
return Reflect.ownKeys(this.prototype).filter(name => {
return typeof Reflect.getOwnPropertyDescriptor(this.prototype, name)["get"] === "function";
}) as string[];
}
public static getSetters(): string[] {
return Reflect.ownKeys(this.prototype).filter(name => {
return typeof Reflect.getOwnPropertyDescriptor(this.prototype, name)["set"] === "function";
}) as string[];
}
}
The main difference: using Reflect.ownKeys(this.prototype) instead of Object.keys(this.prototype).
What you can do is for the class you want to use it extend the class above and make the properties public for this reason;
class TestExposed extend Test {
public _rowsCount: string;
public _rowsCount2: string;
}
And in your Test class make the private protected:
class Test {
protected _rowsCount: string;
public get RowsCount(): string {
return this._rowsCount;
};
public set RowsCount(value: string) {
this._rowsCount = value;
};
protected _rowsCount2: string;
public get RowsCount2(): string {
return this._rowsCount2;
};
public set RowsCount2(value: string) {
this._rowsCount2 = value;
};
}
Then you should be able to iterate over the properties in an external class;
But if you want to have the values; Why not make a function that exposes the values by returning them in an array or log them as a string;

Categories

Resources