Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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" });
}
like image 562
David Avatar asked Sep 14 '15 20:09

David


1 Answers

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
        })
    });
like image 163
David L Avatar answered Oct 20 '22 18:10

David L