paint-brush
Replacing the angular 1 router with Elm — Part 2by@julianjelfs_61852

Replacing the angular 1 router with Elm — Part 2

by Boolean Julian JәlfsJanuary 11th, 2017
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

This is a follow up post to <a href="https://medium.com/@julianjelfs_61852/replacing-the-angular-1-router-with-elm-d71753e74e32#.2fd4y8xip" target="_blank">part one</a>.
featured image - Replacing the angular 1 router with Elm — Part 2
Boolean Julian Jәlfs HackerNoon profile picture

This is a follow up post to part one.

Now we are successfully rendering angular components beneath the Elm router which is great. But there is a problem. If we are trying to migrate an existing angular app that used the angular router then there is a very good chance that we are using the $location service to perform programmatic route changes for example:

$location.path('/pagefour/1234');

This is not going to work any more because Elm is in control. So we need to find a way to tell Elm to change the route rather than telling angular to change route. And preferably we need to do this without changing our angular code in any intrusive way (because we might have a lot of it).

One way do this is to decorate the $location service and override its default behaviour to send a message to an inbound Elm port so that Elm can take over. Something like this:

const app = Elm.Main.embed(root);


angular.module('MyApp', []).config(function($locationProvider, $provide) {

$locationProvider.html5Mode(true);

$provide.decorator('$location', \['$delegate',   
    function $locationDecorator($delegate) {  
        const p = $delegate.path;  
        **$delegate.path = function(url) {  
            app.ports.newUrl.send(url);  
        }**  
        return $delegate;  
    }\]);  

});

This will intercept all calls to $location.path and send the url to our newUrl port. On the Elm side we need to define that port as follows:

port newUrl : (String -> msg) -> Sub msg

and then we need to subscribe to that port so our subscriptions function becomes:



subscriptions : Model -> Sub Msgsubscriptions model =newUrl NewUrl

We are already handling the NewUrl Msg in our update function and everything appears to work.

But there is still a problem

Anchor tags will still not work. So it turns out that decorating $location.path is not the right approach because it is only one way that angular can change the location, we need to cover them all. The solution is to listen out for angular’s $locationChangeStart event and send the message to our Elm port when that happens.





angular.module('MyApp', []).run(function($rootScope) {$rootScope.$on('$locationChangeStart', (e, newUrl) => {app.ports.newUrl.send(newUrl);});});

There is one final issue with this. We actually don’t want to listen to the angular event if the route change was initiated by Elm — only if it was initiated by angular. So we can just create a flag and set it when Elm triggers a route change (we know when that happens because we are listening on an outbound port from Elm) and modify our listening code slightly.








angular.module('MyApp', []).run(function($rootScope) {$rootScope.$on('$locationChangeStart', (e, newUrl) => {if(!listenForRouteChanges) {return;}app.ports.newUrl.send(newUrl);});});

When we have finished handling the Elm initiated route change we just set that listForRouteChanges flag back to true.

But there is still a problem

The browser’s history is getting messed up, more details and some sort of solution in part three …

As before, full source for this proof of concept can be found here.