I have class where I set type of range to IntervalRange
export class Test {range: IntervalRange;}
then in parent class I initialize the value:
export class TestInitializer {
Create(){
return <Test>{
range: IntervalRange.initialize(Database.GetByName('MONTH').id};
}
InitializeElement() {
let test = <Test>JSON.parse(JSON.stringify(configuration));
return test;
}
then in my other component I use this as:
#Input() range: IntervalRange;
However on function range.getRange();
I get: ERROR TypeError: this.range.getRange is not a function
Why is that? it says range is an Object, though it should be IntervalRange.
I tried writing as IntervalRange, <IntervalRange> range
nothing worked. How to fix that?
Update: let type = typeof(this.range); prints "object"
method:
ngOnChanges() {
if (this.range) {
let type = typeof(this.range);
let ddd = this.range.getRange(); //<----- this is where I get error
}
A typecast only casts the type. Typescript doesn't exist at runtime. Therefore if JSON.parse doesn't return a proper Test instance (which it won't since methods won't get serialized), it will fail at runtime. Instead of typecasting you probably want to build up a Test instance, and load the serialized data into it.
The TypeScript "cast" (<A>tmp or tmp as A) :
makes structural type compatibility checking → it is unavailable if the source data has type any.
has no runtime counterpart → TypeScript types can be compatible but runtime types can differ, for instance an object literal vs a real class instance.
When the data comes from an unsafe/external source, e.g. JSON.parse() or a WebAPI call, it's a DTO (data transfer object), with the any type. The TypeScript "cast" is unsafe too.
To ensure the cast operation, you can use a mapping function from the DTO to the "domain model" class. The key point is to return a real class instance.
Object.assign(new IntervalRange(), this.range) (that you mentioned in a comment) satisfies this point.
We can be even more stricter with a "field by field mapping": this.a = range.a; this.b = range.b; ...).
Both options are better encaspulated in a function: the class constructor, a static factory method in the class, an external function in the same module.
Related
I am totally new to TypeScript, just started learning it 3 days ago. While I was playing around with TypeScript(interfaces), I came across a problem. When I try to do this, it gives me an error:
const divEl:HTMLElement = document.createElement("div");
divEl.someRandomThing = "Some random thing!";
ERROR: error TS2339: Property 'someRandomThing' does not exist on type 'HTMLElement'.
How do I add a custom property(type) to HTMLElement(this interface is from lib.dom.d.ts)?
I tried this:
interface IMyInterface extends HTMLElement {
someRandomThing: string
}
const divEl:IMyInterface = document.createElement("div");
divEl.someRandomThing = "Some random thing!";
It gives me another error:
ERROR: error TS2741: Property 'someRandomThing' is missing in type 'HTMLDivElement' but required in type 'IMyInterface'.
If I add a question mark to "someRandomThing", the error disappear:
interface IMyInterface extends HTMLElement {
someRandomThing?: string //<-----
}
Do I always have to add a question mark? is there a way that I don't need to use the question mark and just add the property(type) into HTMLElement(this interface is from lib.dom.d.ts) interface?
How do I add a custom property(type) to HTMLElement(this interface is from lib.dom.d.ts)?
Your interface is fine for doing that, the problem is that what you get back from createElement doesn't have your new property, so it doesn't satisfy the requirements of your new interface type, so TypeScript warns you of that since that's TypeScript's primary job. :-)
When you need to extend an object in this way, Object.assign is a handy way of doing one-offs:
const div = Object.assign(
document.createElement("div"),
{ someRandomThing: "Some random thing!"}
);
The return type of Object.assign is an intersection of the types of its inputs, in this case HTMLElement & { someRandomThing: string; }.
For something reusable, you could write a new function that returns IMyInterface, handling the change in the type of the object internally. That function could either use Object.assign as above, or just directly assign to the object by internally using a minor type assertion:
function createExtendedElement(tagName: string, someRandomThing: string): IMyInterface {
const element: Partial<IMyInterface> = document.createElement(tagName);
element.someRandomThing = someRandomThing;
return element as IMyInterface;
}
// Usage:
const divEl = createExtendedElement("div", "Some random thing!");
The as IMyInterface is a type assertion. In general, avoid type assertions, but in well-contained contexts like this kind of utility function, they're okay.
You could generalize that function if you wanted by making it generic and supplying both the property name and value:
function createExtendedElement<Key extends PropertyKey, Value extends any>(
tagName: string,
propName: Key,
value: Value
): HTMLElement & {[P in Key]: Value} {
const element = Object.assign(
document.createElement(tagName),
{[propName]: value}
);
return element as HTMLElement & {[P in Key]: Value};
}
// Usage:
const divEl = createExtendedElement("div", "someRandomThing", "Some random thing!");
Playground link for the above.
One thing you might have noticed looking at the above is that with the very first inline version, the type of the element was HTMLDivElement (plus someRandomThing), not jut HTMLElement (plus someRandomThing). But the other versions make it just HTMLElement (plus someRandomThing).
You can fix that by making the tag name generic as well, lifting the definition of the tag parameter and return type from the ones on createElement:
function createExtendedElement<
Tag extends keyof HTMLElementTagNameMap,
Key extends PropertyKey,
Value extends any
>(
tagName: Tag,
propName: Key,
value: Value
): HTMLElementTagNameMap[Tag] & {[P in Key]: Value} {
const element = Object.assign(
document.createElement<Tag>(tagName),
{[propName]: value}
);
return element as HTMLElementTagNameMap[Tag] & {[P in Key]: Value};
}
// Usage:
const divEl = createExtendedElement("div", "someRandomThing", "Some random thing!");
In that, divEl's type is HTMLDivElement & { someRandomThing: string; }.
Playground link
Side point: It's largely tangential to the type-related aspects above, but in general, adding your own properties to objects that come from outside your code (in this case, the DOM). (This isn't a TypeScript thing, just a coding thing.) There are a few reasons:
It's really not your object to modify, from an engineering perspective. You can't be sure what that will do to the code that owns the object.
There's potential for conflicts with other code running on the page (including browser extensions). Suppose your code adds a marker property to an element, and other code in the page also uses a custom marker property on elements? Suddenly your code and the other code are cross-talking and may interfere with each other.
If done in enough code or a successful enough library, it makes life difficult for (in this case) the WHAT-WG to add new standard properties to DOM elements. Here's a concrete example of that: In ES5, JavaScript's arrays got a new standard method called some. It returns a flag for whether any element in the array matches a predicate you supply. So why is it called "some" rather than "any"? Because there was a significant library out in the wild that added its own method called any to arrays, and the way the library was written, adding a standard one would have broken code on the websites using that library. So we're stuck with some, which doesn't really make sense but was the best available.
In the specific case of the DOM, adding your own property does work reliably on all modern browsers and there's a long history of doing it (jQuery's done it since v1, though with increasingly arcane names). If you choose to do it, make sure you use a name that is very unlikely to conflict with anything now or in the future, or better yet, use a Symbol as the property key instead, which is guaranteed unique (if you don't make it globally accessible via Symbol.for).
Whenever you want to add your own information to objects that come from outside your code (and aren't meant to be "owned" by your code), there are at least two things you can do rather than adding your own properties to the objects:
Use composition. For example:
const something = {
element: document.createElement("div"),
someRandomThing: "Some random thing!"
};
Now the element and someRandomThing are just held together in the same container. Whenever you need someRandomThing, you use something.someRandomThing. When you need the element, you use something.element.
Use a WeakMap keyed by the element. For example:
// Somewhere you can access it wherever you need it in your app:
const elementInfo = new WeakMap();
// ...
// Now when you're associating information with the element:
const element = document.createElement("div");
elementInfo.set(element, {
someRandomThing: "Some random thing!"
});
The extra information is in the WeakMap keyed by the actual element instance. When you want the element, use element. When you want the information you have for it, use elementInfo.get(element)?.someRandomThing. If the element is removed from the DOM at some point and all other references to it are removed, the WeakMap doesn't prevent the element from being garbage-collected. Instead, the entry for it in the map disappears.
Just FWIW!
I have an object with structure like this: {prop_1: function_1, prop_n: function_n}. I use jest to unit-test my code, which you may check here: https://jestjs.io.
Functions provided by prop_${i} have some requirement object {prop_1:requirementCallback_1, prop_n: requirementCallback_n} needed such that requirementCallback_${i}, for example, that given function returns a defined value or it asserts some expected number of input arguments. Therefore, it is advisable from my point of view that we run requirement values against function_{i} to verify such conditions.
I am not familiar to Typescript, but its name suggests that the language may have some out-of-the-box solution to check such conditions. What do you think about this scenario?
Does the object have dynamic properties? then try:
//change void to your appropriate return type
type MyObjectType = Record<string, (someStringArg: string, someNumberArg: number) => void >
If you are trying to have defined properties with same funtion types then try:
type SameFunctionType = (string_arg1: string, number_arg2: number)=>void//or any return type for the function
interface MyObjectType{
prop_1: SameFunctionType;
prop_n: SameFunctionType;
}
If you are trying to have defined properties with different funtion types then try:
interface MyObjectType{
prop_1: ()=>number;
prop_2: (arg1: Date)=>number;
}
Say you have a Typescript class like this:
class CompExt extends Comp {
public static sum(a: number, b: number): number {return a+b};
};
The function sum will be sligtly different from the original class and it must be static.
Your actual code is
let ff: Function = CompExt;
console.log('the summ works fine',ff.sum(1,2));
Both the code editor and compilation will warn me this:
bas.ts(47,16): error TS2339: Property 'sum' does not exist on type 'Function'.
Is there a way to avoid compilation errors? If I use any rather than Function everthing is fine but I was wondering if Typescript supports such style.
Just don't specify the type to be a Function because it's not a generic function, it's CompExt.
If you really want to specify the type, you can use: let ff: typeof CompExt = CompExt.
A possible solution to your situation can be provided using decorators. Check this answer Static property on a class
Suppose I have a function that needs to return something of type StringMap<string, boolean>. An example return that is valid is: {"required": true}.
Now, I've read in a tutorial (it's not important which tutorial) you can create a function that has return type of { [s: string]: boolean } and this is the same return type as the StringMap above.
I don't understand how are these two the same return type? And how the second version is even valid?
All the return types I have seen in TypeScript have only included the type in the past i.e. boolean, number, any. For example function (): number {}. In our second version we use s: string which means we give the variable a name, and specify it's type, how are we suddenly allowed to give the variable the name s?
On top of that we put this string inside an array [s: string] as the key in the second version (therefore the key is now an array). While a StringMap has a string as the key.
The syntax is a bit different than you think. It's a unique syntax for defining dictionaries\maps.
{ [s: string]: boolean } means: a map, which has a key with type string, and it's values are boolean. The s means nothing at all, it could have been anything you want.
(Then why give it a name in the first place? my guess is to make the code more clear, when mapping more complex types. Sometimes you'll want to call the index id, sometimes address, etc..)
More info here, indexed types is what you want.
The Typescript handbook online isn't the most friendly documentation ever, but I think it's good enough and I recommend everyone who uses typescript to at least skim through it. Especially since in 2.0+ they added a bunch of crazy\awesome type features like mapped types.
The type { [s: string]: boolean } defines an indexable type interface.
What you see as an array is just the syntax decided to define the index of the interface.
The name of the key, as far as I know, is ignored and only the type is what matters.
This code { [s: string]: boolean } is defining an indexable interface where the indices are strings and the values are booleans.
I assume that the definition of StringMap is as follows:
export interface StringMap<T, U> = { [s: T]: U };
Which is kind of redundant if you ask me (as the name says that it should be a string map, so the keys should be strings). I would have declared the IStringMap interface as:
export interface IStringMap<T> = { [key: string]: T };
Interfaces in TypeScript just define the "shape" of the object. The previous three definitions have equivalent shapes, so this is perfectly valid:
function fn() : IStringMap<boolean> {
let myMap : StringMap<string, bool> = { };
myMap["foo"] = true;
myMap["bar"] = false;
myMap["baz"] = true;
return myMap;
}
let foo: { [bazzinga: string]: boolean } = fn();
I have an AngularJS service coded in Typescript. I call the functions of the service with a parameter like this setFalse('retrieve'):
class StateService implements IStateService {
state = [];
get = (property: string) => {
return this.state[property];
}
setFalse = (property: string) => {
this.state[property] = false;
}
setNull = (property: string) => {
this.state[property] = null;
}
setTrue = (property: string) => {
this.state[property] = true;
}
// more code here
}
Is there some way that I could remove the need for the quoted string 'retrieve', use a constant or check the values being used before run time?
1. Is there some way that I could remove the need for the quoted string 'retrieve'?
You could desugar the overload, and provide lots of different versions of the function.
//just to give the idea
function setFalse_retrieve(){ return setFalse('retrieve') }
The upside of doing this is that its really typesafe and there is no way to ever call setFalse with a bad parameter. The downside is that there is lots of boilerplate and you can't pass around a property value if you want to.
2. Use a constant?
Typescript has an enum feature for this:
enum Properties { retrieve, frobnicate };
You can now use Properties.retrieve instead of "retrieve" in your code and it will catch any typos in the enum name.
Properties.retriev; // Error: The property 'retriev' does not exist on value of type 'typeof Properties'.
Just be aware that Typescript makes the enum values be integers so you will need to convert them to strings when calling the Angular functions:
var enumProperty = Properties.retrieve; // 0
var strProperty = Properties[enumProperty]; // "retrieve"
The downside of the enum approach is that you can pass any number where an enum value is expected and the error will not be detected at runtime (do don't do that):
var x:Property = 10; // :(
3. Use a constant or check the values being used before run time
Typescript has function overloading on function constants but AFAIK, you can only use it to specialize return types depending on inputs, not to restrict your valid inputs to a set of constants. That is, you would still need to have a generic case that accepts any string, which is not what you want.