How do I properly type Classes that implement interfaces?
Example code:
interface IPlugin{
name:string;
}
class SomePlugin implements IPlugin{
name;
constructor(){
this.name = 'Sam';
}
}
const arrayOfClass:IPlugin = [SomePlugin];
// Ther error :
/*
Type 'typeof SomePlugin[]' is not assignable to type 'IPlugin'.
Property 'name' is missing in type 'typeof SomePlugin[]'.
*/
How should I go about this?
Create an interface that describes objects that will instantiate objects that implement IPlugin. You can do this by using a new signature:
interface IPluginConstructor {
new(...args: any[]): IPlugin;
}
Now type arrayOfClass as an array of IPluginConstructors:
const arrayOfClass: IPluginConstructor[] = [SomePlugin];
Note the [] in the type. That was absent in the question.
Sidenote
If you look closely, the type of name is any in SomePlugin... it's been set as any because the type was implicitly typed as any and string is assignable to any. That means the following code compiles:
const s = new SomePlugin();
const num: number = s.name; // this compiles... sad! :(
You should type that either explicitly...
class SomePlugin implements IPlugin {
name: string;
constructor() {
this.name = 'Sam';
}
}
...or implicitly...
class SomePlugin implements IPlugin {
name = 'Sam';
}
I recommend you enable the noImplicitAny compiler flag to help catch these kind of mistakes in the future.
Related
I have an abstract base class validator with a method which takes a generic type as parameter.
I will be passing generic type parameter to base class from the subclass inheriting the base class.
Base Class:
abstract class BaseValidator {
bool isValid<T>(T obj);
}
Child Class:
class IPv4Validator extends BaseValidator{
final IPV4_REGEX = "^((25[0-5]|(2[0-4]|1d|[1-9]|)d).?\b){4}\$";
#override
bool isValid<String>(String obj) {
bool hasMatch = RegExp(IPV4_REGEX).hasMatch(obj);
return hasMatch;
}
}
Here hasMatch takes in non nullable string. When I directly pass some string hasMatch doesn't throw an error.
But when I try to pass the generic value in the method parameter, it shows an error.
The argument type 'String' can't be assigned to the parameter type
'String'.
I couldn't able to understand why generic type is not accepting, even though its compile-time type.
The following code solves this particular problem. But it may be different from what you intended to implement. On the other hand, the code will be cleaner if you create a new concrete class for different data types.
abstract class BaseValidator<T> {
bool isValid(T obj);
}
class IPv4Validator extends BaseValidator<String>{
final IPV4_REGEX = "^((25[0-5]|(2[0-4]|1d|[1-9]|)d).?\b){4}\$";
#override
bool isValid(String obj) {
bool hasMatch = RegExp(IPV4_REGEX).hasMatch(obj);
return hasMatch;
}
}
Explanation.
In the line class IPv4Validator extends BaseValidator<String> we are not declaring a new class BaseValidator, it is already declared as BaseValidator<T>. Here we are inheriting the specialization of the existing generic class BaseValidator. While in the line bool isValid<String>(String obj), we declare a new function, so the compiler understands it as if we were declaring a new generic function with a parameter type named String. So, here bool isValid<String>(String obj) is equivalent to bool isValid<T>(T obj), just instead of name T we used name String, which is not an object String.
another fix that you can do is to use the covariant keyword, to implement that, try this:
abstract class BaseValidator<T> {
bool isValid(T obj);
}
class IPv4Validator extends BaseValidator {
final IPV4_REGEX = "^((25[0-5]|(2[0-4]|1d|[1-9]|)d).?\b){4}\$";
#override
bool isValid(covariant String obj) {
bool hasMatch = RegExp(IPV4_REGEX).hasMatch(obj);
return hasMatch;
}
}
Not sure if I'm using generics properly but is there a way I can let <T> know that it has (or will have) a certain attribute when it's used? This wouldn't be a problem if it weren't generics but since it's not I keep getting the error The getter 'id' isn't defined for the type 'T & Object'.
class Foo<T> {
List<T> items = [];
removeById(int id) {
items.removeWhere((T element) => element.id! == id); // Error
}
}
You can have either static (compile-time) checks or runtime checks. For compile-time checks, you would need to parameterize your generic on some base interface. For example:
abstract class HasId {
int get id;
}
class MyClass implements HasId {
#override
final int id;
MyClass(this.id);
}
class Foo<T extends HasId> {
...
}
If you really want duck-typing, that inherently requires using dynamic to disable static type-checking and relying on runtime checks:
class Foo<T> {
List<T> items = [];
void removeById(int id) {
items.removeWhere((element) => (element as dynamic).id! == id);
}
}
If there's a possibility that instances of T might not have an id member, you will need to catch a potential NoSuchMethodError yourself.
I have a class:
abstract class Foo {
String getName(T f);
}
and:
class Bar implements Foo {}
or
class Bar extends Foo {}
how can Foo know Bar and implement T as Bar?
UPDATE:
I considered statically passing the type of the child, like:
#override
String getName<Bar>(Bar p1) {
return p1.name;
}
this way I ran into this error: The property 'name' can't be unconditionally accessed because the receiver can be 'null'. Try making the access conditional (using '?.') or adding a null check to the target ('!').
so, I edited it to be:
#override
String getName<Bar>(Bar p1) {
return p1!.name;
}
and now I'm getting this error: The getter 'name' isn't defined for the type 'Bar & Object'. Try importing the library that defines 'name', correcting the name to the name of an existing getter, or defining a getter or field named 'name'.
I guess the only solution, for now, is using dynamic type, like this:
abstract class Foo {
String getName(f);
}
and
class Bar implements Foo {
#override
String getName(f) {
return (f as Bar).name;
}
}
but I'd really like to know the answer to this question.
abstract class Foo {
String getName(T f);
}
should not be valid. T is not specified anywhere.
You need to specify a place for the generic to be passed:
abstract class Foo<T> {
String getName(T f);
}
Then pass that generic when you extend/implement the abstract class:
abstract class Foo<T> {
String getName(T f);
}
class Bar implements Foo<Bar> {
final String name = '';
#override
String getName(Bar p1) {
return p1.name;
}
}
If getName will always accept an implementer of Foo, you can remove the generic and instead use the covariant keyword:
abstract class Foo {
String getName(covariant Foo f);
}
class Bar implements Foo {
final String name = '';
#override
String getName(Bar p1) {
return p1.name;
}
}
Hope one of you angular2 / typescript wizards can help out or at least provide a pointer in the right direction, before I got crazy :-)
Here is what I'd like to
have a parent class that implements it's own defined parent Interface, however using Generic Types so I can when creating a child class provide it with the child's specific and tailored class & data Interface.
the child class should be able to extend the parent data class by
being able to overwrite default/parent set variables
overwriting parent functions() and have the child's version called instead of the parent's default
In the below pseudo code example, I would like the call to the child's (inherited) someOtherfunction() to return "2"...
Am I asking for too much?
I can't seem to find any decent examples on the web...
How do I get this right?
Thank you -
Oliver
(CODE BELOW MAY BE BROKEN, IT'S JUST FOR ILLUSTRATION)
//
// Parent Class
//
export interface ICoreData <T> {
observeItems: Observable <T[]> ;
items: Array <T>;
}
#Injectable()
export class CoreData<T> implements ICoreData<T> {
public observeItems: Observable<T[]>;
private items: Array<T>;
constructor( 'Dependency Injection...' ) {}
coreFunction(): number {
return 1;
}
someOtherfunction(){
return this.coreFunction();
}
}
//
// Child class
//
export interface IMyDataStructure {
name: string;
age: string;
}
export interface ISpecificData extends ICoreData<IMyDataStructure> {
someExtraKey: number;
}
#Injectable()
export class SpecificData extends CoreData<IMyDataStructure> implements ISpecificData {
constructor() {
super();
}
coreFunction(): number{
//
// This function should "overwrite" the parent's original function
// and be called by the parent's someOtherfunction() function
//
return 2;
}
}
You're not asking too much. However you can't use interfaces to accomplish what you're trying to accomplish. You need to extend a class, which can be generic.
An interface is simply a contract, or a blueprint if you like, for a data type. There is no functionality associated with an interface. However in your case you wanted to be able to have methods on the base class; methods you could override in the derived.
The way I usually do this is to declare an abstract base class (so that the base class can't be instantiated itself), and then extend classes from that. Here's an example:
Note, I've removed all the Angular2 cruft in order to keep the example as simple as possible.
abstract class Base<T> {
constructor(public controlled: T) { }
doIt(): string {
return `Base.doIt: ${JSON.stringify(this.controlled)}`;
}
doSomethingElse(): string {
return `Base.doSomethingElse: ${JSON.stringify(this.controlled)}`;
}
};
interface Foo {
foo: string;
bar: string;
};
class Derived extends Base<Foo> {
constructor(foo: Foo) {
super(foo);
}
doSomethingElse(): string {
return `Derived.doSomethingElse: ${JSON.stringify(this.controlled)}`;
}
};
let d: Derived = new Derived({ foo: 'foo', bar: 'bar' });
console.log(`doIt ==> ${d.doIt()}`);
console.log(`doSomethingElse ==> ${d.doSomethingElse()}`);
Output:
doIt ==> Base.doIt: {"foo":"foo","bar":"bar"}
doSomethingElse ==> Derived.doSomethingElse: {"foo":"foo","bar":"bar"}
Playground link.
I have an abstract class Model with a static attribute and another generic class Controller<T extends Model>. I want to access the static attribute of Model in an instance of Controller. That should like this:
abstract class Model{
static hasStatus: boolean = false;
}
class MyModel extends Model{
static hasStatus = true;
}
class Controller<T extends Model>{
constructor(){
if(T.hasStatus)...
}
}
But TS says 'T' only refers to a type, but is being used as a value here.
Is there an easy way to achieve this? Or should i subclass Controller for each Heritage of Model and implement a method to retrieve the value?
There is no way to do that in typescript. Generic type parameters can only appear where types may appear in declarations, they are not accessible at runtime. The reason for that is simple - single javascript function is generated for each method of the generic class, and there is no way for that function to know which actual type was passed as generic type parameter.
If you need that information at runtime, you have to add a parameter to the constructor and pass a type yourself when calling it:
class Controller<T extends Model>{
constructor(cls: typeof Model){
if (cls.hasStatus) {
}
}
}
let c = new Controller<MyModel>(MyModel);
Here is how it looks when compiled to javascript to illustrate the point - there is nothing left of generic parameters there, and if you remove cls parameter there is no information about where hasStatus should come from.
var Controller = (function () {
function Controller(cls) {
if (cls.hasStatus) {
}
}
return Controller;
}());
var c = new Controller(MyModel);