I have an abstract class that does new this(), however, it isn't creating an instance of itself, but it is creating an instance of the class that extended it.
This works in JavaScript when compiled and returns the proper class. However, TypeScript is complaining.
Cannot create an instance of an abstract class.
abstract class Model {
static find<T extends Model>(someVar) {
let inst = new this() as T
// Do some extra stuff with the instance
return inst
}
}
class A extends Model { }
A.find()
The first problem of your solution - it isn't safe, T type may not contain empty constructor. The second problem: even if you call find() on A class, return type will be inferred as Model if it isn't set explicitly.
Because anyway you should specify A type when you call find, it would be better to pass the type constructor to the find method, in this case both problems will be solved.
abstract class Model {
static find<T extends Model>(ModelClass: new () => T, param: string): T {
let inst = new ModelClass()
// Do some extra stuff with the instance
return inst;
}
}
class A extends Model {}
const a = Model.find(A, "param");
Related
I have a web component that only works when some properties are set, when I create the component with Document.createElement() I can't pass properties then an error happens in my component.
Is it possible to create the component by creating an instance of the component class or something similar and be able to pass parameters to it's constructor?
customElements.define('x-card', class extends HTMLDivElement {
//...
}, { extends: 'div' });
You can create a new instance using new WCard. This requires having access to the class though.
Please note that custom element names preferrably should not start with x-.
So change your code:
export class WCard extends HTMLDivElement {
//...
}
customElements.define('w-card', WCard, { extends: 'div' });
window.WCard = WCard;
This allows you to call new on the class globally, or if your code imports the class.
const wcard = new WCard(...args);
I tried to create a base class that has a static utility method to construct the class, which uses return new this(). But it doesn't work when I have a subclass extending it, because that utility method returns the base class instead of subclass, which is not the same as JavaScript.
Minimal Example:
class Base {
static create() {
return new this()
}
}
class Sub extends Base {
fn() {
return this
}
}
Sub.create().fn() // TypeScript error, JavaScript ok
Base.create().fn() // TypeScript error, JavaScript error
TypeScript Playground
It's because TypeScript doesn't have polymorphic this types for static methods. There's a longstanding open issue, microsoft/TypeScript#5863, asking for such a feature. For now it's not part of the language, but luckily comments on that issue describe a workaround that should get you the behavior you're looking for:
class Base {
static create<T extends Base>(this: new () => T) {
return new this()
}
}
Here we are making create() a generic method with a parameter of type T extends Base that can only be called on an object whose this context is a zero-arg constructor that returns a value of type T.
Therefore the compiler knows that Sub.create() will return a Sub and that Base.create() will return a Base:
Sub.create().fn() // okay
Base.create().fn() // Property 'fn' does not exist on type 'Base'
This also has the advantage over your existing code in that the following is an error now:
class Oops extends Base {
x: string;
constructor(x: string) {
super();
this.x = x.toUpperCase();
}
}
Oops.create(); // error!
The Oops class does not have a zero-arg constructor, and so the implementation of Oops.create() will end up calling new Oops() without the required parameter and cause a runtime error when it tries to call toUpperCase() on undefined.
Okay, hope that helps; good luck!
Playground link to code
Edit: this question is different from How to extend a class without having to using super in ES6? - although the answers are related, this is very obviously a different question. It relates to a specific error, and the two main classes involved Person and CreationEvent don't actually inherit from each other.
I have two ES6 classes, Person and CreationEvent (CreationEvent inherits from Event). I wish to to make a new CreationEvent when I make a new Person (as the CreationEvent is part of the events in the history of person's account).
Running new CreationEvent() on it's own works fine. However I can't run new Person().
Even with reduced version of the code still fails:
class Event {
constructor() {
this.time = Date.now()
this.tags = []
}
}
class CreationEvent extends Event {
constructor() {
this.description = "Created"
}
}
class Person {
constructor(givenName, familyName, email) {
var creationEvent = new CreationEvent()
}
}
Running new Person() returns
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
How do I make a new ES6 Object in another Object's constructor?
You need to call super() in the CreationEvent class because it extends the Event class and needs te be initialized. Like this:
class CreationEvent extends Event {
constructor() {
super();
this.description = "Created"
}
}
Let's say I have this code:
export class ProductsListComponent {
#Output() onProductSelected: EventEmitter<Product>;
constructor() {
this.onProductSelected = new EventEmitter();
}
}
This is some example of EventEmitter usage. I don't understand why first we declare onProductSelect explicitly stating that it is EventEmitter that carries Product instance, and then we instantiate it with just new EventEmitter(). Why not new EventEmitter<Product>()?
I think that in C# I would have to go with the second way, because otherwise it wouldn't compile if EventEmitter was generic.
Why doesn't TypeScript require that?
//EDIT:
Further clarification fo my question.
What's the difference between:
#Output() onProductSelected: EventEmitter<Product>;
this.onProductSelected = new EventEmitter();
and
#Output() onProductSelected: EventEmitter;
this.onProductSelected = new EventEmitter();
As explained in documentation chapter, type argument inference occurs when type isn't specified for generic class or function.
A function can infer T type from its argument or return types, and a class can infer T type from constructor argument or return types:
function foo<T>(v: T) { return v }
foo(1); // T inferred to number
class Foo<T> {
constructor(v: T) {}
}
new Foo(1); // T inferred to number
If there's nothing to infer, T is inferred to empty object {} for some reason:
class Foo<T> {
foo(v: T) {}
}
new Foo().foo(1); // T inferred to {}
In order to avoid inference to {}, default type can be provided:
class Foo<T = string> {
foo(v: T) {}
}
new Foo().foo(1); // type error
If generic class or function isn't supposed to be used with default type, some impossible type can be specified:
class Foo<T = never> {
foo(v: T) {}
}
new Foo(); // no type error, T isn't involved
new Foo().foo(<any>1); // type error
Since EventEmitter generic class has no default type specified, the latter is inferred to {}. There usually won't be problems with that, because emit is the only method that is affected by generic type. Since all non-nully types can be coerced to object type, this usually won't cause type errors - as long as nully types are ignored.
strictNullChecks compiler option will be a problem for EventEmitter with default type and nully values:
const ee = new EventEmitter();
ee.emit(null); // type error
So for all-round EventEmitter it shouldn't rely on default type and be instantiated as:
const ee = new EventEmitter<any>();
EventEmitter() will work the same as EventEmitter<any>().
If you draw this in HTML on the selector of ProductsListComponents,
then you can listen to onProductSelected event and assign an action like onSelected when that happens. By default you'll get a new EventEmitter<any>(), and defining a variable of any type is the generic approach in Typescript.
<product-list (onProductSelected)="onSelected($event)"> </product-list>
So every time in your child you call
this.onProductSelected.emit("hello");
this.onProductSelected.emit(1);
Parent's function onSelected($event) will get called and you can do anything with that data.
If you're only expecting one type of data to get outputted to the parent so it can further handle it, then you'd want to stick to a specific data type.
onProductSelected: Product = new EventEmitter<Product>();
products: Product[];
then somewhere in your code you can trigger to emit
this.onProductSelected.emit(this.products[1]);
Added an example in stackblitz
Regarding your updated question
#Output() onProductSelected: EventEmitter; is an error, because here you're declaring a type (after :, before =), as oppose to defining a type (after =), when you declare it as an EventEmitter you do need an argument <type>
If you declare the type of EventEmitter, then the compiler will make sure you don't emit anything other than Product type, or what type you declare it to be.
#Output() onProductSelected: EventEmitter<Product>;
I read that using static for propTypes puts the it on the class instead of the instance of the React component. I don't get the difference between putting something on the class vs. the instance though. Why is propTypes on the class but not methods like render and componentDidMount, or other custom made methods inside the component?
It's for the same reason as the keyword static. This is un-changing static metadata that helps describe your class. It can be accessed without needing to instantiate your class (accessed without calling the constructor).
class example extends Component {
static propTypes = {
something: PropTypes.object,
}
static displayName = "ExampleDisplay";
render() {
return <div />;
}
}
// I can access static properties here directly
var types = example.propTypes;
var name = example.displayName;
// I can NOT access the render method without instantiating the class.
var instance = new example(); // <- this calls the constructor and creates an instance.
var renderFn = instance.render;
The question is really: why should I need to create the class just to read the propTypes or displayName? you shouldn't need to. that's why you have static.