AngularJS: How to make angular load script inside ng-include?
Asked Answered
S

9

85

Hey I am building a web page with angular. The problem is that there are somethings already build without angular and I have to include them as well

The problem is this.

I have something like this in my main.html:

<ngInclude src="partial.html">
</ngInclude>

And my partial.html has something like this

<h2> heading 1 <h2>
<script type="text/javascript" src="static/js/partial.js">
</script>

And my partial.js has nothing to do with angularjs. nginclude works and I can see the html, but I can not see the javascript file being loaded at all. I know how to use firebug/ chrome-dev-tool, but I can not even see the network request being made. What am I doing wrong?

I knwo angular has some special meaning to script tag. Can I override it?

Sippet answered 30/8, 2012 at 13:22 Comment(0)
H
101

The accepted answer won't work from 1.2.0-rc1+ (Github issue).

Here's a quick fix created by endorama:

/*global angular */
(function (ng) {
  'use strict';

  var app = ng.module('ngLoadScript', []);

  app.directive('script', function() {
    return {
      restrict: 'E',
      scope: false,
      link: function(scope, elem, attr) {
        if (attr.type === 'text/javascript-lazy') {
          var code = elem.text();
          var f = new Function(code);
          f();
        }
      }
    };
  });

}(angular));

Simply add this file, load ngLoadScript module as application dependency and use type="text/javascript-lazy" as type for script you which to load lazily in partials:

<script type="text/javascript-lazy">
  console.log("It works!");
</script>
Health answered 19/11, 2013 at 13:31 Comment(7)
I dont think this works if your script tag has a src attribute rather than inline js.Thermy
@Thermy Try this one: gist.github.com/subudeepak/9617483#file-angular-loadscript-jsHealth
This works perfectly, however there's one more thing I want. How do I remove this once the directive have been destroyed?Each
When I declare controller in the lazy script, I got error when refer to the controller in the view. Do you know why?Coney
That did the trick. I've used it to run tab initialisation code for a tabset loaded in the partialCulvert
Are there any security considerations one needs to take into account when using this?Levasseur
Thank you for providing this! So helpful!Wop
R
39

Short answer: AngularJS ("jqlite") doesn't support this. Include jQuery on your page (before including Angular), and it should work. See https://groups.google.com/d/topic/angular/H4haaMePJU0/discussion

Riverhead answered 30/8, 2012 at 15:37 Comment(4)
How does this work if your partial.js contains a controller? The template will render before the controller loads and give an error: Argument 'MyController' is not a function, got undefined.Siskind
@sthomps, see if this helps: Loading a controller dynamicallyRiverhead
I don't understand why this works - won't the browser automatically load an injected script tag? Why is this a jQuery or jqLite concern?Playhouse
worked for me as well, the content of my <script> tag was angularjs unrelated. When i did not load jQuery before angularjs everything in my partial below and including the script tag was removed, very weird.Larue
D
20

I tried neemzy's approach, but it didn't work for me using 1.2.0-rc.3. The script tag would be inserted into the DOM, but the javascript path would not be loaded. I suspect it was because the javascript i was trying to load was from a different domain/protocol. So I took a different approach, and this is what I came up with, using google maps as an example: (Gist)

angular.module('testApp', []).
    directive('lazyLoad', ['$window', '$q', function ($window, $q) {
        function load_script() {
            var s = document.createElement('script'); // use global document since Angular's $document is weak
            s.src = 'https://maps.googleapis.com/maps/api/js?sensor=false&callback=initialize';
            document.body.appendChild(s);
        }
        function lazyLoadApi(key) {
            var deferred = $q.defer();
            $window.initialize = function () {
                deferred.resolve();
            };
            // thanks to Emil Stenström: http://friendlybit.com/js/lazy-loading-asyncronous-javascript/
            if ($window.attachEvent) {  
                $window.attachEvent('onload', load_script); 
            } else {
                $window.addEventListener('load', load_script, false);
            }
            return deferred.promise;
        }
        return {
            restrict: 'E',
            link: function (scope, element, attrs) { // function content is optional
            // in this example, it shows how and when the promises are resolved
                if ($window.google && $window.google.maps) {
                    console.log('gmaps already loaded');
                } else {
                    lazyLoadApi().then(function () {
                        console.log('promise resolved');
                        if ($window.google && $window.google.maps) {
                            console.log('gmaps loaded');
                        } else {
                            console.log('gmaps not loaded');
                        }
                    }, function () {
                        console.log('promise rejected');
                    });
                }
            }
        };
    }]);

I hope it's helpful for someone.

Deprived answered 31/10, 2013 at 19:46 Comment(1)
Weird, I made my own version on 1.2.0-rc3 with external scripts :) Your version is way better anyway, thanks for sharing this !Drivein
M
12

I used this method to load a script file dynamically (inside a controller).

var script = document.createElement('script');
script.type = 'text/javascript';
script.src = "https://maps.googleapis.com/maps/api/js";
document.body.appendChild(script);
Monogamist answered 23/1, 2017 at 2:39 Comment(2)
nice one @azmeerDewar
Voted up because it is simple and best solution for dynamic script loading.Silesia
D
8

This won't work anymore from 1.2.0-rc1. See this issue for more about it, in which I posted a comment describing a quick workaround. I'll share it here as well :

// Quick fix : replace the script tag you want to load by a <div load-script></div>.
// Then write a loadScript directive that creates your script tag and appends it to your div.
// Took me one minute.

// This means that in your view, instead of :
<script src="/path/to/my/file.js"></script>

// You'll have :
<div ng-load-script></div>

// And then write a directive like :
angular.module('myModule', []).directive('loadScript', [function() {
    return function(scope, element, attrs) {
        angular.element('<script src="/path/to/my/file.js"></script>').appendTo(element);
    }
}]);

Not the best solution ever, but hey, neither is putting script tags in subsequent views. In my case I have to do this is order to use Facebook/Twitter/etc. widgets.

Drivein answered 29/10, 2013 at 10:38 Comment(0)
F
4

ocLazyLoad allows to lazily load scripts in the templates/views via routers (e.g. ui-router). Here is a sniplet

$stateProvider.state('parent', {
    url: "/",
    resolve: {
        loadMyService: ['$ocLazyLoad', function($ocLazyLoad) {
             return $ocLazyLoad.load('js/ServiceTest.js');
        }]
    }
})
.state('parent.child', {
    resolve: {
        test: ['loadMyService', '$ServiceTest', function(loadMyService, $ServiceTest) {
            // you can use your service
            $ServiceTest.doSomething();
        }]
    }
});  
Fastidious answered 27/2, 2015 at 7:38 Comment(1)
i faced the same issue like this. in enterprise app you have to use all your routes so its better to use ng-include with with tabs but ng-include has its issues wich is it has no dependency loading feature..Polder
H
1

To dynamically load recaptcha from a ui-view I use the following method:

In application.js:

    .directive('script', function($parse, $rootScope, $compile) {
    return {
        restrict: 'E',
        terminal: true,
        link: function(scope, element, attr) {
            if (attr.ngSrc) {
                 var domElem = '<script src="'+attr.ngSrc+'" async defer></script>';
                 $(element).append($compile(domElem)(scope));


            }
        }
    };
});

In myPartial.client.view.html:

 <script type="application/javascript" ng-src="http://www.google.com/recaptcha/api.js?render=explicit&onload=vcRecaptchaApiLoaded"></script>
Harryharsh answered 12/4, 2015 at 1:36 Comment(4)
does that add to or override the angular native script directive? And is that using jquery with that?Daff
It adds to. And no, no Jquery is used thereHarryharsh
thx - 'no jQuery is used here' -weird- $(element) why do you need the $() syntax. from the ajs docs 'All element references in Angular are always wrapped with jQuery or jqLite' (such as the element argument in a directive's compile / link function) - so from that and my other experiences - I was expecting you to use 'element.append(...)` - although i ended up not being what I could use in the end - when testing Iwas confused when I saw that part and didn't work for me when I tried only worked with the 'element.append' syntaxDaff
doh yes it is, but you could replace that with angular.element() if you wanted, most likely.Harryharsh
A
0

Unfortunately all the answers in this post didn't work for me. I kept getting following error.

Failed to execute 'write' on 'Document': It isn't possible to write into a document from an asynchronously-loaded external script unless it is explicitly opened.

I found out that this happens if you use some 3rd party widgets (demandforce in my case) that also call additional external JavaScript files and try to insert HTML. Looking at the console and the JavaScript code, I noticed multiple lines like this:

document.write("<script type='text/javascript' "..."'></script>");

I used 3rd party JavaScript files (htmlParser.js and postscribe.js) from: https://github.com/krux/postscribe. That solved the problem in this post and fixed the above error at the same time.

(This was a quick and dirty way around under the tight deadline I have now. I am not comfortable with using 3rd party JavaScript library however. I hope someone can come up with a cleaner and better way.)

Asafoetida answered 17/11, 2014 at 11:11 Comment(0)
N
0

I tried using Google reCAPTCHA explicitly. Here is the example:

// put somewhere in your index.html
<script type="text/javascript">
var onloadCallback = function() {
  grecaptcha.render('your-recaptcha-element', {
    'sitekey' : '6Ldcfv8SAAAAAB1DwJTM6T7qcJhVqhqtss_HzS3z'
  });
};

//link function of Angularjs directive
link: function (scope, element, attrs) {
  ...
  var domElem = '<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>';
  $('#your-recaptcha-element').append($compile(domElem)(scope));
}
Nightrider answered 22/12, 2014 at 6:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.