I'm building a React app with an embedded Google Map.
I've got a custom menu element that I want to display on the map after a click. Google's docs instruct me to 'implement' (although I think in Typescript terms, they mean extend) the google.maps.OverlayView
class in order to render elements over the map.
When I define the class ContextMenu extends google.maps.OverlayView
class inline, the code compiles fine and my element shows up on click. I want to define this class in a separate file, using Typescript.
However, when I move ContextMenu
to a separate file, React errors out with ReferenceError: google is not defined
.
Any idea how to 'import the namespace' such that ContextMenu.ts
knows where google
is? It seems like I am missing something fundamental about Typescript here, but none of their documentation I've been able to find has discussed the practice of creating classes with external namespaces.
Or is extends
the wrong way to do this here? Should I just follow Google's instructions, even in Typescript which exists to avoid messing with prototypes?
Inherit from this class by setting your overlay's prototype:
MyOverlay.prototype = new google.maps.OverlayView()
;.
Working code:
// App.tsx
import React from 'react'
import { Wrapper } from '@googlemaps/react-wrapper'
// cannot define ContextMenu here
const App: React.VFC = () => {
// cannot define ContextMenu here
const onClick = (e: google.maps.MapMouseEvent) => {
// CAN define ContextMenu here
class ContextMenu extends google.maps.OverlayView {
private origin_: google.maps.LatLng
constructor(origin: google.maps.LatLng) {
super()
this.origin_ = origin
}
}
const menu = new ContextMenu(e.latLng)
}
return (
<Wrapper ...>
// Map goes here
</Wrapper>
)
}
Broken code:
// App.tsx as above, without ContextMenu defined.
// ContextMenu.ts
class ContextMenu extends google.maps.OverlayView {
// ...
}
CodePudding user response:
It is not possible to directly extend a google.maps.* class since it actually isn't available (this might depend on tsconfig target, but I haven't tested). You can use the following pattern in TypeScript to delay.
export interface OverlayViewSafe extends google.maps.OverlayView {}
/**
* Extends an object's prototype by another's.
*
* @param type1 The Type to be extended.
* @param type2 The Type to extend with.
* @ignore
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function extend(type1: any, type2: any): void {
// eslint-disable-next-line prefer-const
for (let property in type2.prototype) {
type1.prototype[property] = type2.prototype[property];
}
}
/**
* @ignore
*/
export class OverlayViewSafe {
constructor() {
// We use the extend function for google.maps.OverlayView
// because it might not always be available when the code is defined.
extend(OverlayViewSafe, google.maps.OverlayView);
}
}