21

I am trying to bind the src attribute of an HTML <script> element to a variable in my angular controller, so that I can update it from controller without dealing with any UI.

So far I've tried all these options:

<script type="text/javascript" ng-src="{{sourceUrl}}"></script>
<script type="text/javascript" src="{{sourceUrl}}"></script>
<script type="text/javascript" ng-src="sourceUrl"></script>

In my controller I have:

$scope.sourceUrl = "https://<some url goes here>";

When running the page in the browser after the $scope.sourceUrl gets set there is no outgoing request to sourceUrl, so I am sure I am doing something wrong. Any ideas?

I found several posts about src attribute of <img> element, and ng-src should work as they say, but I guess <script> is somehow different.

3
  • <script> tags are interpreted only once, during the page loading, by the browser. Commented Dec 5, 2014 at 0:21
  • I accomplished this by having multiple <script> tags but enabling them using ng-if: e.g. <script ng-if="condition1" src="script1"/> <script ng-if="condition2" src="script2"/>. Also you should be aware of the scope you are using. To be safe add the scope variables to $rootScope Commented Dec 5, 2014 at 0:32
  • @Aidin, I use a lot of bindings in my code, so my controller scope is fine. The problem with multiple options is my sourceUrl is generated based on some input and some random data, so there is no limited set of URLs that I can pre-define. Commented Dec 5, 2014 at 0:41

7 Answers 7

24

Unfortunately, you can not use Angular in this way. Angular processes the web page only after the page has been loaded and built by which time the <script> tag has been processed its one time (script tags are only ever run once). Other tags such as img will change the visual appearance of the screen when their properties change after the page has loaded ... but as mentioned, the script is only processed once and even then during page load and before Angular can ever get control.

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

3 Comments

hey, thanks a lot for a quick response. However, I still think there should be a way to do that. The last resort, which I don't want to deal with is just getElementById("scriptElemId").src = newUrl;. But as I mentioned, I want to have pure model in the controller, no UI friction. Is there a better way to do that? Or maybe I will need to get rid of the old <script> element and render a new one with new value in src instead.
Personally, I frown on dynamically loaded code ... however, the generally accepted mechanism is to use "require.js" ... have a good study of the following and see if you can see how it can be used to dynamically load arbitrary code.... requirejs.org
I have solved my issue adding a directive to append a generated <script> element (with a generated url as a value for 'src' attribute) to a containing element. And it worked perfectly.
5

You could have added it dynamically to the end of the body from within the controller:

        $("<script>").attr({src: $scope.sourceUrl}).appendTo("body");

1 Comment

Not the Angular way, though
4

Adding my solution as an answer per @Jonathan's suggestion.

(function (ng) {

    // Retrieve main app module
    var appModule = angular.module('appModule');

    // This directive appends a child <script> element to an element with 'my-container' attribute.
    // This is needed for 'src' attribute generation and script evaluation of some object after the
    // page has been loaded.
    appModule.directive('myContainer', ['$log', 'MvcModelService', function ($log, MvcModelService) {
        return {
            restrict: 'A',
            scope: false,
            link: function (scope, elem, attrs) {
                var s = document.createElement("script");
                s.type = "text/javascript";

                var JSObjectName = "JSObject";

                // Just a random number ...
                var randomNumber = Math.floor(Math.random() * Number.MAX_VALUE);

                // flowId is a UUID representing current session.
                var flowId = MvcModelService.FlowId;

                // Construct the url  where the object contents will be loaded from:
                var Url = MvcModelService.UrlPrefix + "Get" + whatever + "/" + JSObjectName +
                          "someOtherStuffDepending on flowId and randomNumber";

                s.src = Url;

                $log.info("Adding script element to MyContainer with source url: " + Url);
                elem.append(s);
            }
        };
    }]);
}(angular));

And the view snippet follows:

<div id="JSObjectScript" style="display: inline" my-container />

Comments

3

@Kolban is right, anyway you can try to create the script element, add the src attr and then append it to the template. For example:

 var exampleController = function() {
 controller.exampleController= [ '$sce',function($sce) {

     //Remember to set the src as a trusted src

     var trustedSrc = $sce.getTrustedUrl("www.example.com");

     var scriptElement = document.createElement('script');

     //Add attributes 

     scriptElement.setAttribute("data-size", '1220x700' );
     scriptElement.setAttribute("src", trustedSrc);


     //Append the srcipt to some element in the template

     var elementContainer = angular.element('#elementToAppend');
     elementContainer.append(scriptElement);
 }]

return controllers;
}

Comments

1

Although the script tags may only be interpolated once you can just add more script tags.

<script ng-repeat="script in scripts" ng-src="{{script.src}}"></script>

In your controller just push more objects like {src: 'foo.js'} to the scripts array and presto, lazy loaded scripts.

Here is a Plunker that demonstrates this: http://plnkr.co/edit/6QuwuqsGoyrASk8FKmu2?p=preview

Comments

1

I know the answer comes late but here is an elegant and simple solution:

You have to use the Script Contextual Escaping with $sce.trustAsResourceUrl(value).

See Documentation

It would probably look like :

In app.js:

//Dynamic Url for Tagging
    $rootScope.exUrl1 = $sce.trustAsResourceUrl(confserver.example.url);

In index.html:

<script type="text/javascript" src="{{exUrl1}}"></script>

The src attribute of the script tag will be binded to its value in Angular. Here is the result when I run my webapp and launch the debugger to check the rendered HTML:

<script type="text/javascript" src="http://exampleUrl.js"></script>

Please note that the script will not be executed if the bind is done after the first loading of the tag !

1 Comment

Your solution didn't work for me until I used ng-src instead of src. After that setting the trusted url in ApplicationController did the trick.
0

Solution using AngularJS directive.

function dynamicScript () {
    return {
        restrict: 'E',
        scope: {
            dynamicScriptSrc: '='
        },
        link: function(scope, element, attrs, ngModel) {
            setScriptSrc(element, scope.$eval(attrs.dynamicScriptSrc));

            scope.$watch('dynamicScriptSrc', src => {
                setScriptSrc(element, src);
            });
        }
    }
}

function setScriptSrc(element, src) {
    if(src) {
        var newScript = document.createElement("script");
        newScript.type = 'text/javascript';
        newScript.src = src;
        element.empty();
        element.append(newScript);
    }
}

Usage:

<dynamic-script dynamic-script-src="vm.dynamicJsPath"></dynamic-script>

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.