Skip to content
This repository has been archived by the owner on Jan 12, 2018. It is now read-only.

Loading with v4 arcgis-js-api.d.ts #7

Closed
nickcam opened this issue May 7, 2016 · 7 comments
Closed

Loading with v4 arcgis-js-api.d.ts #7

nickcam opened this issue May 7, 2016 · 7 comments
Assignees

Comments

@nickcam
Copy link
Contributor

nickcam commented May 7, 2016

Hi Guys,

Out of the box the loader won't work with the official typings for v4. https://github.com/Esri/jsapi-resources/blob/master/4.x/typescript/arcgis-js-api.d.ts

It's not surprising as the typings files declares everything as a separate module (as it should for amd I'm thinking), and the loader registers everything under the one module name in systemjs,

snippet from arcgis-js-api.d.ts

declare module "esri/core/Evented" {
    import Evented = __esri.Evented;
    export = Evented;
}

declare module "esri/Basemap" {
    import Basemap = __esri.Basemap;
    export = Basemap;
}

declare module "esri/Camera" {
    import Camera = __esri.Camera;
    export = Camera;
}

A "fairly" easy workaround is to add a new module to the typings file named the same as the output module name in the loader - for example "esri-mods" - that contains every interface as an export in it.
for example:

declare module "esri-mods" {
    export import Evented = __esri.Evented;
    export import Basemap = __esri.Basemap;
    export import Camera = __esri.Camera;
    export import Color = __esri.Color;
    export import Graphic = __esri.Graphic;
    export import Map = __esri.Map;
    export import MapView = __esri.MapView;
    export import SceneView = __esri.SceneView;
   etc...
   etc...
   //add a an exact line as above for every interface declared in the __esri namespace.
}

This way import statements are the same as they currently are as well.

But I was thinking that instead of having to modify the official typings, I just could modify esriSystem.ts.
Since the names of the modules added to the dojo loader call exactly match the typings file - as they should, I tried to create a System.register call for every esri module included using the modules name just by looping the returned modules in the require callback and calling System.register on each one.

Couldn't get it working after a couple of hours trying though and wasn't sure why. So instead of possibly burning days trying to get it going, thought I'd check if you had any ideas of how to handle this?

Thanks!

@tomwayson
Copy link
Member

This is a good idea @nickcam.

Can you share the cod you wrote, even if it's not working yet?

@nickcam
Copy link
Contributor Author

nickcam commented Jun 1, 2016

Hi @tomwayson,
I've been meaning to post what I found here.

In a nutshell, I got it working using the official *.d.ts file....but only when transpiling typescript using 'system' as the module type. The other types would work but would require minor edits to the *.d.ts file.

The original loading remains intact, but if you specify an option of maintainModuleNames: true then it will register modules individually under their full name which will match the typings file module names.

esriSystem.ts

declare var System: any;
declare var require: Function;

module esriSystem {

    // return just the last part of a module name
    function moduleName(name: string, overrides?: any) {
        if (overrides && overrides[name]) {
            return overrides[name];
        } else {
            return name.match(/[^\/]+$/).shift();
        }
    }

    //get the name of the module by stripping out the host from the url
    function getName(location: string) {
        let startIndex = window.location.href.indexOf("/", 8) + 1; 
        return location.substring(startIndex, location.length);
    }

    // takes an array of modules and registers them as a module
    // with system.js using the given module name
    function _register(mods, names, options: any) {
        const opts = options || {};

        if (!opts.maintainModuleNames) {
            //not maintaining module names so register all esri modules into outModuleName or into 'esri' if not set.
            System.register(opts.outModuleName || 'esri', [], function (exp) {
                return {
                    setters: [],
                    execute: function () {
                        mods.map(function (mod, idx) {
                            exp(moduleName(names[idx], opts.moduleNameOverrides), mod);
                        });
                    }
                };
            });
            return;
        }

        //maintaining the module names so loop each module and register individually.
        for (var i = 0, len = mods.length; i < len ; i++) {

            System.register(names[i], [], function (exp) {
                let name = getName(arguments[1].id); //get the module name this callback relates to from arguments[1].id property -> which will be <currentUrl>/<moduleName>.
                let index = names.indexOf(name);
                let mod = mods[index];
                let result = {
                    setters: [],
                    execute: () => {
                        //make the name 'default' here as system compiled modules will automatically create a 'default' property on modules. The ArcGIS .d.ts file contains one export per module so default works.
                        //NOTE: Will only work if compiling using "module": "system" in tsconfig.
                        exp("default", mod);
                    }
                };
                return result;
            });
        } 

    }

    // load esri modules and expose via a System.js module
    export function register(moduleNames: string[], callback: Function, options) {
        // TODO: config should be optional, parse from arguments

        // call Dojo's require to load esri modules
        require(moduleNames, function (...modules) {
            // register a System.js module to wrap the required modules
            _register(modules, moduleNames, options);

            // call callback (if any)
            if (callback) {
                callback();
            }
        });
    };
}

Calling using maintainModuleNames.

esriSystem.register([
    'esri/Map',
    'esri/views/MapView',
    'esri/views/SceneView',
    'esri/core/urlUtils'
], function () {
    // bootstrap the app
    System.import('app/main')
        .then(null, console.error.bind(console));
    }, {
    maintainModuleNames: true
});

Then to import just use the default import syntax.

import {Injectable} from '@angular/core';

import Map from 'esri/Map';
import urlUtils from 'esri/core/urlUtils';

@Injectable()
export default class MapService {
    map: any; 
    constructor() {
    }

    init() {
        this.map = new Map({ basemap: 'streets' });
        let staticTest = urlUtils.urlToObject("http://www.google.com");
    }
}

This keeps Visual Studio happy which appears to be the harder to satisfy IDE as far as typescript compilation errors go.

It works as system modules automatically add a default property to modules, so we can name the export 'default' in the register call and it will resolve when importing. Other module loaders don't do this, although to be honest I only tested commonjs. There's probably a better way to do it that's more consistent across module loaders though.

It would still be nice if the official typings were changed to be more compliant with loaders/IDE's.

Perhaps something like this for each module? I'm not sure how it would impact other users though.

declare module "esri/Map" {
    export import Map = __esri.Map;
}

I had a "fun" time upgrading from Angular 2 Beta-17 to RC1 in Visual Studio as well, but it's all running now, so might put together a starter repository for VS 2015 / ArcGIS 4 / Angular 2 when time permits.

@nickcam
Copy link
Contributor Author

nickcam commented Jun 8, 2016

Just found that when I deployed the app to somewhere that contains a virtual path the getName function wouldn't work. Here's an updated esriSystem.ts. Now getName just looks for 'esri' or 'dojo' in the location to find out where to start getting the module name from.

declare var System: any;

module esriSystem {

    // return just the last part of a module name
    function moduleName(name: string, overrides?: any) {
        if (overrides && overrides[name]) {
            return overrides[name];
        } else {
            return name.match(/[^\/]+$/).shift();
        }
    } 

    //get the module name from the url that system js thinks it's loading it from
    function getName(location: string) {
        let startIndex = location.lastIndexOf("esri");
        if(startIndex === -1) startIndex = location.lastIndexOf("dojo");
        return location.substring(startIndex, location.length);
    }


    function registerToOutModule(mods, names, options: any) {
        System.register(options.outModuleName || 'esri', [], function (exp) {
            return {
                setters: [],
                execute: function () {
                    mods.map(function (mod, idx) {
                        exp(moduleName(names[idx], options.moduleNameOverrides), mod);
                    });
                }
            };
        });
    }

    // takes an array of modules and registers them as a module
    // with system.js using the given module name
    function _register(mods, names: string[], options: any) {
        const opts = options || {};

        if (!opts.maintainModuleNames) {
             //not maintaining module names so register all esri modules into outModuleName or into 'esri' if not set.
            registerToOutModule(mods, names, options);
            return;
        }

        //maintaining the module names so loop each module and register individually.
        for (var i = 0, len = mods.length; i < len; i++) {

            System.register(names[i], [], function (exp, idObj) {
                let name = getName(idObj.id);
                let index = names.indexOf(name);
                let mod = mods[index];
                let result = {
                    setters: [],
                    execute: () => {
                        //make the name 'default' here as system compiled modules will automatically create a 'default' property on modules. The ArcGIS .d.ts file contains one export per module so default works.
                        //NOTE: Will only work if compiling using "module": "system" in tsconfig.
                        exp("default", mod);
                    }
                };
                return result;
            });
        }
    }

    // load esri modules and expose via a System.js module
    export function register(moduleNames: string[], callback: Function, options) {
        // TODO: config should be optional, parse from arguments

        // call Dojo's require to load esri modules
        require(moduleNames, function (...modules) {
            // register a System.js module to wrap the required modules
            _register(modules, moduleNames, options);

            // call callback (if any)
            if (callback) {
                callback();
            }
        });
    };
}

@tomwayson tomwayson self-assigned this Jun 10, 2016
@tomwayson
Copy link
Member

tomwayson commented Jun 10, 2016

@nickcam thanks for keeping plugging away at this. I still haven't had time to try this out on my own (just been very busy w/ UC around the corner), but this looks solid.

Would you be interested in making a pull request with these changes?

cc @jwasilgeo

@nickcam
Copy link
Contributor Author

nickcam commented Jun 10, 2016

No worries...am using it in a new project so just getting sorted out up front.
Keen for other module types besides 'system' to work as well though, pretty sure I'll need it to soon as we start integrating ArcGIS & Angular2 with (possibly many) other libraries.

Bit pushed for time as well - unfortunately not because of UC :) - but want to try and take another look next week at getting all or at least more module types to load. Will raise a PR after I get a chance to check it out again.

@jwasilgeo
Copy link
Contributor

@nickcam awesome. Just wanted to hop on here and also say thanks and definitely encourage you to make a PR when you have the chance.

@tomwayson
Copy link
Member

Resolved in #10

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants