I have the following interface in TypeScript:
interface IX {
a: string,
b: any,
c: AnotherType
}
I declare a variable of that type and I initialize all the properties
let x: IX = {
a: 'abc',
b: null,
c: null
}
Then I assign real values to them in an init function later
x.a = 'xyz'
x.b = 123
x.c = new AnotherType()
But I don't like having to specify a bunch of default null values for each property when declaring the object when they're going to just be set later to real values. Can I tell the interface to default the properties I don't supply to null? What would let me do this:
let x: IX = {
a: 'abc'
}
without getting a compiler error. Right now it tells me
TS2322: Type '{}' is not assignable to type 'IX'. Property 'b' is missing in type '{}'.
This question is related to
typescript
You can use the Partial
mapped type as explained in the documentation:
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html
In your example, you'll have:
interface IX {
a: string;
b: any;
c: AnotherType;
}
let x: Partial<IX> = {
a: 'abc'
}
You can implement the interface with a class, then you can deal with initializing the members in the constructor:
class IXClass implements IX {
a: string;
b: any;
c: AnotherType;
constructor(obj: IX);
constructor(a: string, b: any, c: AnotherType);
constructor() {
if (arguments.length == 1) {
this.a = arguments[0].a;
this.b = arguments[0].b;
this.c = arguments[0].c;
} else {
this.a = arguments[0];
this.b = arguments[1];
this.c = arguments[2];
}
}
}
Another approach is to use a factory function:
function ixFactory(a: string, b: any, c: AnotherType): IX {
return {
a: a,
b: b,
c: c
}
}
Then you can simply:
var ix: IX = null;
...
ix = new IXClass(...);
// or
ix = ixFactory(...);
You could use two separate configs. One as the input with optional properties (that will have default values), and another with only the required properties. This can be made convenient with &
and Required
:
interface DefaultedFuncConfig {
b?: boolean;
}
interface MandatoryFuncConfig {
a: boolean;
}
export type FuncConfig = MandatoryFuncConfig & DefaultedFuncConfig;
export const func = (config: FuncConfig): Required<FuncConfig> => ({
b: true,
...config
});
// will compile
func({ a: true });
func({ a: true, b: true });
// will error
func({ b: true });
func({});
I stumbled on this while looking for a better way than what I had arrived at. Having read the answers and trying them out I thought it was worth posting what I was doing as the other answers didn't feel as succinct for me. It was important for me to only have to write a short amount of code each time I set up a new interface. I settled on...
Using a custom generic deepCopy function:
deepCopy = <T extends {}>(input: any): T => {
return JSON.parse(JSON.stringify(input));
};
Define your interface
interface IX {
a: string;
b: any;
c: AnotherType;
}
... and define the defaults in a separate const.
const XDef : IX = {
a: '',
b: null,
c: null,
};
Then init like this:
let x : IX = deepCopy(XDef);
That's all that's needed..
.. however ..
If you want to custom initialise any root element you can modify the deepCopy function to accept custom default values. The function becomes:
deepCopyAssign = <T extends {}>(input: any, rootOverwrites?: any): T => {
return JSON.parse(JSON.stringify({ ...input, ...rootOverwrites }));
};
Which can then be called like this instead:
let x : IX = deepCopyAssign(XDef, { a:'customInitValue' } );
Any other preferred way of deep copy would work. If only a shallow copy is needed then Object.assign would suffice, forgoing the need for the utility deepCopy
or deepCopyAssign
function.
let x : IX = object.assign({}, XDef, { a:'customInitValue' });
Known Issues
deepCopyAssign
to iterate and check types before assigning.You can't set default values in an interface, but you can accomplish what you want to do by using Optional Properties (compare paragraph #3):
https://www.typescriptlang.org/docs/handbook/interfaces.html
Simply change the interface to:
interface IX {
a: string,
b?: any,
c?: AnotherType
}
You can then do:
let x: IX = {
a: 'abc'
}
And use your init function to assign default values to x.b
and x.c
if those properies are not set.
It's best practice in case you have many parameters to let the user insert only few parameters and not in specific order.
For example, bad practice:
foo(a, b, c, d, e)
Good practice:
foo({d=3})
The way to do it is through interfaces. You need to define the parameter as an interface like:
interface Arguments {
a?;
b?;
c?;
d?;
e?;
}
And define the function like:
foo(arguments: Arguments)
Now interfaces variables can't get default values, so how do we define default values?
Simple, we define default value for the whole interface:
foo({
a,
b=1,
c=99,
d=88,
e
}: Arguments)
Now if the user pass:
foo({d=3})
The actual parameters will be:
{
a,
b=1,
c=99,
d=3,
e
}
I understood it from the following link so big credit :) https://medium.com/better-programming/named-parameters-in-typescript-e32c763d2b2e
My solution:
I have created wrapper over Object.assign to fix typing issues.
export function assign<T>(...args: T[] | Partial<T>[]): T {
return Object.assign.apply(Object, [{}, ...args]);
}
Usage:
env.base.ts
export interface EnvironmentValues {
export interface EnvironmentValues {
isBrowser: boolean;
apiURL: string;
}
export const enviromentBaseValues: Partial<EnvironmentValues> = {
isBrowser: typeof window !== 'undefined',
};
export default enviromentBaseValues;
env.dev.ts
import { EnvironmentValues, enviromentBaseValues } from './env.base';
import { assign } from '../utilities';
export const enviromentDevValues: EnvironmentValues = assign<EnvironmentValues>(
{
apiURL: '/api',
},
enviromentBaseValues
);
export default enviromentDevValues;
Default values to an interface are not possible because interfaces only exists at compile time.
You could use a factory method for this which returns an object which implements the XI interface.
class AnotherType {}
interface IX {
a: string,
b: any,
c: AnotherType | null
}
function makeIX (): IX {
return {
a: 'abc',
b: null,
c: null
}
}
const x = makeIX();
x.a = 'xyz';
x.b = 123;
x.c = new AnotherType();
The only thing I changed with regard to your example is made the property c both AnotherType | null
. Which will be necessary to not have any compiler errors (This error was also present in your example were you initialized null to property c).
It is depends on the case and the usage. Generally, in TypeScript there are no default values for interfaces.
If you don't use the default values
You can declare x
as:
let x: IX | undefined; // declaration: x = undefined
Then, in your init function you can set real values:
x = {
a: 'xyz'
b: 123
c: new AnotherType()
};
In this way, x
can be undefined or defined - undefined
represents that the object is uninitialized, without set the default values, if they are unnecessary. This is loggically better than define "garbage".
If you want to partially assign the object:
You can define the type with optional properties like:
interface IX {
a: string,
b?: any,
c?: AnotherType
}
In this case you have to set only a
. The other types are marked with ?
which mean that they are optional and have undefined
as default value.
In any case you can use undefined
as a default value, it is just depends on your use case.
While @Timar's answer works perfectly for null
default values (what was asked for), here another easy solution which allows other default values: Define an option interface as well as an according constant containing the defaults; in the constructor use the spread operator to set the options
member variable
interface IXOptions {
a?: string,
b?: any,
c?: number
}
const XDefaults: IXOptions = {
a: "default",
b: null,
c: 1
}
export class ClassX {
private options: IXOptions;
constructor(XOptions: IXOptions) {
this.options = { ...XDefaults, ...XOptions };
}
public printOptions(): void {
console.log(this.options.a);
console.log(this.options.b);
console.log(this.options.c);
}
}
Now you can use the class like this:
const x = new ClassX({ a: "set" });
x.printOptions();
Output:
set
null
1
Source: Stackoverflow.com