Correct way to instantiate class with optional attributes - javascript

I'm currently migrating an JavaScript module to TypeScript, and I'm encountering issues when strongly typing an object with optional attributes.
The example is simple:
I've got a method opening a modal. The method has a single parameter, which is a map of the different options for the modal, and are all optional.
As in JavaScript:
openConfirmDialog({text : "are you sure ?"});
openConfirmDialog({title : "Confirmation required", confirmAction : noop});
and so on.
Migrating to TypeScript, I declared a class representing all these different options:
class ConfirmDialogOptions {
text : string;
title : string;
confirmAction : () => any;
cancelAction : () => any;
}
but when trying to instantiate this class as we used to in plain JavaScript:
let confirmOptions : ConfirmDialogOptions = { text : "are you sure ?" };
the compiler raises an error:
Initialized type not assignable to variable type ConfirmDialogOptions
And I'm forced to force type casting using:
let a : ConfirmDialogOptions = {text: "lala"} as ConfirmDialogOptions;
This works, but well, it seems kind of like overheat, and globally makes me wonder what is the correct approach to instantiate a typed object which have many or all optional properties in TypeScript.
What is the correct approach?

Instead of class declare an interface with optional properties:
interface ConfirmDialogOptions {
text ?: string;
title ?: string;
confirmAction ?: () => any;
cancelAction ?: () => any;
}

What you're looking to use is TypeScripts interface feature, which you can apply to free floating objects:
interface ConfirmDialogOptions {
text?: string;
title?: string;
confirmAction?: () => any;
cancelAction?: () => any;
}
class doesn't do what you think it does... It's actually an ES6 feature that acts as sugar around vanilla JavaScript functions.
// ES6 Class
class Person {
constructor(name) {
this.name = name;
}
}
// Equivalent JS
var Person = (function () {
function Person(name) {
this.name = name;
}
return Person;
}());
Since you're defining a class TypeScript assumes you're intending for ConfirmDialogOptions to match instances from a new ConfirmationDialogOptions(...) call, which isn't type equivalent with a vanilla JavaScript object like { text: 'hello' }.

You can do this by making the parameters optional with a question mark
class ConfirmDialogOptions {
text ?: string;
title ?: string;
//
}
Or a better way to declare class variables is in the constructor
class ConfirmDialogOptions {
constructor(private text?:string, private title?: string) { }\
//
}
An other solution is to give the parameters a default value
class ConfirmDialogOptions {
constructor(private text:string="Default text", private title: string="Default title") { }
//
}

Related

Typescript nest types and interfaces, for better organization

Is there a way to nest types and interfaces for better organization?
eg. let myshark: Animals.Fish.Shark
To allow referring the nested Shark interface, what kind of implementation structure of the interfaces work?
One attempt could be =>
interface Animals{
interface Mamals{
interface Bat {
kind:string;
wings:number;
}
}
interface Fish {
interface Shark {
variant:string;
color:string;
fins:number;
}
}
}
To then use them as follows
let greatwhite: Animals.Fish.Shark;
let mybat: Animals.Mamals.Bat;
I have also tried using classes and namespaces? Is there a proper way to do this or is it not possible to get type declarations such as let myshark: Animals.Fish.Shark?
If you don't want to use separate modules for each namespace, you could use TypeScript-specific namespaces. Either you can define them directly so that Animals is an actual object at runtime that can hold properties as well as types (and you need to remember to export anything you want visible from the outside):
namespace Animals {
export namespace Mammals {
export interface Bat {
kind: string;
wings: number;
}
}
export namespace Fish {
export interface Shark {
variant: string;
color: string;
fins: number;
}
}
}
Or you can just declare the namespace and then anything mentioned within is automatically treated as exported (since otherwise they just wouldn't be declared):
declare namespace Animals {
namespace Mammals {
interface Bat {
kind: string;
wings: number;
}
}
namespace Fish {
interface Shark {
variant: string;
color: string;
fins: number;
}
}
}
Either way you will then be able to refer to the nested interfaces via dot notation, as desired:
let greatwhite: Animals.Fish.Shark; // okay
let mybat: Animals.Mammals.Bat; // okay
Playground link to code
Yes this is (partly) possible with namespace, see Docs. Though I suspect you may not be happy with the fact you cannot nest namespace objects in one another without getting an error:
"A 'declare' modifier cannot be used in an already ambient context.(1038)"
I personally have never done this as I don't (yet) see the benefit to it.
declare namespace GreetingLib {
interface LogOptions {
verbose?: boolean;
}
interface AlertOptions {
modal: boolean;
title?: string;
color?: string;
}
}
// Usage example
const aVariable: GreetingLib.LogOptions = {};
const anotherVariable: GreetingLib.LogOptions = {
verbose: false,
};
const someVariable: GreetingLib.LogOptions["verbose"] = false;
Note: you would have to initiate your variables with some kind of value.
TypeScript will not simply let you initiate these without providing some kind of value
(e.g. const aVariable: GreetingLib.LogOptions = {};)
In my opinion
I would much prefer the use of extending the types based on some kind of mutual properties:
"kingdom": "Plantae" | "Animalia" | ... ,

Return an instance of union type after spreading it

I have a general interface which I use to define properties (let's say it's an Animal).
Then I have extensions of this interface, which have additional different fields.
I want to define a function that gets an instance of an animal, spreads it - and parses a specific field.
The result value should be the same as the input's; however I get an error, as not all animals are equal :P
How can I use the spread operator and still return the same type?
interface AnimalProps { name: string; size: string; }
class Dog implements AnimalProps {
name: string;
size: string;
bark(): void { console.log(`${this.name} says: 'woof'`); }
constructor(name: string, size: string) {
this.name = name;
this.size = size;
}
}
class Bird implements AnimalProps {
name: string;
size: string;
fly(): void { console.log(`${this.name} says: 'I love flying'`); }
constructor(name: string, size: string) {
this.name = name;
this.size = size;
}
}
type Animal = Dog | Bird;
function addSizeUnits(animal: Animal): Animal {
// Property 'fly' is missing in type '{ size: string; name: string; }' but required in type 'Bird'.
return { ...animal, size: `${animal.size} meters` };
}
Obviously I'm simplifying the existing (legacy) code here, but the bottom line is that I do need to use fly and bark and therefore I need the actual union types - and not the basic Animal.
A generic function should work here. Something like:
function addSizeUnits<T extends Animal>(animal: T) {
return { ...animal, size: `${animal.size} meters` };
}
Note that I removed the return type annotation because the type inference should figure that out for you, but if you want to add it back you can use something like T & { size: string } correction, just T should be fine because you already have size in AnimalProps.
This can easily be addressed by using a generic constraint:
function addSizeUnits<A extends Animal>(animal: A): A {
return { ...animal, size: `${animal.size} meters` };
}
This ensures that your argument type and your return type are the same type of Animal.

Typescript interface for array object

I am currently accepting interface for my component and it accept the array object "options" as one of its arguments. My question is how do you create an interface for array object. I believe you need to use index signature for the interface but I havent used it before and not sure how it need to be strcuture.
Currently I uses arrow function. And here is how I declare my interface of my function
interface IOption {
key: number;
text: string;
value: number
}
Interface DropDownInputInt {
name: string;
value: any;
label: string;
disabled: boolean;
onChange: Function;
extraClassName: string;
required: boolean;
options: IOption[] | string;
multiple: boolean;
clearable: boolean;
index?: number;
placeholder: string;
toolTipMessage: string;
addCollon: boolean;
}
const DropDownInput = ({
name,
value,
label,
disabled,
onChange,
extraClassName,
required,
options,
multiple,
clearable,
index,
placeholder,
toolTipMessage,
addCollon
}: DropDownInputInt) => {
//MY CODES HERE
})
My second question is how do you create in interface that accept both string and object.
Do you mean something like this?
interface IOption {
key: number;
text: string;
value: number; // or any
}
function func(options: IOption[]) {}
but if you are accepting other arguments how do you declare it on interface? I hope there is a way without declaring everything inline
I am not quite familiar with React.js, but normally you should be able to do it with TypeScript anyway
interface DropDownInputInt {
name: string;
value: any;
label: string;
// bla bla ...
}
const DropDownInput = (dropdownOptions: DropDownInputInt) => {
// your code here
}
As for your other question
My second question is how do you create in interface that accept both string and object.
Here is an example (see the pipe |):
interface DropDownInputInt {
somethindThatAcceptsBothStringAndObject: string | object
// bla bla ...
}
I tried to incorporate your answer in my code, see my change above but somehow it throws error inside the body of my function saying "Property 'value' does not exist on type 'string | IOption'. when I tried drilling down the options.value
Ok, so as per your DropDownInputInt interface, you accept string and IOption[] types for the options property. Since you have two different types, you should check the options type like so:
const DropDownInput = (dropdownOptions: DropDownInputInt) => {
// ...
if(typeof dropdownOptions.options === 'string') {
// you have a string here
} else if(Array.isArray(dropdownOptions.options) && dropdownOptions.options.length > 0) {
// you have IOption array here, which is not empty
dropdownOptions.options[0].value; // accessing the value of the first option
dropdownOptions.options[1].value; // accessing the value of the second option
// etc. or simply use a for-loop to evaluate the options
}
}

Define typescript class method

I cannot find any information about "declaration and then initialization" of class method, for example can I do this (code below), first declare getName() and then initialize it, tslint tips me that I cannot do this, then what I should do, if doesn't want construction, like public getName(name: string): string { return this.name }?
class Cat {
public getName(name: string): string;
constructor() { ... }
getName(name) {
return this.name;
}
}
One reason for having separate "declaration and then initialization" is that it helps to separate public interface from private implementation.
In TypeScript, one can do that by using interface as declaration and class as initialization. Also, TypeScript has module system built-in the language, so instead of having some things public and some things private in the class, you can just make the whole class to be a private implementation detail, not exported and not available outside the module:
export interface Cat {
getName(name: string): string;
}
// NOTE: this whole class is an implementation detail and is not exported
class CatImpl implements Cat {
name: string;
constructor() { }
getName(name: string) {
return this.name;
}
}
// this is exported and is publicly available for creating Cats
export function createCat(): Cat {
return new CatImpl();
}

Force a function to accept only object of a specific structure (in typescript / js)

I know some of typescript's advantages are enabling type-safe functions -
but is it possible to ensure my function can only get objects with specific keys, or in other words - objects of specific structure ?
I know of many elegant ways to test if a nested key exists, such as [this one][1] ,
and I can of course run a small check at the beginning of my function - but the reason I'm asking this is because my function will be used by many other programmers - and I want to ensure they can understand what input they should insert just from the function's signature.
Example:
function printName(user){
console.log(user.details.name); // I want to ensure details.name exist
}
and I would wish to have some feature like:
function (user : {details: {name : string}}){
//same ...
}
[1]: https://stackoverflow.com/questions/2631001/javascript-test-for-existence-of-nested-object-key#answer-4034468 "this one"
interface User {
details:{name: string}
}
function printName(user:User){
console.log(user.details.name); // I want to ensure details.name exist
}
This is exactly the feature you desire:
function printName(user: { [key: string]: any, details: { [key: string]: any, name: string }}) {
console.log(user.details.name)
}
Allow any properties and require details + name.
More legible and protected against unintentional changes:
// oftentimes you put the following interfaces in extra files:
interface Details {
readonly [key: string]: any // = allow other properties than "name"*
readonly name: string
}
interface User {
readonly [key: string]: any // = allow other properties than "details"*
readonly details: Details
}
// *) consider explicitly specifying them
function printName(user: User) {
console.log(user.details.name)
}
And use the following code if you know other developers may call your function from plain JavaScript code (not TypeScript code):
function printName(user: User) {
const name = ((user || <User>{}).details || <Details>{}).name
if(name == void 0) {
console.error("user name missing")
}
console.log(name)
}
Code on TypeScript Playground
Use keyof if you want to retrieve a value from a specific property, this will highlight the incorrect property name.
You can also use Object.keys to check if the property exists.
interface UserProps {
name: string;
age: number;
}
export class User {
constructor(private data: UserProps) {}
get(propName: keyof UserProps): string | number {
const dataKeys = Object.keys(this.data);
if (!dataKeys.includes(propName)) {
throw Error(`Property ${propName} does not exists on user object`);
}
return this.data[propName];
}
}
You can use interfaces in typescript
export interface Details{
name:string,
age: number
}
export interface User{
details : {
[key: string]: Details
};
}
function printName(user : User){}

Categories