Sunday, 23 March 2014

Problem with AngularJS ng-view, it doesn’t work when inside a directive

I hit an interesting problem yesterday with AngularJS views. They (the views) where working when clicking on a link, but not working when accessed directly, or when the back button was used (which broke the idea of AngularJS routing, since it is supposed to handle those to key scenarios).

After quite a bit of debugging, I was able to track the problem to the fact that if I placed the ng-view directive inside another directive, the refresh and back button would break (although it would work ok for links and direct browser url manipulation).

What is really nice, is that I was able to use the .NET C# based Unit Test infrastructure to confirm this problem and test for it :)

The code set-up

Here is the (simplified) pages/code that I created to test/confirm the issue.

The default.html page loads up the AngularJS includes and uses the custom views directive instead of the native ng-view directive (see highlighted line below):

image


The users.html page loads up the AngularJS includes but uses the native ng-view directive

image


The custom views directive is mapped on the directives.js file and it is a simple templateUrl  mapping:

image

The views.html (mapped to the views directive shown above) contains the native ng-view directive

image

The route.js contains a test mapping to a specific view (in this case test.html). Note that this mapping exist on both route.js files (the one used by default.html and the one used by users.html (shown below))

image

The topMenu.html view (mapped to the custom topMenu directive on directives.js) provides a link to the test route (via an href to #/test )

image

Finally the test.html page contains just a little html (to provide a visual clue and ‘unit testable’ page change)

image

The problem

With the code shown above, this is what happens:

1) if when opening up the http://localhost:3187/tbot_v2/default.htm#/ page

image

2) … we click on the test link

image

3) … the test page will show up

image

4) If we manually change the path (i.e. enter the AAAs shown below and press enter on the location bar), we will get the 404 page (mapped via an .otherwise mapping)

image

5) If we remove (also manually) the extra chars (and press enter), it will work

image

6) But if we hit refresh now

image

7) … it will not work (i.e the route events don’t fire and the view is not rendered):

image

8) The same scenario happens if we open another page and hit the back button:

image

9) and the worse scenario is the fact that if we open the link in a new window, it will not work:

image

The solution

As mentioned before, after a while, I tracked the problem to the fact that in the default.html page I did 'one refactoring too many', and AngularJS doesn’t seem to fully-support the cases when the native ng-view directive is placed inside a custom directive (which is the refactoring that I was trying to do).

To confirm this, if we open the http://localhost:3187/tbot_v2/users.htm#/test page in a new window (which is using the native ng-view directive inside the main html page), the AngularJS routing events will trigger and the test.html view will be shown:

image

This page is also working for the back button.

UnitTest using VisualStudio, NCrunch and FluentSharp.WatiN

To test this behavior I used wrote the following UnitTest in VisualStudio:

image

To see it in action, lets add a script_IE_WaitForClose(); instruction to the end of the script:

image

… which will open an O2 Platform REPL script environment with the current WatiN IE object:

image

Note that from here we have full access to the IE object, and are able to use the FluentSharp.WatiN extension methods.

For example here is how to access the current links:

image

Update: See AngulaJS Issue 6812 for my report to the AngularJS team (note that this might not even be a bug, since it could be the expected behaviour of AngularJS routes and the ng-view directive)