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 '{}'.
Can I tell the interface to default the properties I don't supply to null? What would let me do this
No. You cannot provide default values for interfaces or type aliases as they are compile time only and default values need runtime support
Alternative
But values that are not specified default to undefined
in JavaScript runtimes. So you can mark them as optional:
interface IX {
a: string,
b?: any,
c?: AnotherType
}
And now when you create it you only need to provide a
:
let x: IX = {
a: 'abc'
};
You can provide the values as needed:
x.a = 'xyz'
x.b = 123
x.c = new AnotherType()
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.
x.b
and x.c
with null
. When writing let x = {a: 'abc'}
then x.b
and x.c
are undefined
, so this answer doesn't fully meet the requirements, although it's a smart quick fix.
let a: IX = Object.assign({b:true}, x);
will cause b,c to be optional also in instance object, which may not be desired
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
Partial<IXOptions>
. This way TS knows all properties will be present in this.options
, but none are required in the constructor.
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 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'
}
let a: IX = Object.assign({b:true}, x);
will cause error Partial<IX> cannot be assigned to IX
x
no longer implements IX
, but a partial of IX
. Partial
is good for places where every property might be optional, for example with an ORM, where you can pass a partial of an object interface and update only the fields that are defined (as opposed to undefined
which is what every field of a Partial
can be). For interfaces that have fields with default values, you can declare object literals implementing those type of interfaces, without having to declare the default values, using the let x: Partial<IX> = { /* non-default fields */ } as IX
syntax.
x
doesn't implement IX
, but you can still use it to create an object that implements IX
by using defaults. You need to provide defaults for all required values though, otherwise there's no guarantee of implementing IX
. E.g. const defaults: IX = { a: 'default', b: {}, c: new AnotherType() }; const y = { ...defaults, ...x };
I use the following pattern:
Create utility type Defaults<T>
:
type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T];
type Defaults<T> = Required<Pick<T, OptionalKeys<T>>>
Declare class with options/defaults:
// options passed to class constructor
export interface Options {
a: string,
b?: any,
c?: number
}
// defaults
const defaults: Defaults<Options> = {
b: null,
c: 1
};
export class MyClass {
// all options in class must have values
options: Required<Options>;
constructor(options: Options) {
// merge passed options and defaults
this.options = Object.assign({}, defaults, options);
}
}
Create class instance:
const myClass = new MyClass({
a: 'hello',
b: true,
});
console.log(myClass.options);
// { a: 'hello', b: true, c: 1 }
Default values to an interface are not possible because interfaces only exists at compile time.
Alternative solution:
You could use a factory method for this which returns an object which implements the XI interface.
Example:
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'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=1, c=99, d=88, e?)
foo(null, null, null, 3)
Since you have to supply all the parameters before the one you actually want (d).
Good practice to use is:
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
}
Another option without declaring an interface is:
foo({
a=undefined,
b=1,
c=99,
d=88,
e=undefined
})
Follow Up: In the previous function definition we define defaults for the fields of the parameter object, but not default for the object itself. Thus we will get an extraction error (e.g. Cannot read property 'b' of undefined
) from the following call:
foo()
There are two possible solutions:
const defaultObject = {a=undefined, b=1, c=99, d=88, e=undefined} function foo({a=defaultObject.a, b=defaultObject.b, c=defaultObject.c, d=defaultObject.d, e=defaultObject.e} = defaultObject) const defaultObject = {a=undefined, b=1, c=99, d=88, e=undefined} function foo(object) { const {a,b,c,d,e} = { ...defaultObject, ...object, } //Continue the function code.. }
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
It will not deep assign in this guise but it's not too difficult to modify deepCopyAssign to iterate and check types before assigning.
Functions and references will be lost by the parse/stringify process. I don't need those for my task and neither did the OP.
Custom init values are not hinted by the IDE or type checked when executed.
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;
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({});
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.
Or even
let x: Partial<IX> = { ... }
Which makes all the fields optional.
In any case you can use undefined
as a default value, it is just depends on your use case.
You can also have a helper method/function that returns the object with default property values, and then the calling code can override the defaults as needed. That's the approach I'm following as I've been faced with the same question in my current project. This way coding the default property value object is a one time affair, and you can reuse this object throughout your entire application.
Another way is to use https://www.npmjs.com/package/merge
This is the same as the last answer but a bit tidier.
Lets install merge
yarn add -D merge
Next lets create an interface with some options. We'll place this into ./types/index.ts
export interface ExampleOpts {
opt1: string,
opt2: string,
opt3: string,
}
Next lets create a set of defaults you could put this in the same file, but lets keep the types seperate and place it into ./config/index.ts
import { ExampleOpts } from '../types'
// Defaults
export const ExampleOptsDefault : ExampleOpts = {
opt1: 'default value 1',
opt2: 'default value 2',
opt3: 'default value 3',
}
Next lets join it all together with a function within ./index.ts
import { ExampleOpts } from './types'
import { ExampleOptsDefault } from './config'
import merge from 'merge'
// The ? makes the parameter optional
export function test1(options?: ExampleOpts) {
// merge tries to load in the defaults first, then options next if it's defined
const merged_opts: ExampleOpts = merge.recursive(ExampleOptsDefault, options)
// log the result to the console
console.log(merged_opts)
}
Another way is to use the Pick utility type and choose the properties you wish to set to required.
interface IX {
a: string,
b: any,
c: AnotherType
}
let x: Pick<IX, 'a'> = {
a: 'abc'
}
Then when you want to declare the real IX object, you just merge the defaults with new values, like so:
const newX: IX = {
...x,
b: 'b',
c: () => {}
}
This answer was taken from "How To Set Up A TypeScript Interface Default Value?"
Working through this now. Use a class
instead of an interface
.
class IX {
a: String = '';
b?: any;
c: Cee = new Cee();
}
class Cee {
c: String = 'c';
e: String = 'e';
}
I needed this for a React Component.
You can use the Nullish Coalescing Operator which will assign a default Value if the left Hand Value is Null or Undefined:
interface IX {
a: string,
b?: any,
c?: AnotherType
}
const ixFunction: React.FC<IX> = (props) => {
console.log(props.b?? "DefaultValue")
}
But this only works if you want to use the variable in only one place.
Success story sharing
any
undermines the purpose of TypeScript. There are other answers without this drawback.