In Angular, I have interfaces which look like this (this is not my code):
export interface Vehicles {
id: number;
cars: Car;
trucks: Truck;
}
Export interface Car {
make: number;
model: number;
}
Export interface Truck {
make: number;
model: number;
}
My service contains two functions doCarMath() and doTruckMath() that do calculations based on the API's returned values:
vehicleNumbers: Vehicles;
constructor(private http: HttpClient) {
this.vehicleNumbers = {};
}
callID(id: string): Observable<Vehicles>{
return this.http.get<Vehicles>(this.myURL+`/${id}`);
}
getMyCarNumbers(id) {
this.callID(id).subscribe(results =>
{
this.vehicleNumbers = results;
}
}
doCarMath(): number {
return this.vehicleNumbers.car.make; / this.vehicleNumbers.car.model;
}
doTruckMath(): number {
return this.vehicleNumbers.truck.make; / this.vehicleNumbers.truck.model;
}
I’d like to combine doCarMath() and doTruckMath() into one doMath() function by passing in an argument to specify either car or truck, like so:
doMath(p_vehicle): number {
return this.vehicleNumbers.p_vehicle.make/this.vehicleNumbers.p_vehicle.model; // can p_vehicle be used here?
}
How can I use parameter p_vehicle here? The above throws intellisense errors for the p_vehicle parameter, but I don't have the code locally and can't share the error.
I think this would do the job:
doMath(p_vehicle): number {
return this.vehicleNumbers[p_vehicle].make/this.vehicleNumbers[p_vehicle].model;
}
Then calling the function would be :
doMath('car'); // or this.doMath('car');
doMath('truck'); // or this.doMath('truck');
Related
// inital types
interface Source {
id: string;
version: number; // discard
masterVariant: MasterVariant;
}
interface MasterVariant {
id: number; // discard
sku?: string;
}
// desired "lighter" types
interface Target {
id: string;
masterVariant: MasterVariantLight;
}
interface MasterVariantLight {
sku?: string;
}
to remove version property we can use the following
export class Convert {
public static toTarget(source: Source): Target {
const { version, ...result } = source;
return result;
}
}
can you see a way to discard masterVariant.id using above transformation?
export class Convert {
public static toTarget(source: Source): Target {
const { version, masterVariant: { id, ...rest }, ...result } = source;
return { id: result.id, masterVariant: { ...rest } };
}
}
Playground
While pure destructuring is possible here, I think it'd be clearer to use one more line to construct the required masterVariant format.
Having a class that never gets instantiated doesn't make much sense either - a plain function would be more suitable for the task (which is fine, unlike in Java).
const toTarget = (source: Source) => {
const { masterVariant, ...origSource } = source;
const newMasterVariant = { sku: masterVariant.sku };
return { ...origSource, masterVariant: newMasterVariant };
};
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());
}
}
I'm relatively new to the Typescript world, and I'm just working on a test app to get my self used to it. So I have this weird (?) issue with type restriction 'not working'.
I have an array defined in a class like member field this:
listings: Array<ICryptListingItem> = [];
And the interface is:
export interface ICryptListingItem {
name: string;
something: number;
}
Why is the compiler fine with doing:
this.listings = listings.data.map((listing) => {
return {
name: listing.name
}
});
The objects returned from listings.data.map is not implementing the interface the array has as it's type? What am I not getting here?
Thanks in advance.
TypeScript does handle this automatically; your code sample is missing some information. For example:
export interface ICryptListingItem {
name: string;
something: number;
}
class MyThing {
listings: Array<ICryptListingItem> = [];
doSomething() {
const listings = {
data: [
{ name: "the keeper" },
{ name: "the seeker" }
]
};
// Error here, as expected
this.listings = listings.data.map((listing) => {
return {
name: listing.name
}
});
}
}
Probably the type of either listings or listings.data is any, so the result of the map call is also any; any is then always an allowed type to assign to this.listings.
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.
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.