Javascript es6 combine 2 static classes into an object - javascript

I am having this code:
class A {
static b() {
}
}
class B {
static c() {
}
}
I am trying to combine these 2 static classes into one:
const combined = { ...A, ...B };
However, the combined object results in an empty one while I am expecting an object containing all the static methods combined.
What am I doing wrong ?

You can get an array of the static methods of a class with Object.getOwnPropertyNames:
class A {
static staticMethod() {}
nonStaticMethod() {}
}
console.log(Object.getOwnPropertyNames(A));
There are a few properties we are not interested in, namely prototype, length and name. We can filter them out manually, e.g. like this:
class A {
static staticMethod() {}
nonStaticMethod() {}
}
console.log(
Object.getOwnPropertyNames(A)
.filter(prop => prop !=='prototype' && prop !== 'length' && prop !== 'name')
);
Good! Now we can create the combined object and add A's filtered methods to it:
class A {
static b() {
console.log('called A.b');
}
}
const classAMethods = Object.getOwnPropertyNames(A)
.filter(prop => prop !== 'prototype' &&
prop !== 'length' &&
prop !== 'name');
const combined = {};
combined.A = {};
classAMethods.forEach(method => {
combined.A[method] = () => A[method]();
});
console.log(combined);
combined.A.b();
If you'd rather like to be able to call combined.b(), you can do the following. Note that this way methods with the same name in multiple classes will clash. E.g. if you have both A.b and B.b defined, combined can only hold one of them.
class A {
static b() {
console.log('called A.b');
}
}
const classAMethods = Object.getOwnPropertyNames(A)
.filter(prop => prop !== 'prototype' &&
prop !== 'length' &&
prop !== 'name');
const combined = {};
classAMethods.forEach(method => {
combined[method] = () => A[method]();
});
console.log(combined);
combined.b();
To bring all together, we have the following. Note that I used ...args to add support for passing arguments when calling the class methods.
class A {
static b() {
console.log('called A.b');
}
}
class B {
static c(name1, name2) {
console.log('called B.c, hello', name1, 'and', name2);
return 'returned by B.c';
}
}
const getMethods = (cls) => Object.getOwnPropertyNames(cls)
.filter(prop => prop !== 'prototype' &&
prop !== 'length' &&
prop !== 'name');
const combined = {};
const addMethodsToCombined = (cls) => {
combined[cls.name] = {};
getMethods(cls).forEach(method => {
combined[cls.name][method] = (...args) => cls[method](...args);
});
};
addMethodsToCombined(A);
addMethodsToCombined(B);
console.log(combined);
combined.A.b();
console.log(combined.B.c('world', 'universe'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
When you add new static methods to class A or B, they will automatically be available through combined as well. If you create a new class C, you just need to call addMethodsToCombined(C).

You can set the methods at a new object
const combined = {A:A.b, B:B.c}

You could extend B from A, then extend another class from B.
If you're using static classes though, it might be better to just use object literals
class A {
static b() {
console.log('called A.b')
}
}
class B extends A {
static c() {
console.log('called B.c')
}
}
class Combined extends B {}
Combined.b()
Combined.c()

Related

Lit-translate shows code or key instead of translation

I am trying to use lit-translate to translate my "Elmish" typescript website into different languages. I use webpack and dotnet.
Inside my index.ts I register the translate config:
registerTranslateConfig({
lookup: (key, config) => config.strings != null ? config.strings[key] : key,
empty: key => key,
loader: lang => {
return new Promise((resolve, reject) => {
resolve({"title": "Der Titel"});
})}
});
use("en");
(The loader is hardcoded because getting the localization file also didn't work, but that's not important for now).
Inside html I use get("title")or translate("title") to get the translation.
Instead of the translation, I either read [title] or
(part) => { partCache.set(part, cb); updatePart(part, cb); }
If I assign the result of translate() to a variable, I get the following result inside the Object:
TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
at Function.r (<anonymous>:1:83)
at Module../src/index.ts (http://localhost:8080/app.bundle.js:9800:10)
at __webpack_require__ (http://localhost:8080/app.bundle.js:12476:33)
at http://localhost:8080/app.bundle.js:13494:11
at http://localhost:8080/app.bundle.js:13497:12
I already tried disabling webpack strict mode.
The full class looks like the following:
export class App extends Application<Model, Message, {}> {
#property({type: Boolean})
hasLoadedStrings = false;
init(): [Model, Cmd] {
const initModel: Model = {
openPage: [{title: "Home"}, home]
}
return [initModel, Cmd.none]
}
constructor() {
super();
this.hasLoadedStrings = false;
}
shouldUpdate (changedProperties: Map<string | number | symbol, unknown>) {
return this.hasLoadedStrings && super.shouldUpdate(changedProperties);
}
async connectedCallback () {
super.connectedCallback();
await use("en");
this.hasLoadedStrings = true;
}
}
customElements.define('my-element', App);
I solved the problem by writing my own little translation library.
The lit-translate package contains errors if you use it as suggested in the documentation so feel free to use my solution:
translate.ts:
const de_config =
{
"Category": {
"Home": "Start",
},
"Home": {
"Welcome back,": "Willkommen zurück,"
};
export function translate(key: string) {
const keyParts = key.split(".");
const keyCategory = keyParts.shift();
const keyWord = keyParts.join('.');
let translationIndexes = typedKeys(de_config);
for(let translationIndex of translationIndexes)
{
if(translationIndex == keyCategory) {
let translationKeys = typedKeys(de_config[translationIndex]);
for(let translationKey of translationKeys) {
if(translationKey == keyWord) {
return de_config[translationIndex][translationKey];
}
}
}
}
return key;
}
function typedKeys<T>(o: T): (keyof T)[] {
return Object.keys(o) as (keyof T)[];
}
Access translations with:
import { translate } from './translate';
translate("Category.Home");
One could also store translation object in a different file, write a function to change language dynamically, etc...

Pass Element as a type in function in React/Typescript

I am trying to create a generic collection for a React project in Typescript. I have:
class List<T>
{
constructor(data: any){...}
}
which will create a list of a type. I want to cast it to something by calling it with something like:
let carList: List<Car> = ...
carList.cast(Car)
if assuming Car is a class.
How do I pass in a class type as a parameter to map it? I have this so far:
public cast(Type: T)
{
this.list = this.list.map(x => x as Type)
}
which does not work as the compiler complains about 'Type refers to a value but is being used as a type'.
Assume this.list is created when the List is initialized.
Type does not exists in JS runtime, so you can not pass it as function parameter.
Usually to cast your List to List in the runtime you have to check is it possible. So you have to check all list content, and it could be expensive from the performance point of view
type Guard<T> = (item: any) => item is T;
// https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards
class List<T> {
constructor(
public data: T[]
) { }
static getUserGuard<T>(itemGuard: Guard<T>): Guard<List<T>> {
return (list: List<any>): list is List<T> => list.data.every(itemGuard)
}
}
// and list usage
const lists: List<any>[] = [
new List<number>([42]),
new List<string>(['foo']),
]
const isNumber = (v: any): v is number => typeof v === 'number';
const isString = (v: any): v is string => typeof v === 'string';
// some code with random lists
const doSomething = (lists: List<any>[]) => {
for (const list of lists) {
if (List.getUserGuard(isNumber)(list)) {
// list is List<number> there
} else if (List.getUserGuard(isString)(list)) {
// list is List<string> there
} else {
// list has some other type there
}
}
}
Also you can check it my classes but not by the content
class List<T> {
constructor(
public data: T[]
) { }
}
class NumbersList extends List<number> {
}
class StringList extends List<string> {
}
// some code with random lists
const doSomething = (lists: List<any>[]) => {
for (const list of lists) {
if (list instanceof NumbersList) {
// list is List<number> there
} else if (list instanceof StringList) {
// list is List<string> there
} else {
// list has some other type there
}
}
}

Typescript: Iterate Through Each Member between Two Classes and Flag Difference in Angular

How do I compare two class models and find corresponding differences?
Following are two models which have exact same members, and we need to compare.
Is there an algorithm in Typescript which conducts this? * A new result class should be created with class members and boolean different flag?
Looking for a easy way in Typescript algorithm. It needs to accept any class,
Note: Some members in class contain a class itself.
currently
Class:
export class PropertyLocation {
streetName: string;
streetType: string;
postdirectional?: string;
unitNumber?: string;
unitType?: string;
city: string;
state?: string;
postalCode: number;
postalCodeExtension?: string;
effectiveStartDate: Date;
addressChangeReason?: AddressChangeReasonDto
addressSource?: SourceOfAddressDto;
}
Result Class array sample:
if there is more optimal storage method, feel free to modify
export class DifferenceClass {
ClassMember: string;
DifferentFlag: boolean
}
looking for code solution, refraining from third party libraries as company does not prefer
Why not just build a method/function into your class that runs the comparison. You could do this by comparing each property individually (typing everything out) or just looping every key in your class and comparing it to every key in the passed in object.
class DifferenceClass {
constructor(classMember, differenceFlag) {
this.ClassMember = classMember;
this.DifferenceFlag = differenceFlag;
}
}
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
isSame(person) {
if (person instanceof Person) {
const differences = [];
for (const key in person) {
if (this[key] !== person[key]) {
differences.push(new DifferenceClass(key, true));
}
}
return differences;
} else {
throw new Error('Object is not a Person class');
}
}
}
const p1 = new Person('John', 'Doe');
const p2 = new Person('Jane', 'Doe');
console.log('Should be empty', p1.isSame(p1));
console.log('Should have diff results', p1.isSame(p2));
console.log('should be an exception', p1.isSame(1));
I have a function that compares two objects and does a deep compare
export const compare = (obj1: any, obj2: any): boolean =>
Array.isArray(obj1)
? Array.isArray(obj2) && obj1.length === obj2.length && obj1.every((item, index) => compare(item, obj2[index]))
: obj1 instanceof Date
? obj2 instanceof Date && obj1.getDate() === obj2.getDate()
: obj1 && typeof obj1 === 'object'
? obj2 && typeof obj2 === 'object' &&
Object.getOwnPropertyNames(obj1).length === Object.getOwnPropertyNames(obj2).length &&
Object.getOwnPropertyNames(obj1).every(prop => compare(obj1[prop], obj2[prop]))
: obj1 === obj2;
You could use this with
const diff = (obj1, obj2) => Object.keys(obj1).map(key => ({ ClassMember: key, DifferentFlag: !compare(obj1[key], obj2[key]) }));
You can use deep-diff library:
import { diff } from 'deep-diff';
const differences = diff(location1, location2);
It creates a nice difference object with the paths to the different attributes.
Another way would be to use a pipe to add a class to your template
import { Pipe, PipeTransform } from '#angular/core';
const resolveProperty = (obj: any, property: string): any =>
property ? property.split('.').reduce((result, prop) => (result ? result[prop] : undefined), obj) : undefined;
#Pipe({
name: 'different'
})
export class DifferentPipe implements PipeTransform {
transform(obj: any, obj2: any, property: string): boolean {
return resolveProperty(obj, property) !== resolveProperty(obj2, property);
}
}
and in you view you can add a class if the object has changed
<div [ngClass]="{ 'changed': original | changed : new : property }">

Ensure my function returns mutated object as instanceof the same class typescript?

export const FilterUndefined = <T extends object>(obj: T): T => {
return Object.entries(obj).reduce((acc, [key, value]) => {
return value ? { ...acc, [key]: value } : acc;
}, {}) as T;
};
I'm migrating a database and part of cleaning up the old data structure, some of the values for some keys end up being literaly undefined. The key will still exist and will have the value undefined
I made this function but after modifying a class object with it, it will no longer be an instanceof the same class. How could I make this return an object that's instanceof the same class as the input parameter?
The as T makes TS compiler shut up but that's it.
I've also tried to get the prototype of that object and return new prototype(obj) or return new prototype.constructor(obj)
The console log of the prototype looks like this:
PROTOTYPE TestClass {}
I'm testing using this setup:
it('should return the same type that it receives', () => {
class TestClass {
name: string;
optionalProperty?: any;
}
let testObject = new TestClass();
testObject.name = 'My Name';
testObject.optionalProperty = undefined;
console.log(testObject instanceof TestClass);
testObject = FilterUndefined(testObject);
console.log(testObject instanceof TestClass);
console.log(testObject);
expect(testObject).instanceOf(TestClass);
});
EDIT: JSFiddle: https://jsfiddle.net/3sdg98xt/2/ but copy-pasted from vscode without any issues running it i'm getting an error 'execpted expression, got ';'
This solution will mutate the input object by removing the keys with undefined values.
function removeUndefined <T>(object: T): T {
for (const id in object) {
if (object[id] === undefined) {
delete object[id];
}
}
return object;
}
It seems it works for your test case: Test in typescript playground
Yes, with each iteration of reduce you are returning a new {} which is an instance Object.
So to make the return object of same instace as of argument you should make following changes.
export const FilterUndefined = (obj) => {
return Object.entries(obj).reduce((acc, [key, value]) => {
if (value) {acc[key] = value;}
else {delete acc[key]}
return acc;
}, new obj.constructor);
};
or you can use new obj.__proto__.constructor as per the target of typescript output you are using.
Reply in case you have typescript issues with this code snippet.

What is the intention of the class `Model` in this code snippets?

I am reading a code base written by someone else. a Model class is defined in the following snippet, but I can not figure out why the writer defined this class. What is the intention to define this class other that using plain Object? What is the pattern here? Thank you very much!
const models = {}
export default class Model {
constructor() {
this._type = this.constructor.name
if (!models[this._type]) {
models[this._type] = this.constructor.prototype
}
}
}
const model = object => {
if (!object) {
return object
}
if (object._type && object._type != object.constructor.name) {
object.__proto__ = models[object._type]
}
(typeof object == "object") && Object.values(object).forEach(v => {
model(v)
})
return object
}
Its usage:
class xxxModel extends Model { ... }.
updated
and there are many occurrences of the following usage pattern:
const refreshData = function (...args) {
// do something to update UI.
const data = args[0]
Object.keys(data).forEach(key => model(this.data[key]))
}

Categories