-1

I have a very simple AngularJS SPA, using UI Router to switch between two views. Upon clicking on the Home link, the user will see a line of text. The About link should do the same, but its routing is configured to use a templateUrl instead of a template string:

  $stateProvider
  .state("home", {
    url: "/home",
    template: "<h3>This is the Home content</h3>"
  })
  .state("about", {
    url: "/about",
    templateUrl: "about.html",
    controller: "aboutController"
  });

about.html is about as simple as it can be:

<!DOCTYPE HTML>
<html ng-app="simpleApp">
<head>
  <title>About</title>
  <script src="aboutcontroller.js"></script>
</head>
<body ng-controller="aboutController">
  <h3>{{message}}</h3>
</body>
</html>

... and its controller is also plain contrived boilerplate:

console.log("Registered aboutController"); ///////////
angular.module("simpleApp")
.controller("aboutController", ["$scope", function ($scope) {
  console.log("Created aboutController"); ///////////
  $scope.message = "This is the About content!";
}]);

Note the two console statements that helped me diagnose the problem.

The problem: While the controller script file executes, the controller itself is never called. So instead of seeing the text, the user just sees:

{{message}}

This seems to be related to the fact that I include the script file in the template file (about.html). If I move the script tag to index.html, the problem goes away and everything works as expected.

Here's a plunker that shows my problem and workaround.

The Real Problem: In this very simple and contrived example, it's reasonable to include all the controller scripts in the main page of the SPA. But for even a very simple web application, there may be dozens of pages with dozens of controllers. Adding all the controllers to the main page won't scale well for performance and won't scale well for maintenance.

So what is the appropriate way of setting this up? How/where should the controller script be included?

Edit: I forgot to mention the error I receive (yes, I know). With the script tag in about.html, I receive a message saying

Argument 'aboutController' is not a function, got undefined

This happens directly after the console message "Registered aboutController". If I move the script tag to include the controller in index.html instead, no error is shown. Again, to me this indicates that the controller is simply not available in time to call. Moved to index.html, it is fully registered by the time it is needed.

5
  • If you see literally {{message}} on the page, that means the template hasn't been processed at all. If Angular did process the template, you'd simply see nothing if message wasn't populated. That points to something producing a fatal error. Have you checked your Javascript console for errors? Commented Mar 15, 2016 at 10:07
  • Why does your about template contain an entire HTML file? It should just contain the snippet relevant for the particular view. You must also not use ng-controller in the file, since you're already using controller: ... in the router. You must include the file with the controller into your main file, as a dependency of your main module, otherwise the router can't find the controller when trying to load your route. Commented Mar 15, 2016 at 10:11
  • @deceze I took some time crafting a much more complete description in the plunker's readme.md doc and somehow forgot to mention the error here. Yes, there is an error to say aboutController is not a function. This displays directly after my console message "Registered aboutController.". If I move the script tag to index.html, the message disappears. So even though the script to register the controller executes in time, the actual controller cannot be created. I'll add this to the question. Thanks for pointing it out. Commented Mar 15, 2016 at 10:13
  • @deceze I have a full HTML file in the template mostly because that's where I ended up with all my tinkering. The original only had the script and h3 tags, but it makes no difference to the outcome. Commented Mar 15, 2016 at 10:15
  • @deceze About using ng-controller: Again, this is the result of tinkering and makes no difference to the result (tested). This part of what you're saying: "You must include the file with the controller into your main file, as a dependency of your main module, otherwise the router can't find the controller when trying to load your route" is the perfect description of what I found. I see that as a workaround though, because (as I mention at the end of my post) that strategy doesn't scale very well at all. Commented Mar 15, 2016 at 10:35

3 Answers 3

1

I figured it out after many hours of research. These are the things I needed to do:

  1. requireJS, to allow me to easily load scripts at runtime. This ended up being a huge volume of work, because everything needs to be loaded dynamically and I needed to set up the dependencies for all our directives and third-party controls. You can go without requireJS, but it simply isn't worth the trouble.
  2. Change the state call to provide the controller using resolve(). This is actually a hack because the resolve() function is actually meant to load the controller's dependencies, but loading the controller's script at this point works just fine. The simpler way of doing this involved using angularAMD, which provides a simple wrapper looking like the snippet below. Unfortunately, it doesn't give you the opportunity to dynamically build the controller name from parameters, so I ended up overriding resolve myself. If your needs are simpler, then using angularAMD is the obvious choice.

Here's what it looks like when you specify the view normally:

$stateProvider.state("home", {
    url: "",
    views: {
    // Other views...
      "client": {
        templateUrl: "clientselection.html",
        controller: "ClientSelectController",
        controllerUrl: "Controllers/ClientSelect.controller"
      }
    }
  });

Here's the same thing with angularAMD. The difference is that the controller is now loading on demand:

$stateProvider.state("home", {
    url: "",
    views: {
    // Other views...
      "client": angularAMD.route({
        templateUrl: "clientselection.html",
        controller: "ClientSelectController",
        controllerUrl: "Controllers/ClientSelect.controller"
      })
    }
  });

An honorable mention must go to Dan Wahlin's terrific blog post. It doesn't use UI Router, but the concepts are transferrable. Unfortunately, the post only gives incomplete snippets. The rest of it you'll need to pick out of a mammoth sample project he uploaded on Github. I don't follow why that was needed, but the blog post is still worth the read.

Sign up to request clarification or add additional context in comments.

Comments

0

For routing in angular, you should use routeProvider it you can then use it like this for navigation:

var app = angular.module('moduleName', [
'ngRoute',
'angularModalService'
]);

app.config(['$routeProvider',
function($routeProvider) {
    $routeProvider.
        when('/home', {
        templateUrl: 'partials/home.html',
        controller: 'homeController'
    }).otherwise({
        redirectTo: '/home'
    });

Here you bind the homeController to the whole home.html, if however you need to have a controller bound to a smaller part of the page you can bind it like so:

<div class="row" ng-controller="homeController">
   //Further HTML
</div>

The controller will be only available within this div.

For the including part, I usually define all the script just above the </body> tag. I seperat them, first the librarys like jQuery and AngularJS, and below them first the app.js (the file with the routeProvoder), the controllers, then the services/factory's and last the directives

1 Comment

The question is about ui router, exactly because that's what I chose to use in the place of ngRoute. Google can give you all the reasons, but in my case it comes down mostly to the need to have multiple named views.
0

To answer your question, Angular is designed in such a way that it loads all its js files and the gives the control to angular. While loading the Js files, all the controllers, Services etc., are registered with the ngApp. That is why it is recommended to give all the script files in the index.html itself.

If you think that giving all the files in a single index.html is clumsy, then you can split your application into modules and then load them. This is a better way to make your app look less clumsy.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.