The basics of Leaflet controls in Angular
Last updated
Last updated
This approach is based upon the information published in asymmetrik/ngx-leaflet-tutorial-plugins
When a Leaflet control is initialised - it usually does four things.
Extends L.Control
to create a new class, for instance L.Control.Loading(options)
i.e. the constructor takes an option object.
Create a factory function (should be L.control.loading
) to create a new control, and
Add the control, one way or another, to the map (for instance L.control.loading(options).addTo(map)
Add listerners to the map to get data back - like clicks or location updates.
These are the tasks and objects that we will be dealing with.
See the following for some worked examples and discusion.
In Angular.io we have to reproduce the above steps.
For the purposes of this project, we also want to do that in a way that is:
Simple and clear,
Idiomatic to Angular
Provides the cleanest API for control in Angular.
In general, these are the steps that need to be performed for each control type with the design decisions that have been made in this project.
Create Typings,
Import the Code,
Create a Directive,
Add the Control to the Map
Sort out the CSS
Interact with the Control (see next page)
There are many ways that the control could be included into the Angular App structure.
To meet the first three strictures above, we really need to include the control as a directive so that it is controllable by structural directives and it is idiomatic.
My preferred way to do this is as a component directive, either in the project or as an external library. The component is passed the options object and map object in the directive.
However, the controls in the @asymmetrik namespace use the approach of an attribute directive. I just think that this approach will get very complicated very fast as more controls are added to the map.
The rest of this page will assume a component with a component directive.
To use the control in Typescript in Angular CLI (which we do assume your are using) then the type definitions need to be included.
The general format is that the Control
, Map
and control
definitions need to be expanded.
This usually takes the form of expanding the leaflet
module with a new class for control in the Control
namespace and new interfaces for control
and for the options object. The later should ideally be in the leaflet
namesapce, but some typings put it in the Control
namespace. A typical example would be:
In this case, the extension to the MapOptions
definition allows you to configure the control in the map options and the extension to the Map
definition is because the control adds methods to the map to return the fullscreen status.
The typings should be (in order of preference) :
In the index.d.ts
of the control npm module, in which case it is loaded automatically, or
defined in the @types definition for the control, or
Defined in application typings.d.ts
.
Remember that all of the typings will be imported from 'leaflet' even though they are defined in your control!
This is because you have defined them by expanding the leaflet
module and thst, in turn, is because controls are defined in Leaflet by expanding the base classes.
The type definitions extend the module leaflet
. In one component, there could be defnitions for this module in the @types/leaflet
module and in a number of control modules. This is not an easy ask for the compiler.
Most of the time, it will only work if you tell the compiler where to look using compiler directives at the top of the component code. A typical example might be:
Leaflet based apps created like this work for ng build
and work with "aot": true
but seem to fail badly for the defaultng build --prod
configuration.
This seems to be around optimization and I think maybe down to tree-shaking. After some experimentation, I found that ng build --prod
works reliably as long as “buildOptimizer”: false
is in angular.json
.
The Next step is t import the JS code into the TS component.
This is usually done from the npm module something like as follows:
Exact location may vary.
As discussed above, we assume that the control is being created as a component, either in the app or an external library.
in the @runette namespace, the component class names are of the form:
and the directives are of the form:
( where "map"
and "object"
refer to a valid map object and a loading object valid for that control and, of course, the string "Loading" and "loading" should be replaced by the name of your control).
Obviously, the control component needs to include something like:
WIth an added complication about the map input ...
This comes in two parts:
Get the map object, and
add the control to the map object.
For the control, the map object is passed to the component in the directive, so the first point does not come up. I would usually use the "onMapReady"
method from ngx-leaflet
as described in this article .
When a ma is passed to the component, we need to do the following as a minimum (we can, of course, do anything else as well).
Create a new control object
run .addTo(map)
to add the control to the specified map.
I define get
and set
on the map
property to allow this.
Ngx-leaflet runs leaflet outside of Angular itself — which I understand to be the best way of avoiding the Leaflet events causing multiple change events in Angular.
However, probably because of this, Leaflet does not fit into the Angular.io CSS encapsulation system and all CSS references have to be at the global level.
This usually means that you add the Leaflet CSS in angular.json as follows
or in styles.css:
This is also true for the styles sheets for each control. The preferred solution for this project, since it promotes readability, is to put the leaflet.css
in angular.json BEFORE the link to styles.css and to put links to the CSS stylesheets for the controls in styles.css
.
In most cases, the stylessheet for the control will be part of the NPM package for the control. Therefore the link in stylse.css
will be a reference to the relevant files, for instance:
When wrapping a control for use in Angular, it is important to provide this detail in the documentation.
Note, the order of the invokation of the CSS stylessheet is important, since the controls will expect to overwrite the default CSS from leaflet. This is another reason for putting the leaflet CSS in angular.json and the control CSS in styles.css: as long as styles.css is last in angular.json there should not be a problem with this.