Sunday, June 11, 2017

OpenLayers 4 + ES2015 Modules + TypeScript

I've just recently upgraded to Angular 4 from version 2. It was pretty straight forward and easy. Nothing really to talk about there, except maybe that I had to now include the animation modules separately.

What's really challenging was upgrading to OpenLayers 4 and using the ES2015 module version of it (https://www.npmjs.com/package/ol)... in TypeScript!

Here's a quick summary:

Biggest lesson was a reminder that TypeScript is a strict superset of ES2015.

I was able to simply use the OL4 Modules as is, but, I have to admit, I stumbled a lot.  First I installed the ol package from npm:
npm install ol@4.1.1 --save
Easy enough. Then I tried using it like I do with all the other Angular stuff:
import { Map } from 'ol/map';
What!? Why does this not work?! Remember, I'm still new at ES2015...

So yes, the first problem was that OL was not exporting named members, only one default member. So removing those curly braces fixed that issue, but took me way too long to get there in the first place :(

Hopefully, this alone will help save someone else's precious time.  More than likely, I'm probably the only dumbass in the world that didn't realize this.

This will work with Webpack and Angular CLI out of the box.

If you want typing to work (which, of course, you do... who wouldn't when you're working in TypeScript?), you're gonna need to do a bit more.  DefinitelyTyped only has the definition for OpenLayers 3 as of this writing.  They may have version 4 now by the time you read this, so check with them first.

In the meantime, I used @jumpinjackie auto-generated OpenLayers 4 typings he used for one of his projects.  This part also took me forever since I didn't really understand how to use custom typings.  I ended up installing "typings" and installed a local custom typing:
typings install file:custom_typings/ol.d.ts --save --global
Note the special "file:" protocol.  I've also put my custom typing for this in my "custom_typings" directory.  Seems, though, that I had to exclude this directory in my tsconfig.json file, otherwise I was getting duplicate definition errors.

Another snag I hit was that I was using things like ol.interaction.Select.Event, which is a public class, by the way.  These were not its own module and there were no typings definition for it.  I had to create my own "extended" typing file for these.  I was able to open up ol module again and extend like such:

Sunday, February 12, 2017

Empty Promises: From Promise to Deferred

The JavaScript Deferred pattern is well documented and used a lot. You've probably even used a jQuery's implementation before.

I needed this "empty promise" pattern for this project and didn't want to be coupled to jQuery anymore than I am by using Bootstrap.

Here's the implementation in TypeScript as an extra layer on Promise:
I have deployed this in various places throughout this project, but one particular example is when a component needs the Selector object (to get selection count, for example). The component initialization is obviously asynchronous. Initializing the Selector object is also done asynchronously and the promise would be better resolved elsewhere. So in the meantime, I would return an "empty promise" to the component requesting the Selector object.

Wednesday, October 19, 2016

Using Typings' global option with npm run

 In my Angular 2 project using Typescript, I wanted to install typing definitions for OpenLayers. Seems the best way to do this is through Typings Definition Manager (npm install typings). So I run this via npm run typings install dt~openlayers and keep getting the following error:
Attempted to compile "openlayers" as an external module, but it looks like a global module. You'll need to enable the global option to continue.
Easy enough, I'll use the global option!

npm run typings install --global --save dt~openlayers

Nope! Same error! Why?!

Turns out, through npm run, it's trying to parse those options for itself. So to pass the options to typings instead, do this:

npm run typings install -- --global --save dt~openlayers
Note the extra "--" in there!

Monday, October 17, 2016

Angular 2 Route Animation

Angular 2 Animation API is pretty slick. It actually uses the browser's implementation of  W3C Web Animation Specification (or polyfill thereof), but the interface to it from Ng is very clean and easy to use.

You can see some quick examples on their documentation.

But here's how I am using it for animating route changes.

This will allow my side panel to fly in and out on route changes.

What's really important here is the "host" attribute. This will put the animation trigger on the host / containing element ("opscon-menu" in this case) itself. When I put the trigger on any element in the template itself rather on the host, the exit animation ("* => void") never gets executed. This is probably because Angular doesn't have any animation promise when removing the host element, so it just removes it right away. By putting the "@routeAnimation" trigger on the host, Angular waits for the animation to complete before removing the element from the DOM. Note that the name "routeAnimation" has no special significance...  just a name.

As a side note, I'm also attaching a class to the host here. I'm not sure if this is the best spot to do this or not...

Sunday, October 16, 2016

Upgrading to Angular 2 and TypeScript

After all that work using AngularJS (aka Angular 1) and plain ol' JavaScript, I decided to switch over to Angular 2 and TypeScript for this project. Here are a few reasons:
  • This project was still in its infancy so it's a good time to switch.
  • Angular 2 final has just been released.
  • Angular 1, for all its awesomeness, was kindda quirky. It was almost like it wasn't written with JavaScript in mind. That's why I think Angular 2 with TypeScript is a killer combination.
  • Angular 1 is gonna be deprecated sooner than later. You can already sense it in their documentation, particularly when they talk about Components for Angular 1(.5). 
  • I originally thought Angular 2 could compile to native code on mobile seamlessly. This doesn't seem to be quite the case, but it's still a damn good option to have!
  • It's about time I started using TypeScript.

Here are some benefits I'm already experiencing with this new project.
  • Cleaner routes - Working with routes in Angular 2 is a much more pleasurable experience.
  • Animations - ditto
  • Modular - You may have been able to be pretty modular in Angular 1 too, but I feel Angular 2 (and its emphasis on components) combined with TypeScript / ES6 takes it to a whole new level.

One concrete example of the last point about modularity is that I think I am now (easily) able to uncouple our mapping backend!

Originally we were quite set on OpenLayers and pretty opinionated about using it and only it. Giving it some more thought, using the proof-of-concept app, and appreciating the last point above, I came to realize that it doesn't have to be that way. I can expose a common API to the mapping backend and be able to swap OpenLayers out for something else.

This would be useful for an ESRI shop, for example. Yes you can export all ESRI layers in a format for OL to consume, but it would significantly affect one's workflow. Take styling a WFS layer: An ArcGIS Feature Layer would already have all the symbologies built in, while a WFS would not. Being able to use ArcGIS API for JS would be quite an advantage in this case.

I hope to get around to writing an ESRI backend too in the future, but for now I will focus on OL, but will keep in mind to write a solid API for this to happen. Here's how Angular 2 and TypeScript is already allowing me to do just that:


It's a matter of uncommenting a line from a couple of files to swap out the mapping backend! Notice that in the "app.module.ts" it's just abstractingly importing whatever "MapModule" our "map.module.ts" is exporting. We can map "MapModule" to anything!

Monday, July 4, 2016

OpenLayers Touch Screen DragBox

I had a frustrating time with this one!

Here's the problem:
I needed a way to multi-select features on touch screen / mobile devices via drag select. On the desktop, this is easily achievable using the built-in DragBox interaction and following the Box Selection example on the OL website.

I search the Google for a long time for a solution. Most just eluded to this line in the DragBox documentation: "This interaction is only supported for mouse devices." and offered no alternatives.

I looked into the OL code for this and found that, yes, there are explicit checks in the code to only work with mouse. Particularly, these 2 lines:

I didn't see why this was a restriction, and I still don't! If you do, please enlighten me! Touch inputs are perfectly capable of doing dragging stuff, I mean, that's how panning works, right?

I got the crazy idea of creating my own interaction to do multi-select with both mouse and touch inputs. I studied OL's DragBox and Pointer classes. Then realized, why not first try to do copy and paste programming and just take out the restriction bits and see if that works. I created TouchDragBox interaction that does exactly that and it seems to work fine so far!

You can see the class on our GitHub project.

Of course, the interaction itself can't just be activated all the time, because we won't be able to do panning! When adding this interaction to the map, I created a custom condition to either detect "CTRL" on desktop or if the user clicks on a button that toggles the touch drag box interaction mode on mobile platforms. Here's the code for that.

This approach could be bad if OL decided to change how interaction works, but really that's a normal process of using an API. In the TouchDragBox implementation, everything used was public API, as far as I can tell. So we should be good for awhile.

Now, there were plenty of other ideas and attempts to resolve this problem at first. I tried to use the Draw interaction to do multi-select, which was kindda quirky, because most folks are used to dragging and not click 2 points to create a rectangle for selection. Furthermore, on the mobile, you don't even see the box that was drawn for the selection. But, ultimately, the thing that killed this idea was how this tool was to be activated. It got weird to a point where I was implementing my own Select interaction. It all went down hill quite quickly. I left some remnants of this attempt in the code, commented out. See the link above if you're interested...

Monday, June 13, 2016

Custom OpenLayers directives with Angular

When I decided to go with Angular for this project, I knew I wanted to have OpenLayers be a directive of sort. Turns out David Rupert made just that with his angular-openlayers-directive project. It's really handy in getting the whole thing running with very little time.

We're using GeoServer as the backend, particularly, its flavor of WFS. angular-openlayers-directive does not have a way to handle this kind of WFS very well, so I created my own ol-layer-wfs directive to help out with this.

Note that this is my first Angular project, so please forgive me if it's not proper... Really just trying to get it working!

Here's the HTML.
Here's the Angular code. Note that we are "extending" the openlayers-directive module here and requiring the openlayers controller to get to the map object. I am also using the Angular link method, which I currently have no idea what it does, but seems to work and get the stuff I need loaded :)

In case you were wondering, the manhole properties look like this: