2

Angular provides ways to dynamically load templates with dynamic names via ng-include. Inline JS and CSS in that partial will load fine, but there is not a good way to download scripts with dynamic urls. We needed to download scripts, relative to the path of the .html partial calling them. (i.e. we had a directory with a file to include, and wanted the .html file to declare the scripts etc. it needed itself).

As opposed to AngularJS: How to make angular load script inside ng-include?, which is loading a script with a static src in a dynamically included partial, I want to include dynamically create the src for a script in a dynamically included partial, using either interpolation or running it through and angular function.

For example, I may be dynamically loading a partial into my app, from a directory in which there are several other resources that partial relies on. Instead of downloading all the files individually, I want to leave that logic in the partial. I realize that the browser cannot do this, so I'll use ng-src and allow Angular to do the heavy lifting here. Instead of parsing every script src tag relative to the partial, so I'm going to use a function to generate the urls, like so:

<script type="application/javascript" ng-src="prefixUrl('myUrl.js')"></script>

How do I do it?

georgeawg
  • 48,608
  • 13
  • 72
  • 95
Max Bates
  • 1,238
  • 1
  • 16
  • 21

1 Answers1

0

Here is a gist

Note that this will override the native script directive in angular (which checks for templates in your script tags). You could rename the directive, but we didn't need that functionality (we were injecting these services / directives into a newly bootstrapped app on the page anyway).

This assumes you have some way of downloading scripts dynamically. We are using $script, but jquery or whatever would work too, just update the service accordingly.

Instead of using what is below, which uses a function prefixUrl, you could easily rewrite the script directive to prefix the url itself (via attrs.ngSrc) without relying on the function.

I've added an event 'WidgetContentLoaded' (easy to change) that will fire once all the scripts are downloaded.

Javascript

angular.module('myApp')
.run(function ($rootScope) {
    $rootScope.mixin = function(urls) {

        if (angular.isUndefined(urls) || urls == '') {
            return $q.when('no mixin url');
        }

        var deferred = $q.defer();

        //timeout our requests at 5 seconds
        var timeoutPromise = $timeout(function() { deferred.reject(null) }, 5000);

        //assume that $script or some other way of downloading scripts is present
        $script(urls, function() {
            $timeout.cancel(timeoutPromise);
            $rootScope.$safeApply(deferred.resolve(urls));
        });

        return deferred.promise;
    };

    $document.on('WidgetContentLoaded', function () {
        //put more interesting logic here... this is like $(document).ready() but for your included partial
        console.log('yay we loaded your scripts');
    });

})
.service('lazyScripts', ['$q', '$timeout', '$document', function ($q, $timeout, $document) {

    var promises = [];

    this.register = function (url) {
        promises.push($clotho.extensions.mixin(url));
    };

    $timeout(function() {
        $q.all(promises).then(function() {
            //broadcast event
            $document.triggerHandler('WidgetContentLoaded');
        })
    });
}])
.directive('script', function($parse, $rootScope) {
    return {
        restrict: 'E',
        terminal: true,
        compile: function(element, attr) {
            if (attr.ngSrc) {
                var scriptUrl = $parse(attr.ngSrc)($rootScope);
                 lazyScripts.register(scriptUrl);
            }
        }
    };
});

And your HTML

<script type="application/javascript" ng-src="prefixUrl('inlineCalledScript.js')"></script>

<style type="text/css">
    .greenListItem {
        color: #44bb44;
    }
</style>

<ul>
    <li>This is a dynamically loaded template.</li>
    <li>Note that angular must already be bootstrapped, with the new script directive above. This will not work in your index.html file</li>
    <li class="greenListItem">Inline CSS works!</li>
</ul>

<!-- this would work without problems -->
<div ng-include="prefixUrl('anotherPartial.html')"></div>
Max Bates
  • 1,238
  • 1
  • 16
  • 21
  • disclaimer - I haven't tested this in a normal app. I am inserting them into a newly bootstrapped app using $provider and $compileProvider, and this does work in that case. – Max Bates Mar 20 '14 at 02:30
  • What is `$clotho`? It does not appear to be defined in your `lazyScripts` service. – Papples Mar 15 '15 at 14:04
  • Oops. You're right. It was basically just a wrapper for a javascript download which returned a promise instead of taking a callback, which didn't download the same path more than once. – Max Bates Mar 15 '15 at 21:43
  • I no longer have access to that code. You could use a library like $script (https://github.com/ded/script.js/) or any other JS loader, and just wrap it so that it returns a promise (i.e. wrap in a promise, resolve in the callback) – Max Bates Apr 13 '15 at 18:52