Is there a way in typescript to set a property name from a variable?
Something like this
export function objectFactory(prop: string) {
return {
prop: {
valid: false
}
};
}
You are looking for computed properties, this is an ES6 feature and not specific to TypeScript.
export function objectFactory(prop: string) {
return {
[prop]: {
valid: false
}
};
}
You can do it like this:
export function objectFactory(prop: string) {
let data: any = {};
data[prop] = {};
data[prop].valid = false;
return data;
}
just wanted to avoid if statements is there way to assign values to object if they have similar keys in response instead of checking each object with if ?
what could be efficient approach here ?
main.ts
public responsehandler(#Body()data: any): any {
const response: Idetails = {} as Idetails;
if (data.details === undefined || data.details === null) {
return data;
}
if (data.details) {
if (data.details.primary) {
response.details.primary.beginningBalance = data.details.primary.beginningBalance;
response.details.primary.endingBalance = data.details.primary.endingBalance;
}
if (data.details.secondary) {
response.details.secondary.beginningBalance = data.details.secondary.beginningBalance;
response.details.secondary.endingBalance = data.details.secondary.endingBalance;
}
}
return response;
}
interface.ts
export interface Idetails {
primary:balanceDetails;
secondary: balanceDetails;
}
export interface balanceDetails {
beginningBalance: string;
endingBalance: string;
}
data
details: {
primary: {
beginningBalance: 458,
endingBalance: 890
},
secondary: {
beginningBalance: 47,
endingBalance: 871
}
}
Check this out: https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Objetos_globales/Object/hasOwnProperty
You could try this approach:
public responsehandler(#Body()data: any): any {
const response: Idetails = {} as Idetails;
if (!data.details) {
return data;
}
for (const key in response.details) {
if (data.details.hasOwnProperty(key)) {
response.details[key] = data.details[key];
}
}
return response;
}
You should check more validations or conditions to make it work for you.
If your goal is just to avoid if statements, you could rewrite it like so:
// in js (sorry not a ts user)
function responseHandler(data) {
return data.details == null ? data : {details: {...data.details}};
}
console.log(responseHandler({details: {primary: {beginningBalance: 0, endingBalance: 1}}}));
console.log(responseHandler({details: {secondary: {beginningBalance: 0, endingBalance: 1}}}));
console.log(responseHandler({noDetails: 'oopsy'}));
I am working on a react project but I think this question relates to all JS
I have a Token file that contains the following functions:
export default {
set(token) {
Cookies.set(key, token);
return localStorage.setItem(key, token);
},
clear() {
Cookies.remove(key);
return localStorage.removeItem(key);
},
get() {
try {
const retVal = localStorage.getItem(key) || '';
return retVal;
} catch (e) {
return '';
}
},
Now I want to add a set of what are essentially environment variables for the domain of these 3 functions. In my case its based on window.location.hostname but could really be anything.
In this instance lets say we want key to be dev, uat or prod based on window.location.hostname
getKey = host => {
if(host === 'a')
return 'dev'
elseIf (host === 'b')
return 'uat'
else
return 'prod'
}
I think the above is fairly standard to return the key you want. but what if your key has 6 vars, or 8, or 20. How could you set all the vars so that when you call set(), clear() and get() that they have access to them?
Basically I want to wrap the export in a function that sets some vars?
To illustrate this a bit more
class session extends Repo {
static state = {
current: false,
};
current(bool) {
this.state.current = bool;
return this;
}
query(values) {
<Sequelize Query>
});
}
export session = new Session();
using this I can call current(true).session() and sessions state would be set to true. I want to apply a similar pattern to the Token file but I don't want to change all my calls from Token.set(token) to Token.env(hostname).set(token)
This acomplished what I wanted, I had to call the function from within the others as window is not available on load. It essentially illustrates the pattern I was looking for. Thanks for Jim Jeffries for pointing me towards the answer.
class Token {
constructor(props) {
this.state = {
testKey: null,
};
}
setCred = host => {
if (host === 'uat') {
this.state.testKey = 'uat';
} else if (host === 'prod') {
this.state.testKey = 'prod';
} else {
this.state.testKey = 'dev';
}
};
set(token) {
this.setCred(window.location.hostname);
Cookies.set(testKey, token);
return localStorage.setItem(testKey, token);
}
clear() {
this.setCred(window.location.hostname);
Cookies.remove(testKey);
return localStorage.removeItem(testKey);
}
get() {
this.setCred(window.location.hostname);
try {
const retVal = localStorage.getItem(key) || '';
return retVal;
} catch (e) {
return '';
}
}
}
export default new Token();
If anyone else has another idea please share.
Is there a way in typescript to set a property name from a variable?
Something like this
export function objectFactory(prop: string) {
return {
prop: {
valid: false
}
};
}
You are looking for computed properties, this is an ES6 feature and not specific to TypeScript.
export function objectFactory(prop: string) {
return {
[prop]: {
valid: false
}
};
}
You can do it like this:
export function objectFactory(prop: string) {
let data: any = {};
data[prop] = {};
data[prop].valid = false;
return data;
}
How would I map a typescript enum? For example, with strings you can do this:
let arr = [ 'Hello', 'Goodbye' ];
arr.map(v => {
if (v === 'Hello') {
return ':)';
} else if (v === 'Goodbye') {
return ':(';
}
); // [ ':)', ':(' ]
This, of course, doesn't work with enums:
enum MyEnum { Hello, Goodbye };
MyEnum.map(v => {
if (v === MyEnum.Hello) {
return ':)';
} else if (v === MyEnum.Goodbye) {
return ':(';
}
}); // does not work
Ideally, I'd like to do this in a generalized way so I can simply take any enum I have and put it through a map function while preserving type information. Usage might look something like this:
map(MyEnum, v => {
if (v === MyEnum.Hello) {
return ':)';
} else if (v === MyEnum.Goodbye) {
return ':(';
}
}); // [ ':)', ':(' ]
I've been fiddling around with getting a function that does this for me but keep having issues getting the generics just right.
To map an enum do this:
(Object.keys(MyEnum) as Array<keyof typeof MyEnum>).map((key) => {})
The function to solve this is quite simple.
// you can't use "enum" as a type, so use this.
type EnumType = { [s: number]: string };
function mapEnum (enumerable: EnumType, fn: Function): any[] {
// get all the members of the enum
let enumMembers: any[] = Object.keys(enumerable).map(key => enumerable[key]);
// we are only interested in the numeric identifiers as these represent the values
let enumValues: number[] = enumMembers.filter(v => typeof v === "number");
// now map through the enum values
return enumValues.map(m => fn(m));
}
As you can see, we first need to get all of the keys for the enum (MyEnum.Hello is actually 1 at runtime) and then just map through those, passing the function on.
Using it is also simple (identical to your example, although I changed the name):
enum MyEnum { Hello, Goodbye };
let results = mapEnum(MyEnum, v => {
if (v === MyEnum.Hello) {
return ':)';
} else if (v === MyEnum.Goodbye) {
return ':(';
}
});
console.log(results); // [ ':)', ':(' ]
The reason we need to filter the enum to be numbers only is because of the way enums are compiled.
Your enum is actually compiled to this:
var MyEnum;
(function (MyEnum) {
MyEnum[MyEnum["Hello"] = 0] = "Hello";
MyEnum[MyEnum["Goodbye"] = 1] = "Goodbye";
})(MyEnum || (MyEnum = {}));
;
However we are not interested in "Hello" or "Goodbye" as we can't use those at runtime.
You will also notice a funny type statement right before the function. This is because you can't type a parameter as someParameter: enum, you need to explicitly state it as a number -> string map.
Mapping in Typescript can be extremely powerful for writing less code.
I have been using key value Enum mapping a lot recently and would recommend it!
Here are a couple of examples!
Basic enum usage
enum InlineStyle {
"Bold",
"Italic",
"Underline"
}
type IS = keyof typeof InlineStyle
// Example of looping
(Object.keys(InlineStyle) as Array<IS>).forEach((key) => {
// code here
})
// Example of calling a function
const styleInline = (style: IS) => {
// code here
}
Enum key value usage
enum ListStyle {
"UL" = "List",
"OL" = "Bullet points"
}
// Example of looping
Object.entries(ListStyle).forEach(([key, value]) => {
// code here
})
Interface mapping
enum InlineStyle {
"Bold" = "isBold",
"Italic" = "isItalic",
"Underline" = "isUnderlined"
}
type InlineStyleType = Record<InlineStyle, boolean>
enum ListStyle {
"UL",
"OL"
}
type LS keyof typeof ListStyle
interface HTMLBlock extends InlineStyleType {
// This has extended with
// isBold: boolean
// isItalic: boolean
// isUnderlined: boolean
listType: LS
}
With ts-enum-util (npm, github), it's easy, type-safe (uses generics), and takes care of skipping the numeric reverse lookup entries for you:
import { $enum } from "ts-enum-util";
enum MyEnum { Hello, Goodbye };
$enum(MyEnum).map(v => {
if (v === MyEnum.Hello) {
return ':)';
} else if (v === MyEnum.Goodbye) {
return ':(';
}
}); // produces [':(', ':)']
NOTE: ts-enum-util always iterates based on the order of the sorted enum keys to guarantee consistent order in all environments. Object.keys() does not have a guaranteed order, so it's impossible to iterate enums "in the order they were defined" in a cross-platform guaranteed way.
(update: new version of ts-enum-util now preserves the original order in which the enum was defined)
If you are using string enums, then combine it with ts-string-visitor (npm, github) for even more generic type-safe compiler checks to guarantee that you handle all possible enum values in your map function:
(update: new version of ts-enum-util now includes functionality of ts-string-visitor, and it works on numeric enums now too!)
import { $enum } from "ts-enum-util";
import { mapString } from "ts-string-visitor";
enum MyEnum { Hello = "HELLO", Goodbye = "GOODBYE" };
$enum(MyEnum).map(v => {
// compiler error if you forget to handle a value, or if you
// refactor the enum to have different values, etc.
return mapString(v).with({
[MyEnum.Hello]: ':)',
[MyEnum.Goodby]: ':('
});
}); // produces [':(', ':)']
I would not call it general but I use this many times and may it will be handy for others too:
type TMyEnum = ':)'|':(';
class MyEnum {
static Hello: TMyEnum = ':)';
static Goodbye: TMyEnum = ':(';
}
console.log(MyEnum.Hello); // :)
console.log(MyEnum.Goodbye); // :(
Now you don't need any mapping function and it works as expected however you have to create separate similar class for every enum (which should not be a problem since you would do at anyway). The only drawback I can think now is that you can not iterate over it's properties. But until now it wasn't a problem for me I didn't need it. And you can add a static array to the class when you need it.
Maybe this will help you:
enum NumericEnums {
'PARAM1' = 1,
'PARAM2',
'PARAM3',
}
enum HeterogeneousEnums {
PARAM1 = 'First',
PARAM2 = 'Second',
PARAM3 = 3,
}
type EnumType = { [key: string]: string | number };
type EnumAsArrayType = {
key: string;
value: string | number;
}[];
const enumToArray = (data: EnumType): EnumAsArrayType =>
Object.keys(data)
.filter((key) => Number.isNaN(+key))
.map((key: string) => ({
key,
value: data[key],
}));
console.log(enumToArray(NumericEnums));
console.log(enumToArray(HeterogeneousEnums));
// Usage
enumToArray(HeterogeneousEnums).map(({ key, value }) => {
console.log(`${key}: ${value}`);
// Your necessary logic
return null;
});
Console result
This is a working function you can use. Below I'm passing ItemMaterial to getEnumKeys function and getting ["YELLOW", "WHITE", "ROSE", "BLACK"].
Similarly use the getEnumValues function to get values of the enum.
Take a look at the splitEnumKeysAndValues function to see how these variables extracted from the enum.
enum ItemMaterial {
YELLOW,
WHITE,
ROSE,
BLACK,
}
const keys = getEnumKeys<typeof ItemMaterial>(ItemMaterial)
const values = getEnumValues<typeof ItemMaterial, `${ItemMaterial}`>(ItemMaterial);
function getEnumKeys<TypeofEnum>(value: TypeofEnum): keyof TypeofEnum {
const { values, keys } = splitEnumKeysAndValues(value);
return keys as unknown as keyof TypeofEnum;
}
function getEnumValues<TypeofEnum, PossibleValues>(value: TypeofEnum): PossibleValues[] {
const { values, keys } = splitEnumKeysAndValues(value);
return values as unknown as PossibleValues[];
}
function splitEnumKeysAndValues<T>(value: T): { keys: keyof T, values: Array<string | number> } {
const enumKeys = Object.keys(value);
const indexToSplit = enumKeys.length / 2
const enumKeysKeyNames = enumKeys.slice(0, indexToSplit) as unknown as keyof T;
const enumKeysKeyValues = enumKeys.slice(indexToSplit);
return {
keys: enumKeysKeyNames,
values: enumKeysKeyValues,
}
}