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;
}
}
Related
I'm trying to declare a return type from a simple getter in TypeScript. That return type should conform to one of two static variables, associated with the class that implements them.
Take this code:
class Hairdryer {
public static ON = 'ON'
public static OFF = 'OFF'
private _state: Hairdryer.ON|Hairdryer.OFF = Hairdryer.OFF
get state (): Hairdryer.ON|Hairdryer.OFF {
return this._state
}
}
The TypeScript compiler doesn't like this, and throws errors:
'Hairdryer' only refers to a type, but is being used as a namespace here.(2702)
Return type of public getter 'state' from exported class has or is using private name 'Hairdryer'.(4043)
I know I could just have 'ON'|'OFF' as my return type, but I wanted to be able to reference the possible types from outside the class, without instantiating it, hence the use of static properties.
Here's a playground link.
Ill suggest you to use Enum instead of static values inside the class this is more TS friendly.
enum State {
ON = 'ON',
OFF = 'OFF'
}
class Hairdryer {
public static state = State;
private _state: State= State.OFF
get state (): State {
return this._state
}
}
You need to use an index access type on the static part of the class (typeof Hairdryer not Hairdryer)
type State = typeof Hairdryer['ON']| typeof Hairdryer['OFF'];
class Hairdryer {
public static ON = 'ON' as const
public static OFF = 'OFF' as const
private _state: State= Hairdryer.OFF
get state (): State {
return this._state
}
}
Playground Link
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();
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();
}
I try to create a user class and want to be able to inherit from a type alias:
type PlainUser = { email: string }
class User extends PlainUser {
constructor (initialValues: PlainUser) {
this.email = initialValues.email
}
update () { ... }
}
This doesn't work of course, but I would like to have the following semantics without having to duplicate email (and all the other fields that I don't show to keep it brief):
type PlainUser = { email: string }
class User {
email: string
constructor (initialValues: PlainUser) {
this.email = initialValues.email
}
update () { ... }
}
Is this possible with flow?
Not that I know of, but you can at least use implements to enforce that the User class implements the PlainUser interface (yes, you have to change it to be an interface).
interface PlainUser {
email: string;
}
class Foo implements PlainUser {
}
(tryflow)
The code above yields the following error with Flow v0.41, since Foo does not specify an email property:
7: class Foo implements PlainUser {
^ property `email` of PlainUser. Property not found in
7: class Foo implements PlainUser {
^ Foo
Of course, this isn't exactly what you've asked for. But at least you are getting automatic checking that User implements PlainUser, rather than nothing at all.
You can only extend from classes, and your type alias is an interface, so you have to use implement here. TypeScript Salsa allows doing the following since this suggestion was implemented:
type PlainUser = { email: string };
class User implements PlainUser {
constructor (initialValues: PlainUser) {
this.email = initialValues.email;
}
}
If you do not use salsa, you have to explicitly declare the inherited properties:
type PlainUser = { email: string };
class User implements PlainUser {
public email: string;
constructor (initialValues: PlainUser) {
this.email = initialValues.email;
}
}
Playground
I'll admit this was a head scratcher initially, but something like what you want to do is very possible. It does require rethinking the approach a bit.
First, you need to start with the class instead of the object literal. Intuitively this makes sense, as that's also the way javascript works.
class User {
email: string;
}
Next you want to use flow's $Shape transformation. This will cast your type to the enumerable properties of the class.
type PlainUser = $Shape<User>;
const Bob: PlainUser = { email: "bob#bob.com" }
or
const BobProperties: PlainUser = { ...new PlainUserClass("bob#bob.com") }
Finally, extend the User class as normal.
class AdminUser extends User {
admin: true;
}
example
I'm using React.js and Typescript and in the React.js store I store Javascript objects. Sometimes they're sent by the server, so they're just objects, they have no member functions. But I want member functions, so that instead of this:
// Interface and "external" member function for an object in the store:
interface User {
id: UserId;
isAdmin?: boolean;
isModerator?: boolean;
...
}
function isStaff(user: User) {
return user.isAdmin || user.isModerator;
}
if (isStaff(user)) {
showPowerOffDataCenterButton();
}
I can do this:
if (user.isStaff()) {
...
Is there any React or Javascript features or Typescript syntactic-sugar magic that can add member functions to the React store data structures? Please note that the objects are sometimes sent from the server as JSON and parsed with JSON.parse(..), so I don't think I can declare my own Javascript classes and add functions to their .prototype field (becasue I don't control the creation of the objects).
(I'm planning to use Redux later + some immutable-JS library, in case that matters)
Or if not possible, any workarounds?
You could just make a class that contains that information instead of having them in separate functions.
interface UserInfo {
id: string;
isAdmin?: boolean;
isModerator?: boolean;
}
class User {
constructor(private user: UserInfo) { }
public isStaff(): boolean {
return this.user.isAdmin || this.user.isModerator;
}
}
let user: User = new User({ id: "wqe" });
console.log(user.isStaff());
You can also make getters and setters for the properties, so you don't lose expresiveness.
class User {
constructor(private user: UserInfo) { }
public isStaff(): boolean {
return this.user.isAdmin || this.user.isModerator;
}
public get isAdmin() {
return this.user.isAdmin;
}
public set isAdmin(value) {
this.user.isAdmin = value;
}
}
You can then get or set isAdmin as you would on a normal object.
user.isAdmin = false;
You can also enforce that isAdmin can not be set by not making a setter for it. so the User class is immutable.