googlemaps / js-api-loader

Load the Google Maps JavaScript API script dynamically.
Apache License 2.0
353 stars 64 forks source link

README not updated load() deprecated, importLibrary doesn't replace it 1-1 #837

Open realgs opened 8 months ago

realgs commented 8 months ago

Steps to reproduce

  1. Install js-api-loader 1.16.6, use steps from README to load google map script asynchronously - you get the IDE hint that load() is marked as deprecated, use importLibrary instead. ImportLibrary doesn't replace load(). Using load() everything works just fine but with the deprecation alert, using importLibrary it doesn't load maps.

Code example

            const loader = new Loader({
                libraries: ['places'],
                apiKey: this.apiKey,
            });
            loader.load().then((google) => {
                this.google = google;
                this.isLoading = false;
                this.isReady = true;
            });

I'd be happy to open a pull request to improve the project once you provide me with a solution. If load is deprecated what (and how) should be used instead?

wangela commented 8 months ago

If you would like to upvote the priority of this issue, please comment below or react on the original post above with :+1: so we can see what is popular when we triage.

@realgs Thank you for opening this issue. 🙏 Please check out these other resources that might help you get to a resolution in the meantime:

This is an automated message, feel free to ignore.

Schmell commented 7 months ago

@realgs Ya there is a new way to do it. I guess its more tree-shakeable or something

const addressSearchInput = document.querySelector('autocomplete')
const options = {
    componentRestrictions: { country: 'ca' },
    fields: ['formatted_address', 'address_components'],
}

// make a new loader here. NOTE there are no libraries here anymore
const loader = new Loader({
    apiKey: PUBLIC_GOOGLE_MAPSAPI_KEY,
    version: 'weekly'
})

// you can get anything from the places library here
const { Autocomplete, PlacesService, Place } = await loader.importLibrary('places')

//and to use one its something like this
const addressComplete = new Autocomplete(addressSearchInput , options)
addressComplete .addListener('place_changed', (place)=> console.log(place))

//repeat for other libraries
const { Map, TrafficLayer } = await loader.importLibrary('maps')
const { Marker } = await loader.importLibrary('marker')

I agree that documentation needs to reflect these changes.

usefulthink commented 7 months ago

I'd like to add that after loader.importLibrary() you will also have access to the global google.maps namespace as it was before after calling .load(). So for example:

const loader = new Loader(loaderOptions);
await loader.importLibrary('maps');

// you can now access everything like before:
const map = new google.maps.Map();
owaisahmed5300 commented 6 months ago

Yes, indeed. with load we were able to load multiple libraries at once without any problem but with importLibrary we could only import 1 library at a time and in next recall we get below error.

Google Maps already loaded outside @googlemaps/js-api-loader.This may result in undesirable behavior as options and script parameters may not match.

Assume we need Map and Markers, what we suppose to do?

owaisahmed5300 commented 6 months ago

@Schmell Getting Warning

Google Maps already loaded outside @googlemaps/js-api-loader.This may result in undesirable behavior as options and script parameters may not match.

owaisahmed5300 commented 6 months ago

Relevent Issue: https://github.com/googlemaps/js-api-loader/issues/809

Fix: Promise.all() instead await for every importLibrary.


import { Loader } from '@googlemaps/js-api-loader';

const loader = new Loader({
  apiKey: ''
  version: 'weekly',
});

let libraries: any = null;

async function loadLibraries() {
  if (!libraries) {
    // not ideal to load all but just for an example.
    libraries = await Promise.all([
      loader.importLibrary('maps'),
      loader.importLibrary('places'),
      loader.importLibrary('marker'),
      loader.importLibrary('geocoding'),
    ]);
  }
  return libraries;
}

const [{ Map }, { Autocomplete }, { AdvancedMarkerElement }, { Geocoder }] = await loadLibraries();

export { Map, AdvancedMarkerElement, Geocoder, Autocomplete };
jmeinlschmidt commented 2 months ago

I don't get the libraries property in the constructor LoaderOptions. Is this an obsolete option for loading additional libraries? Should it be deprecated in favour of .importLibrary()? In what way are these two related?

const loader = new Loader({ libraries: ['maps'] });

vs

const loader = new Loader();

const { ... } = loader.importLibrary('maps');

Unfortunately, I couldn't find any satisfying answer in the docs.

jmeinlschmidt commented 2 months ago

Also, the load and loadCallback methods are being highlighted in both README file and Google Documentation, although being deprecated.

Kocal commented 2 months ago

Hi folks, if you need to mimic the google.maps structure without using the deprecated method loader.load(), but by using loader.importLibrary(), you can use the following code:

const google = {};
const libraries = ['core', 'maps', 'marker', /* ... */];

const librariesImplementations = await Promise.all(libraries.map((library) => loader.importLibrary(library)));
librariesImplementations.map((libraryImplementation, index) => {
    const library = libraries[index];

    // The following libraries are in a sub-namespace
    if (['marker', 'places', 'geometry', 'journeySharing', 'drawing', 'visualization'].includes(library)) {
        google.maps[library] = libraryImplementation;
    } else {
        google.maps = { ...(google.maps || {}), ...libraryImplementation };
    }
});

By doing this way, you can have access to google.maps.LatLng, google.maps.InfoWindow, google.maps.marker.AdvancedMarkerElement, etc...

mycarrysun commented 2 months ago

Hi folks, if you need to mimic the google.maps structure without using the deprecated method loader.load(), but by using loader.importLibrary(), you can use the following code:

const google = {};
const libraries = ['core', 'maps', 'marker', /* ... */];

const librariesImplementations = await Promise.all(libraries.map((library) => loader.importLibrary(library)));
librariesImplementations.map((libraryImplementation, index) => {
    const library = libraries[index];

    // The following libraries are in a sub-namespace
    if (['marker', 'places', 'geometry', 'journeySharing', 'drawing', 'visualization'].includes(library)) {
        google.maps[library] = libraryImplementation;
    } else {
        google.maps = { ...(google.maps || {}), ...libraryImplementation };
    }
});

By doing this way, you can have access to google.maps.LatLng, google.maps.InfoWindow, google.maps.marker.AdvancedMarkerElement, etc...

This should definitely be in this library, not something every caller has to implement themselves

walmartwarlord commented 2 months ago

Hi folks, if you need to mimic the google.maps structure without using the deprecated method loader.load(), but by using loader.importLibrary(), you can use the following code:

const google = {};
const libraries = ['core', 'maps', 'marker', /* ... */];

const librariesImplementations = await Promise.all(libraries.map((library) => loader.importLibrary(library)));
librariesImplementations.map((libraryImplementation, index) => {
    const library = libraries[index];

    // The following libraries are in a sub-namespace
    if (['marker', 'places', 'geometry', 'journeySharing', 'drawing', 'visualization'].includes(library)) {
        google.maps[library] = libraryImplementation;
    } else {
        google.maps = { ...(google.maps || {}), ...libraryImplementation };
    }
});

By doing this way, you can have access to google.maps.LatLng, google.maps.InfoWindow, google.maps.marker.AdvancedMarkerElement, etc...

How do I apply this logic with my current code that uses load?

  useEffect(() => {
    const init = async () => {
      if (!apiKey) return;

      try {
        if ( !window.google || !window.google.maps || !window.google.maps.places ) {
          await new Loader({ apiKey, ...{ libraries: ['places'], ...apiOptions }}).load();
        }
        initializeService();
      } catch (error) {
        if (typeof onLoadFailed === 'function') onLoadFailed(error);
      }
    }

    if (apiKey) init();
    else initializeService();
  }, []);
usefulthink commented 2 months ago

modifying as little of the code as possible, that'd be something like this:

useEffect(() => {
  const init = async () => {
    if (!apiKey) return;

    try {
      if ( !window.google || !window.google.maps || !window.google.maps.places ) {
        const loader = new Loader({ apiKey, apiOptions });

        await Promise.all([
          loader.importLibrary('maps'), 
          loader.importLibrary('places')
        ]);
      }
      initializeService();
    } catch (error) {
      if (typeof onLoadFailed === 'function') onLoadFailed(error);
    }
  }

  if (apiKey) init();
  else initializeService();
}, []);
walmartwarlord commented 2 months ago

modifying as little of the code as possible, that'd be something like this:

useEffect(() => {
  const init = async () => {
    if (!apiKey) return;

    try {
      if ( !window.google || !window.google.maps || !window.google.maps.places ) {
        const loader = new Loader({ apiKey, apiOptions });

        await Promise.all([
          loader.importLibrary('maps'), 
          loader.importLibrary('places')
        ]);
      }
      initializeService();
    } catch (error) {
      if (typeof onLoadFailed === 'function') onLoadFailed(error);
    }
  }

  if (apiKey) init();
  else initializeService();
}, []);

So I dont have to do { libraries: ['places'], ...apiOptions } anymore?

Also, does importLibrary automatically going to attach a google object to the window?

usefulthink commented 2 months ago

Just noticed an error in this line: const loader = new Loader({ apiKey, apiOptions });, it should have been const loader = new Loader({ apiKey, ...apiOptions });

Also, does importLibrary automatically going to attach a google object to the window?

Yes, this still works the same way with the async loader.

While I'm a fan of the new way to do it since I don't need to know which libraries to load beforehand, I'm mostly don't like having to use await every time I need some class from the maps API. So I'll typically mix the old an new way.

So I dont have to do { libraries: ['places'], ...apiOptions } anymore?

I think you can do that as well, in that case you'd only need to e.g. loader.importLibrary('core') or loader.importLibrary('maps') instead of multiple importLibrary calls.

mycarrysun commented 1 month ago

So I dont have to do { libraries: ['places'], ...apiOptions } anymore?

If you have libraries in the apiOptions object you'll need to loop through those and call importLibrary for each one.