Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you extend a class in Typescript that is loaded dynamically

I'm using the google maps JS API together with the google.maps namespace via npm install @types/googlemaps. I believe the API is loaded dynamically so the google.maps JS global is not available immediately.

But I don't understand why I get the runtime error: Uncaught ReferenceError: google is not defined when I try to extend google.maps.Marker with a class but not an interface

// No problem!
export interface UuidMarker extends google.maps.Marker {
  uuid: string;
}

// Uncaught ReferenceError: google is not defined!!
export class UuidMarker0 extends google.maps.Marker {
  uuid: string;
  constructor(uuid: string, options?: gmMarkerOptions) {
    super(options);
    this.uuid = uuid;
  }
}

Alternate approach using only interface

// this works
export function UuidMarkerFactory(uuid: string, marker: google.maps.Marker): google.maps.Marker & {uuid:string} {  
  return Object.assign(marker, {uuid});
}

// this fails with google is not defined!! 
export function UuidMarkerFactory0(uuid: string, options?: any): google.maps.Marker & {uuid:string} {
  if (typeof google == "undefined") return null
  return Object.assign(new google.maps.Marker(options), {uuid});
}

What is the best practice for extending a class that loaded dynamically?

Additional Info

I'm using ionic2@RC0 which uses rollup to bundle all the modules. All my typescript and node_modules are bundled into a single main.js script with source maps. The actual google maps API is loaded by the angular2-google-maps script.

If I extend using an interface (which seems more typescript "friendly") what pattern can I use to create an object that fits the UuidMarker interface?

like image 857
michael Avatar asked Oct 31 '16 08:10

michael


People also ask

How do I extend a class in TypeScript?

Just like object-oriented languages such as Java and C#, TypeScript classes can be extended to create new classes with inheritance, using the keyword extends . In the above example, the Employee class extends the Person class using extends keyword.

Can an interface extend a class TypeScript?

In TypeScript, interfaces can also extend classes, but only in a way that involves inheritance. When an interface extends a class, the interface includes all class members (public and private), but without the class' implementations.

Can you extend and implement at the same time TypeScript?

Yes you can do that.

What is extends keyword in TypeScript?

The extends keyword is used to create a child class of another class (parent). The child class inherits all the methods from another class. Inheritance is useful for code reusability: reuse properties and methods of an existing class when you create a new class.


1 Answers

In the compilation process you're not getting any errors because the compiler has access to the google.maps definitions that you installed using @types.

However, in runtime your file is probably being loaded before the google.maps library has loaded and so the interpreter can't find the google.maps.Marker object.

You need to load your file only after you know that the google.maps file has loaded successfully.

You don't get runtime errors for the UuidMarker interface because it does not exist in runtime.
Interfaces don't exist in javascript, they are only used by the typescript compiler and are not being "translated" into js.


You can do a trick by placing the class definition inside a function.
This way the interpreter won't execute it before this function is called, which can be after the google maps lib has loaded:

interface UuidMarker extends google.maps.Marker {
    uuid: string;
}

let UuidMarker0: { new (uuid: string, options?: gmMarkerOptions): UuidMarker };

function classLoader() {
    UuidMarker0 = class UuidMarker0 extends google.maps.Marker implements UuidMarker {
        uuid: string;

        constructor(uuid: string, options?: gmMarkerOptions) {
            super(options);
            this.uuid = uuid;
        }
    }
}

(playground code which simulates this)

Another approach, is to drop the class as you suggested and just do:

function UuidMarker(uuid: string, marker: google.maps.Marker): google.maps.Marker & { uuid: string } {
    return Object.assign(marker, { uuid });
}

Edit

This syntax:

type MyType = google.maps.Marker & { uuid: string };

Is called Intersection Types and it means that MyType has everything that google.maps.Marker has plus the uuid property.

A simple example:

interface Point {
    x: number;
    y: number;
}

type Point3D = Point & { z: number };

let p1: Point = { x: 0, y: 0 };
let p2: Point3D = { x: 0, y: 0, z: 0 };
like image 140
Nitzan Tomer Avatar answered Sep 28 '22 08:09

Nitzan Tomer