22

I am trying to make a simple single page mobile app with multiple views and a next\back button to control each view. I am using the Angular Mobile UI library.

The basic mockup is as follows:

<html>
    <head>
        <link rel="stylesheet" href="mobile-angular-ui/dist/css/mobile-angular-ui-base.min.css">
        <link rel="stylesheet" href="mobile-angular-ui/dist/css/mobile-angular-ui-desktop.min.css">

        <script src="js/angular/angular.min.js"></script>
        <script src="js/angular/angular-route.min.js"></script>
        <script src="mobile-angular-ui/dist/js/mobile-angular-ui.min.js"></script>

        <script src="app/app.js"></script>
        <script src="app/firstController.js"></script>
        <script src="app/secondController.js"></script>
        <script src="app/thirdController.js"></script>

    </head>

    <body ng-app="demo-app">
        <div ng-view></div>

        <div ng-controller="nextBackController" class="navbar navbar-app navbar-absolute-bottom">
            <div class="btn-group justified">
              <a href="#/" class="btn btn-navbar btn-icon-only"><i class="fa fa-home fa-navbar"></i></a>
              <a href="#/second" class="btn btn-navbar btn-icon-only"><i class="fa fa-list fa-navbar"></i></a>
            </div>
        </div>

    </body>


</html>

App.js is as follows:

var app = angular.module('demo-app', [
  "ngRoute",
  "mobile-angular-ui"
]);

app.config(function($routeProvider) {
  $routeProvider.when('/', { controller: "firstController",
                             templateUrl: "views/first.html"});
  $routeProvider.when('/', { controller: "secondController",
                             templateUrl: "views/first.html"});
  $routeProvider.when('/', { controller: "thirdController",
                             templateUrl: "views/first.html"});
});

controllers = {};

controllers.nextBackController = function($scope, $rootScope) {
    //Simple controller for the next, back buttons so we just put it in app.js
};

app.controller(controllers);

firstController.js will contain something similar to:

controllers.firstController = function($scope) {
    //Do our logic here!!!
};

The problem is if you notice at the top of the HTML page I have to load all the controllers in. This is not scalable. I want each controller to be in it's own JS file and not have to statically load each one since the user may never even require that controller. Is there a way to dynamically load the actual JS file when switching routes? or can I stick a script tag at the top of my "first.html", "second.html", etc.

Dale K
  • 25,246
  • 15
  • 42
  • 71
user3621014
  • 271
  • 1
  • 2
  • 4
  • What you want to do is exactly how I'm doing it now. Each controller is in its own JS file. The way I load them is by doing: `app.controller("ControllerName", function () { /* Controller code */ });` (since you stored your app module in the global variable `app`). Therefore, I don't use `app.controller(controllers)` – Ian May 14 '14 at 13:39
  • 1
    @lan My question is how can I pull in the controller JS files as I need them as opposed to just putting all of them at the top of the main page in script tags. Let's say I have 20 controllers, one for each component. This will make loading time horrible. I only want to pull in firstController's .JS when\if I need it. Or does angular automatically search for the JS file?? – user3621014 May 14 '14 at 13:43
  • Especially for mobile solutions you should better concatenate all scripts, minify them and load only one javascript file. In most cases this will be faster than loading many small files. Latency is your enemy. – hgoebl May 14 '14 at 13:57
  • @user3621014 Ahhhh okay, I think I see now, sorry for misunderstanding. If you're worried about loading times, you can always look into minification, gzipping, and even bundling if that's a real concern. – Ian May 14 '14 at 13:57
  • have a look at this really cool sample project mrgamer created. Would'nt take this as the holy grail of all projects, but its cool to have another perspective on what you COULD do. in the solution everything is seperated for dev ease, but everything gets combined during the build phase... very cool stuff indeed. Makes for a pretty fast and slick SPA application https://github.com/mrgamer/angular-login-example.git – Rohan Büchner May 14 '14 at 14:00
  • I feel like these are related: http://stackoverflow.com/questions/17674945/angularjs-dynamic-loading-a-controller and http://stackoverflow.com/questions/20909525/load-controller-dynamically-based-on-route-group – Ian May 14 '14 at 14:20
  • possible duplicate of [Single page application - load js file dynamically based on partial view](http://stackoverflow.com/questions/15939913/single-page-application-load-js-file-dynamically-based-on-partial-view) – Paolo Moretti Jul 03 '14 at 17:21

7 Answers7

22

If I understand correctly you need to load specific scripts for each view? I am sharing this snippet from a personal project that uses ocLazyLoader a plugin that loads modules on demand.

var myApp = angular.module("myApp", [
"ui.router", 
"oc.lazyLoad",  
]); 

then in your routing you could load dynamic JS / CSS files accordingly, in this example I am loading the UI Select plugin dependencies

myApp.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {

$stateProvider

  // State
    .state('demo', {
        url: "/demo.html",
        templateUrl: "views/demo.html",
        data: {pageTitle: 'demo page title'},
        controller: "GeneralController",
        resolve: {
            deps: ['$ocLazyLoad', function($ocLazyLoad) {
                return $ocLazyLoad.load([{
                    name: 'ui.select',
 // add UI select css / js for this state
                    files: [
                        'css/ui-select/select.min.css', 
                        'js/ui-select/select.min.js'
                    ] 
                }, {
                    name: 'myApp',
                    files: [
                        'js/controllers/GeneralController.js'
                    ] 
                }]);
            }]
        }
    })
Dale K
  • 25,246
  • 15
  • 42
  • 71
Raja Khoury
  • 3,015
  • 1
  • 20
  • 19
  • 1
    You saved me! I had small issue with this and fiddling for more than 4 hours, but your solution just worked for me.Thanks man! – Rajkumar Jan 05 '16 at 09:02
7

If you're familiar with Grunt:

https://github.com/ericclemmons/grunt-angular-templates

Use Grunt and the above build task to create one .js from all views. Include a watch task listener over all html files in a views directory. Whenever a change is made to any partial views, a "$templateCache" entry is created with all of the html in the file and a url to alias the cache. Your routes will point to the partial views in the same manner, but the html files do not need to be present. Only the .js file with the templates. The beauty of this is that it loads once and is available on the client side for the entire session. This cuts down on http calls and your traffic can be reduced to web service calls, only.

This is the example of a template from the github link, above:

angular.module('app').run(["$templateCache", function($templateCache) {
$templateCache.put("home.html",
// contents for home.html ...
);
...
$templateCache.put("src/app/templates/button.html",
// contents for button.html
);
}]);

If you're not familiar with Grunt

Check it out. It's pretty invaluable for automating builds, minification, concatenation, transpiling, etc...

http://gruntjs.com/

Lee Duckworth
  • 240
  • 4
  • 15
5

Unless your app is MASSIVE, you should REALLY avoid serving small js files individually. This will noticeably slow down your app, even if you were to figure out a way to lazily fetch files on an as-needed basis as you suggest in your question.

A much better way to do this (and the way used and suggested by the AngularJS team) is to have a BUILD PROCESS (you should use grunt for this) concatenate all your javascript files, and serve them as a single app.js file. This way you can maintain an organized code base with as many tiny js files as you want, but reduce script fetching to a single request.

Evan Drewry
  • 794
  • 6
  • 17
4

How to Install OCLazyLoad.

1. Download ocLazyLoad.js here

It can be found in the 'dist' folder of the git repository. You can also install it with bower install oclazyload or npm install oclazyload.

2. Add the module oc.lazyLoad to your application:

var myApp = angular.module("MyApp", ["oc.lazyLoad"]);

3. Load your JS files on demand, based on routes:

    myApp.controller("MyCtrl", function($ocLazyLoad){
       $ocLazyLoad.load('testModule.js');
    }});`

With $ocLazyLoad you can load angular modules, but if you want to load any component (controllers / services / filters / ...) without defining a new module it's entirely possible (just make sure that you define this component within an existing module).

There are multiple ways to use $ocLazyLoad to load your files, just choose the one that you prefer.

Also don't forget that if you want to get started and the docs are not enough, see the examples in the 'examples' folder!

Quick Start

Community
  • 1
  • 1
AllJs
  • 1,760
  • 4
  • 27
  • 48
1

I am not sure how it works in standard angular, however, you could use angular-ui-router:

Controllers are instantiated on an as-needed basis, when their corresponding scopes are created, i.e. when the user manually navigates to a state via a URL, $stateProvider will load the correct template into the view, then bind the controller to the template's scope.

https://github.com/angular-ui/ui-router/wiki

Dale K
  • 25,246
  • 15
  • 42
  • 71
BAYELK
  • 73
  • 1
  • 10
  • I'm pretty sure this is exactly how normal angular routing works too. The OP's point is that they don't want to even load the controller (file) until it's needed...and I don't think the ui-router does anything different in regards to that – Ian May 14 '14 at 13:59
1

RequireJS can be really useful in this case. You can declare the dependencies of your JS files using RequireJS.

You can refer this simple tutorial.

Gurjaspal
  • 74
  • 2
1

I think the best way is use RequireJS, as mentioned here Does it make sense to use Require.js with Angular.js? It is totally allowed and it will let you reach what you are trying.

here is an example code

https://github.com/tnajdek/angular-requirejs-seed

Community
  • 1
  • 1
bto.rdz
  • 6,636
  • 4
  • 35
  • 52