AngularJS Ui-Router with ASP.Net MVC RouteConfig. How does it work?

11,044

Happy Path Lifecycle

  1. A request is made to the server since the client hasn't loaded any resources yet when requesting content from a URL. ASP.NET routing picks up request and returns whatever content is bound for that route.
  2. If the index is returned (that contains the entrance point to the AngularJS App, the HTML content and subsequent resources are loaded from the server.
  3. AngularJS runs after receiving the DOM ready event
  4. AngularJS parses the DOM and begins executing the various components that it encounters.

Client-Side Routing

Once your application has loaded, client-side routing modules like ui-router ngRoute or the new component router will control the route for you and load content that is bound to that route. However, this only works properly if you always maintain to a pre-defined root route that you use to bootstrap your Angular application.

I.E. www.site.com/Home#/login or www.site.com/Home!/login if using $locationProvider.hashPrefix('!').html5Mode(true);.

Fully reloading your root URL will load your main index content, re-initialize the angular application and pick up the corresponding client-side routing (which in this case would be the login route and module associated with that route). However, once you deviate from this, you run into unhappy path concerns.

Unhappy Path

If you request a URL that does NOT return the entrance point to the AngularJS application, Angular has no way to intercept the request and, subsequently will never run. As a result, if a request is being made to a URL that is not the entrance point to the Angular application, you have to decide how you want to handle it. A common approach is to redirect to Index.

Additional Questions

  • Home/Index.cshtml - since you are blending both worlds, I'd recommend allowing Angular to control and drive all of the navigation once loaded. That would include calling partial routes and loading up the razor views as if they were templates (if you want to keep using Razor).
  • Login/Index.cshtml - this is really tricky. One option is to detect whether or not they are calling this route from Ajax or not. If they are, allow the request. If not, redirect back to index since they're performing a full page load. An additional alternative would be to serve up the entire url, but you'd need to ensure that your angular app entrance point was also delivered. You could then work some magic after your angular app was loaded to load the main page content, then pop the modal afterwards. However, this would quickly get messy once you had additional situations of this nature.
  • What if I have a page which has multiple lists in it? - Would you ever be SHOWING multiple lists at once? If so, that's fine, since you can actually load a parent state/template/controller that then includes additional child templates/controllers without needing to change the parent state. However, this question is incredibly broad and complex and really would need to be a separate, specific question.

What This Might Look Like

You would want to declare a route that handles your default entrance point:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "LoadMain", id = UrlParameter.Optional }
    );
}

public class HomeController : Controller
{
    public ActionResult LoadMain()
    {
        return View("Main.cshtml");
    }
}

public class CategoriesController : Controller
{
    public ActionResult Index()
    {
        return PartialView("Index.cshtml");
    }
}

This way, if a user hit www.site.com, we would return the content from /Home/LoadMain. This content would include all of our required Angular references (Angular, UI-Router, our main app, etc), which would then allow us to default our client-side route to /home and subsequently load /Home/Index

Main.cshtml:

<head>
    <script src="//angular.js"></script>
    <script src="//angular-ui-router.min.js"></script>
    <script src="app.js"></script>
<body ng-app="app">
    <div ui-view></div>
</body>

app.js

var app = angular.module('app', ['ui.router']);
app.config(function($stateProvider, $urlRouterProvider) {

    $urlRouterProvider.otherwise('/home');

    $stateProvider
        .state('home', {
            url: '/home',
            templateUrl: '/Categories/Index' // corresponds to an MVC partial route
        })
        .state('login', {
            url: '/login',
            templateUrl: '/Login/Index' // corresponds to an MVC partial route
        })
    });
Share:
11,044
David
Author by

David

Hey! I'm David, co-founder at a design &amp; dev startup called All Front. I love to build single page apps with the amazing design &amp; dev team! I'm enthusiastic about Angular, React and progressive web apps.

Updated on June 15, 2022

Comments

  • David
    David almost 2 years

    I'm reading this article and I still can't get around my head on how Angular's UI Router is working with ASP.Net routing.

    Can someone explain in an easy and clear way, the full life cycle from when a URL is typed into the browser manually (ex: http://myapp/stateOne ). Who handles the request, ASP.Net? If not, how does angular intercept a URL request if the page is being served by ASP before it can step in? And when does AngularJS come in with the state provider?

    I'm very confused on this and can't find much on the idea.

    In addition to the question, I have a couple of other things I can't understand. I'm writing an app where:

    • Home/Index.cshtml -> Landing page serves content based on states (ie: it has a ui-view="content", which should in turn serve Categories/Index.cshtml in it, for example). So, how would I go around setting the "Default state" so that navigating to index would load the home page and the ng-view content with default categories in it?
    • Login/Index.cshtml -> A modal form which I'm hiding and showing using modal.show() and modal.hide(). Served from a ui-view. This is working. However, if the user types http://myapp/login what should I do? Can I somehow intercept the request and show the index page with the open modal form in it? or even better, the current page and just open up the modal form? (ie: Is it possible to set a state without a templateURL)?
    • What if I have a page which has multiple lists in it? I've seen people set the state to state.childState for instance, but what if I need to load multiple lists (and therefore, multiple states-> views) at once?

    Edit: Special attention to this part, which uses black magic to cause routeTwo/6 to load the index page with the routeTwo page loaded in a UI-View if I'm understanding correctly.

    HTML5 mode is working, but only in a very superficial way. A refresh of the page is sending the full URL to the server (as we have removed the scotch) which doesn't know what to do. We can fix this by reconfiguring MVC's RouteCollection properly. We need to be explicit about the route for each of our views, and then add a catch-all which sends all other URL's to our landing page, to be handled by Angular.

    Update the RegisterRoutes method inside App_Start => RouteConfig.cs like so:

    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
        routes.MapRoute(
            name: "routeOne",
            url: "routesDemo/One",
            defaults: new { controller = "RoutesDemo", action = "One" });
    
        routes.MapRoute(
            name: "routeTwo",
            url: "routesDemo/Two/{donuts}",
            defaults: new { controller = "RoutesDemo", action = "Two", donuts = UrlParameter.Optional });
    
        routes.MapRoute(
            name: "routeThree",
            url: "routesDemo/Three",
            defaults: new { controller = "RoutesDemo", action = "Three" });
    
        routes.MapRoute(
            name: "login",
            url: "Account/Login",
            defaults: new { controller = "Account", action = "Login" });
    
        routes.MapRoute(
            name: "register",
            url: "Account/Register",
            defaults: new { controller = "Account", action = "Register" });
    
        routes.MapRoute(
            name: "Default",
            url: "{*url}",
            defaults: new { controller = "Home", action = "Index" });
    }
    
  • David
    David over 8 years
    You've already cleared up a lot, thanks! Agreed on the third point (additional questions). Regarding If you request a URL that does NOT return the entrance point to the AngularJS application -> So the only entrance point is Home/Index.cshtml right? Can I specify additional "Entrance points"? Are they specified from ASP.Net RouteConfig, angular state provider, or both?
  • David
    David over 8 years
    $locationProvider.hashPrefix('!').html5Mode(true); should handle the hash (but I may be wrong). So to clarify, once angular has loaded, if the user has typed site.com/Login, it will work out the magic to load the Index page that serves the content, and then serve the /Login templateURL in it?
  • David L
    David L over 8 years
    As for your entrance point, you can technically make the entrance point a shared view that is always loaded, meaning that the angular application will get loaded for every single full url load. However, this will create other issues down the road, such as modals being loaded as the full page html for an angular app...it just won't make sense. It's safer and wiser to carefully choose a limited number of entrance points OR build separate angular apps per entrance point and heavily modularize your application, although that might be overkill
  • David
    David over 8 years
    Interesting. So reloading the page at any time would redirect to the home page?
  • David L
    David L over 8 years
    Correct, you can use html5 mode instead of a hash as long as you are fine with limited IE support. However, your url example isn't correct. You would actually want to have your user reach site.com/!/Login OR intercept the request and redirect it to site.com/!/Login to ensure minimal entrance points.
  • David L
    David L over 8 years
    No, reloading the page at any time will simply reload whatever page you are on, depending on how you've defined your routing, your interceptors and your redirects. However, if you are always on the same entrance point root with additional clientside routing, Angular will always restore that modules state as long as you write it in an idempotent fashion.
  • David
    David over 8 years
    Ok, do you think you could give a code/pseudocode example of how I could define a default state so that the index page loads Categories/Index.cshtml upon first navigation? I usually end up with an infinite redirection loop when I try :)
  • David L
    David L over 8 years
    @David Added. Note that the main entrance point is NOT the default home view. They can and should be different to avoid your redirect loop.
  • David
    David over 8 years
    Amazing answer that has cleared up days of frustration. Going to try some new stuff now :)
  • David L
    David L over 8 years
    @David Happy to help! It's an incredibly nutty concept when you're first peering into it. Best of luck!
  • David
    David over 8 years
    Note that the main entrance point is NOT the default home view.. The key, aha moment. main.cshtml is essentially equivalent to the Razor master page.
  • David L
    David L over 8 years
    @David correct :). We just use it bootstrap our angular application and let it take over from there.
  • David
    David over 8 years
    I've followed your steps and everything seems to work beautifully - but it seems that when I refresh the page it's redirecting the request to ASP.Net, which doesn't know what the hell to do with it (for example I have an 'adminpanel' state with the url 'admin' and templateUrl /AdminPanel/Index. I'm loading this state with $state.go. The first time it loads, but as soon as I reload the page from the browser, it sends a request to site.com/admin to ASP, which results in a 404. RouteConfig is exactly as your post.
  • David L
    David L over 8 years
    @David that sounds like it might be another question altogether, but your URL doesn't sound correct. It should be site.com/!/admin. Without the bang, you won't be on the main route and you wouldn't be able to route properly on reload. See angular-ui.github.io/ui-router/sample/#/about as an example
  • nevradub
    nevradub almost 7 years
    Working for me, but i have a question. Why when you load first time a page is slowly ? AngularJS use a cache after the load page ?