Define typescript class method - javascript

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();
}

Related

Decorators and Private fields javascript

I find myself trying to use decorators with the native javascript private properties (#) and these first 'recognize' that are in use do not work.
I identified this by using the class-validator decorators on the private properties of my value objects.
The error I get in my code editor is: Decorators are not valid here
Example:
import { IsString } from 'class-validator';
Class Person {
#IsString()
#name: string;
constructor(name: string) {
this.#name = name;
}
get name(): string {
return this.#name;
}
}
Okey as suggested by VLAZ:
Private fields in JS are completely private and inaccessible to anything from outside. Thus it makes sense they cannot be decorated - there is no way for the decorator to access them.
This is completely correct, so when I took a closer look at the value object I realized that it does have public get properties, so by testing it is possible to use decorators on those properties.
Leaving something like:
import { IsString } from 'class-validator';
Class Person {
#name: string;
constructor(name: string) {
this.#name = name;
}
#IsString()
get name(): string {
return this.#name;
}
}

A factory function that accepts class method call and returns an instance of this class

I will start with overview of desired end result I'm trying to achieve:
class Dog {
public static withName(name: string) {
this.name = name;
}
constructor() {
//
}
//
}
class Cat {
public static withName(name: string) {
this.name = name;
}
constructor() {
//
}
//
}
function getAnimal(...) { ... }
let dog = getAnimal(Dog.withName('Rex'))
dog.bark();
dog.meow(); // TS Error: method 'meow' does not exist on type 'Dog'
let cat = getAnimal(Cat.withName('Fluffy'))
dog.meow();
dog.bark(); // TS Error: method 'bark' does not exist on type 'Cat'
Of course this is pretty much a simplified almost-pseudo-code.
So the idea is next: a function that accepts a class with called static method on it (in order to pre-setup some properties, e.g. withName).
Then this function returns an instance of this class with pre-setuped property.
function getAnimal(classWithStaticMethodCall: ???) {
return new classWithStaticMethodCall()
}
It's not a big deal to achieve something like this:
function foo(bar) {
return new bar()
}
But the thing is that I want TypeScript to somehow figure out WHAT EXACT CLASS I'm passing and not just return an instance of this class, but the instance of this class with pre-setuped property (with the help of static method withName()).
After trying different things I'm starting to thinking that this is not doable because I'm passing the method and not the class itself (Dog.withName() not Dog).
The thing is that I actually saw this API before and now I'm trying to replicate it.
To sum up:
let dog = getAnimal(Dog.withName('Lucky')) // Property name is now set on this exact dog
dog.bark();
dog.name // 'Lucky'
First off we need to break down what your code looks like and realize it doesn't do what you think it does.
let dog = getAnimal(Dog.withName('Rex'))
is the same as
const dogWithName = Dog.withName('Rex');
let dog = getAnimal(dogWithName);
In this case dogWithname is calling a function with a void return thus will be undefined
What it appears you want is:
let dog = getAnimal(Dog);
dog.withName('Rex');
but you don't want to have to call .withName. This can be easily accomplished with an interface. You should use an interface because you want each type passed to your factory to have this method so it can be configured and typescript can validate the method exists.
If you don't use an interface and use the code suggested in Dave Cousineau Answer, then there is no point in the getAnimal() method if all it does is call a passed in method (a layer with no additional benefit). Additionally, future programmers won't necessarily call getAnimal() as there is no restriction on the type passed in, but if they do, there is no guarantee they will call .withname() either.
But the thing is that I want TypeScript to somehow figure out WHAT EXACT CLASS I'm passing and not just return an instance of this class, but the instance of this class with pre-setuped property (with the help of static method withName()).
So assuming you don't use a static method, but an instance method with an interface take a look at the following.
interface WithName {
withName(name: string): void;
}
class Dog
implements WithName {
public name: string;
withName(name: string): void { this.name = name; }
}
class Cat
implements WithName {
public description: sring;
withName(name: string): void { this.description = name;}
}
class AnimalFactory {
public static getAnimal<TResult extends WithName>(animal: new () => TResult, name: string) : TResult {
const result = new animal();
result.withName(name);
return result;
}
}
Example usage:
const sparky = AnimalFactory.getAnimal(Dog, 'Sparky');
const mrmeow = AnimalFactory.getAnimal(Cat, 'Mr. Meow');
Each implementation of WithName can do whatever you want with the value, it doesn't have to set 'name' on every type, just accept it as a value. (there is no problem with it doing the same thing, but then I'd wonder why not use an abstract class instead with a required super() call?
public abstract class WithName {
constructor(public name: string) { }
}
public Dog extends WithName {
constructor(name: string) {
super(name);
}
}
public Cat extends WithName {
constructor(name: string) {
super(name);
}
}
It just seems the GetAnimal() method is superfluous.
I guess you want something like the following. Basically just accepting a generic function that returns the type. TypeScript will be able to infer the type since it's generic.
Dog
class Dog {
constructor(
private mName: string
) {
}
// function that returns a function
public static withName(name: string) {
return () => new Dog(name);
}
public Bark() {
console.info(this.mName + " says 'Bark'.");
}
}
Cat
class Cat {
constructor(
private mName: string
) {
}
public static withName(name: string) {
return () => new Cat(name);
}
public Meow() {
console.info(this.mName + " says 'Meow'.");
}
}
Example
// function that takes a function, specifically one that returns a 'T'
// and then itself returns a 'T', the result of calling that function
function getAnimal<T>(builder: () => T): T {
return builder();
}
let dog = getAnimal(Dog.withName("Rex"));
dog.Bark();
dog.Meow(); // error Dog cannot Meow
let cat = getAnimal(Cat.withName("Fluffy"));
cat.Bark(); // error Cat cannot Bark
cat.Meow();
Another way you can do this kind of thing is as follows. Basically, you can actually pass the constructor itself as an argument, and type inference will still work. This may be closer to what you were originally looking for.
Dog and Cat
class Dog {
constructor(private mName: string) { }
public Bark() {
console.info(this.mName + " says 'Bark'.");
}
}
class Cat {
constructor(private mName: string) { }
public Meow() {
console.info(this.mName + " says 'Meow'.");
}
}
Example 2
// accepting a constructor as an argument, and additional arguments to pass to it
// this is not strictly necessary in the example, this is only a demonstration
// of the concept, which definitely has uses in the right situation
function getAnimal<T>(
withName: new (name: string) => T,
name: string
): T {
return new withName(name);
}
let dog = getAnimal(Dog, "Rex");
dog.Bark();
dog.Meow(); // error Dog cannot Meow
let cat = getAnimal(Cat, "Fluffy");
cat.Bark(); // error Cat cannot Bark
cat.Meow();

Interface not being applied when Error class is extended in TypeScript [duplicate]

This question already has answers here:
Typescript - Extending Error class
(8 answers)
Closed 4 years ago.
I have an Exception1 class that extends the Error class of JavaScript, along with that it also implements an interface SampleInterface
interface SampleInterface {
func(): string;
}
class SampleClass {
public message: string;
constructor(message: string) {
this.message = message;
}
}
class Exception1 extends Error implements SampleInterface {
constructor(message: string) {
super(message);
}
public func(): string {
return "Exception1"
}
}
On executing console.log(new Exception1('a').func()) in the console I get this error
Uncaught TypeError: (intermediate value).func is not a function
at <anonymous>:1:33
However this works as expected if the class extends some other class for example
class Exception2 extends SampleClass implements SampleInterface {
constructor(message: string) {
super(message);
}
public func(): string {
return "Exception2"
}
}
On executing console.log(new Exception2('a').func()) I get the expected output Exception2
When you extend built-in types like Array, Error, or Map, the prototype chain is a bit messed up, which you need to fix explicitly, so that the members defined in your class become available to the instances of your class. This is pointed out in the TypeScript docu.
So, in order to fix this, you need to do something like below:
interface SampleInterface {
func(): string;
}
class Exception1 extends Error implements SampleInterface {
constructor(message: string) {
super(message);
// Explicitly fix the prototype chain
Object.setPrototypeOf(this, Exception1.prototype);
}
public func(): string {
return "Exception1"
}
}
console.log(new Exception1('a').func()) // and now it works

Correct way to instantiate class with optional attributes

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") { }
//
}

Implementing prototypes for interfaces in TypeScript

I have created a TypeScript interface for my service results. Now I want to define a basic functionality for both my functions inside. The problem is I get an error:
The property 'ServiceResult' does not exist on value of type 'Support'.
I use WebStorm for development (VS2012 makes me nervous because on freezes by large projects - waiting for better integration:P).
Here's how I do it:
module Support {
export interface ServiceResult extends Object {
Error?: ServiceError;
Check?(): void;
GetErrorMessage?(): string;
}
}
Support.ServiceResult.prototype.Check = () => {
// (...)
};
Support.ServiceResult.prototype.GetErrorMessage = () => {
// (...)
};
I have also tried to move my prototypes into the module, but same error still... (of course I removed Support. prefix).
You can't prototype an interface because the compiled JavaScript does not emit anything related to the interface at all. The interface exists purely for compile-time use. Take a look at this:
This TypeScript:
interface IFoo {
getName();
}
class Foo implements IFoo {
getName() {
alert('foo!');
}
}
Compiles to this JavaScript:
var Foo = (function () {
function Foo() { }
Foo.prototype.getName = function () {
alert('foo!');
};
return Foo;
})();
There is no IFoo in the result, at all - which is why you are getting that error. Typically you wouldn't prototype an interface, you would prototype a class that implements your interface.
You don't even have to write the prototype yourself, just implementing the interface as a class is enough and the TypeScript compiler will add the prototype for you.
It looks like you are trying to add implementation to an interface - which isn't possible.
You can only add to a real implementation, for example a class. You may also decide to just add the implementation to the class definition rather than directly using prototype.
module Support {
export interface ServiceResult extends Object {
Error?: ServiceError;
Check?(): void;
GetErrorMessage?(): string;
}
export class ImplementationHere implements ServiceResult {
Check() {
}
GetErrorMessage() {
return '';
}
}
}
Support.ImplementationHere.prototype.Check = () => {
// (...)
};
Support.ImplementationHere.prototype.GetErrorMessage = () => {
// (...)
};

Categories