Search code examples
javascriptinheritancetypescriptabstract-classfactory-pattern

TypeScript factory pattern says property doesnt exist


Im trying to implement the factory pattern in TypeScript, but i can't access the child-functions that doesnt exist in the super class. (It works, but the compiler is giving me an error).

Structure:

abstract class Animal {
    walk(meters:number) { ... }
}

class Elephant extends Animal {
    walk(meters:number) { ... }
}

class Eagle extends Animal {
    walk(meters:number) { ... }
    fly(meters:number) { ... }
}

My factory:

class Zoo {
    animals:Animal[] = [];

    addAnimal(type:string): Animal {
        var a: Animal;

        switch(type) {
            case 'elephant':
                a = new Elephant();
                break;
            case 'eagle':
                a = new Eagle();
                break;
            default:
                throw new Error('Animal of type \'' + type + '\' doesn\t exist');
        }

        this.animals.push(a);
        return a;
    }
}

Then:

var sammy:Animal = addAnimal('eagle');
sammy.fly(15);

This gives me: Error: TS2339: Property 'fly' does not exist on type 'Animal'.

Also i tried to cast:

var sammy:Eagle = addAnimal('eagle');
sammy.fly(15)

Which gives me: Error: TS2322: Type 'Animal' is not assignable to type 'Eagle'. Property 'fly' is missing in type 'Animal'.

I made a code playground on TypeScript page: http://bit.ly/21yXXjf


Solution

  • Quick Fix

    You can use type assertions to take the type checking away from TypeScript and into your own hands.

    var sammy = <Eagle><any>zoo.addAnimal('eagle');
    sammy.fly(15)
    

    This can result in problems, so there is a better solution to your problem (and the factory problem in general)...

    Better Solution

    Use specialized signatures to return the correct type based on the static string:

    class Zoo {
        animals:Animal[] = [];
    
        addAnimal(type: 'elephant'): Elephant;
        addAnimal(type: 'eagle'): Eagle;
        addAnimal(type: string): Animal;
        addAnimal(type: string): Animal {
            var a: Animal;
    
            switch(type) {
                case 'elephant':
                    a = new Elephant();
                    break;
                case 'eagle':
                    a = new Eagle();
                    break;
                default:
                    throw new Error('Animal of type \'' + type + '\' doesn\t exist');
            }
    
            this.animals.push(a);
            return a;
        }
    }
    
    var zoo = new Zoo();
    
    // No type assertion needed, sammy is an eagle!
    var sammy = zoo.addAnimal('eagle');
    sammy.fly(15)