'Why does Typescript not like it when I use a simple object for an interface that has all optional props?
I am trying to provide a mock for uirouter's StateService... I am simply doing:
beforeEach(() => {
stateService = jasmine.createSpyObj('StateService', ['go']) as StateService;
}
...
it('calls go', () => {
// ...
let options = { location: 'replace', inherit: true };
expect(stateService.go).toHaveBeenCalledWith('foo', params, options);
});
I get:
error TS2345: Argument of type '{ location: string; inherit: boolean; }' is not assignable to parameter of type 'Expected'. Type '{ location: string; inherit: boolean; }' is not assignable to type '{ location?: ExpectedRecursive<boolean | "replace">; relative?: ExpectedRecursive<string | StateObject | StateDeclaration>; ... 8 more ...; source?: ExpectedRecursive<...>; }'. Types of property 'location' are incompatible. Type 'string' is not assignable to type 'ExpectedRecursive<boolean | "replace">'. expect(stateService.go).toHaveBeenCalledWith('foo', params, options);
Yet if I do it all inline without a variable assignment:
it('calls go', () => {
// ...
expect(stateService.go).toHaveBeenCalledWith('foo', params, { location: 'replace', inherit: true });
});
Then it works fine...
uirouter's TransitionOptions interface is defined like:
export interface TransitionOptions {
location?: boolean | 'replace';
relative?: string | StateDeclaration | StateObject;
inherit?: boolean;
notify?: boolean;
reload?: boolean | string | StateDeclaration | StateObject;
custom?: any;
supercede?: boolean;
reloadState?: StateObject;
redirectedFrom?: Transition;
current?: () => Transition;
source?: 'sref' | 'url' | 'redirect' | 'otherwise' | 'unknown';
}
Where everything is optional... Why does Typescript not like me assigning this to a simple object as a variable?
Solution 1:[1]
The type of this line:
let options = { location: 'replace', inherit: true };
is
options: { location: string, boolean: true }
This is because the type of 'replace' is expanded to string. Because of this, string is too wide to assign to location which is expected to be 'replace' | boolean | undefined.
To tell Typescript that 'replace' is not a string and should only be replace, use as const.
let options = { location: 'replace' as const, inherit: true };
Solution 2:[2]
This is because when you declare by inferring the type of the variable from the assigned value, it will generalize the value of the location property to string, making it invalid for the replace value, as replace will just be the property value, not its type.
To fix this you can infer the type of the variable in the declaration, like this:
function foo(param: TransitionOptions) {
console.log('foo', param)
}
foo({ location: 'replace', inherit: true })
let bar: TransitionOptions = { location: 'replace', inherit: true }
foo(bar)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | samthecodingman |
| Solution 2 | Edu Müller |
