Consider the following scenario:

  • We have an AngularJS app with views that require some raw JavaScript (in this particular example, drawing a chart).
  • Some of these scripts will only be used in one view, and some will be used in multiple views or multiple times in the same view.
  • How do we load and execute these scripts in a way that is consistent with Angular’s architecture?

For those of you frantically Googling for a solution, here’s a tl;dr. But I encourage you to read the whole post and understand it!

A Quick Angular Review

Let’s stop for a second and remember why we’re using Angular in the first place:

to build single page applications (SPAs)

SPAs are great for most users, no matter the device, because we can avoid loading stuff we don’t need. Whether that’s HTML, data, or in our case scripts, the beauty of SPAs lies in the laziness of downloading resources. This limits data usage to only what’s necessary and reduces the amount of client-side processing required to display the page. In particular, Angular simplifies the development of SPAs for us. We can write code that’s cleaner, more reusable, and that makes more efficient use of the browser’s (and thus the device’s) memory.

With that in mind, let’s start thinking about a solution to our script loading problem.

The Traditional (non-SPA) Approach

If we weren’t developing a SPA, we’d have multiple pages that would be loaded completely and individually. In this case, we’d either write or include our scripts in their respective pages.

In Angular though, we use templates for different views. And templates aren’t complete pages. So we can’t include a script in a template definition.

*index.html - the page that contains our whole app *

<!DOCTYPE html>
<html lang="en-us" ng-app="farmApp">
    <head>
        <title>My AngularJS App</title>

        <!-- load AngularJS -->
        <script src="http://code.angularjs.org/1.3.0-rc.2/angular.min.js"></script>
        <script src="./app.js"></script>

        <!-- Bootstrap, styling, etc. -->

    </head>
    <body>

        <!-- Maybe a page header -->

        <div ng-view></div>

    </body>
</html>

A template - rendered in index.html at the ng-view div. Note that none of this is enclosed in an <html> tag, so it isn’t a standalone page.

<a href="#">Back to Home</a>

<h1>Name: </h1>

<ul>
    <li ng-repeat="v in values">
        <h3></h3>
        <p></p>
    </li>
</ul>


“Makes sense,” you say. “So I’ll just include my scripts in the parent page for all my views.”

index.html

...
<head>
    <title>My AngularJS App</title>

    <!-- load AngularJS -->
    <script src="http://code.angularjs.org/1.3.0-rc.2/angular.min.js"></script>
    <script src="./app.js"></script>

    <!-- raw JavaScript -->
    <script src="script1.js"></script>
    <script src="script2.js"></script>
    <script src="script3.js"></script>

    ...

</head>
...

This doesn’t work for several reasons:

  1. It defeats the purpose of SPAs. We’re now loading and running a bunch of scripts that we don’t need yet or maybe won’t need at all.
  2. Most of these scripts are specific to some particular view(s). Running them as soon as you load one page will probably just not work at all.
  3. Even if we could put scripts in our templates, what if the scripts require data from other Angular logic? We’d have to pass things from the controllers to the view in question to the associated script… which would be a debugging nightmare in anything beyond an introductory app.

An Angular Friendly Solution

The solution is simple: use a service.

Services are one of the fundamental components of well-designed AngularJS apps.

  1. They make your code reusable. Services can be used anywhere in your controller logic or even in other services.
  2. They isolate potentialy complicated code. Things like retrieving data from third party APIs or doing lengthy computations can clutter your controller code very quickly. Services let you factor that out into more manageable pieces.
  3. They’re singletons. So they’re only loaded once in your entire application, which saves memory.
    • As a reminder, a singleton is an object-oriented design pattern in which only one instance of the object is ever created. Referencing it anywhere in the application returns this single instance.

Putting our script in a service is very easy.

  1. Create a new service. This service could contain just one script. Or, if you have a few scripts that are frequently used together, you could bundle them into one service.
  2. Create a function in the service that contains the script.

     myApp.service('scriptService', function() {
         this.myScriptFunc = function(someData) {
             ...
         };
     });
    

    Wrap the script in $scope.$apply() if necessary - if anything in the raw JavaScript needs to be monitored and updated while the view is displayed to the user, it should be added to Angular’s watch list. See here for a more in-depth explanation.

  3. Inject and use this service in the controller that’s associated with the view.

     myApp.controller('mainCtrl', ['$scope', 'scriptService', function($scope, scriptService) {
         $scope.values = scriptService.myScriptFunc(myData);
         ...
     }]);
    

That’s all there is to it! You can now use arbitrary JavaScript wherever you like, cleanly and in an SPA-friendly fashion.

** As of writing this, I’ve only been using AngularJS for about three weeks. So if I said anything that’s incorrect or could be explained better - or if there’s a better way to approach this entire problem - please let me know! Drop a comment or tweet to me @hudsonburgess7 **

tl;dr

For those of you frantically Googling for a solution: make a custom service

  • put the script in a service
  • surround the script with $scope.$apply() if necessary
  • use this service in the template’s controller logic