Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Lazy load non-angular JavaScript files using AngularJS?

Is it possible to load plain old JS or AMD modules from an Angular Controller? I have previously used RequireJS for this.

I have used AngularJS and RequireJS on a fairly large project before. I am working on a new project based on the MEAN Stack seed, and this does not use requireJS.

I am not entirely clear, but Angular has a system for loading modules -- can I load a particular piece of javascript from within my angular controller?

Is there a way to modify my module() declaration to include additional regular javascript files?

Thanks!

EDIT: To give a bit of understanding on what I am doing, I have a page that edits a few different forms. Each of these is saved into my database as a "form". Depending on the type of form, different dictionary values are mapped to different fields in different sub-views. Some of my forms have e.g dropdowns or lists of inputs. These are different, but everything else about the 'form' is handled in a generic way.

So I have this singular form controller that handles a bunch of different forms, and I am very happy with the result. The main issue comes from that each form has a separate set of data I would like to avoid loading unless I need.

I can check which form I am loading by checking my dropdown that drives my ng-include (which loads the subform).

In the short term I have just loaded everything and created namespaces under my scope to differentiate.

e.g $scope.form1 and $scope.form2 for data/rules specific to a particular form. I'd just as soon rather not load the js that I don't need.

Edit 2: http://jsfiddle.net/HB7LU/1320/

function MyCtrl($scope) {       
    $scope.doSomething = function()
    {
     //I'm used to wrapping with e.g require('jquery..... here, can I do the same thing with angular?   
        alert(" I need to run a jquery function here...");
        var xml = $(someblock);
    };
}

I've put up a fiddle with exactly what I am talking about. I want to load arbitrary JS depending on certain paths in my controller, and only when I need them.

Basically I have some larger namespaces I want to load depending on one of many options selected, and it would be expensive to just load them all.

like image 679
Yablargo Avatar asked Dec 14 '13 01:12

Yablargo


2 Answers

Ok, I commented my code so that it should explain everything. If you have any further questions, just let me know. This is the solution to the issues as are further explained in your comments. Live demo here (click).

Markup:

<!DOCTYPE html>
<html ng-app="myApp" ng-controller="myCtrl">
<head>
</head>
<body>

  <button ng-click="load()">Load Foo</button>
  <!-- I'm using this to bootstrap the lazy loaded script -->
  <section ng-repeat="item in loaded" lazy="item"></section>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>  
<script src="script.js"></script>
</body>
</html>

JavaScript:

var app = angular.module('myApp', []);

app.controller('myCtrl', function($scope) {
  //array of things to load
  $scope.lazyThings = [
    {directive:'my-foo-directive', file:'foo.js'}  
  ];
  $scope.loaded = [];
  $scope.load = function() {
    var loadIndex = $scope.loaded.length;
    if ($scope.lazyThings[loadIndex]) {
      $scope.loaded.push($scope.lazyThings[loadIndex]);
    }
  }
});

app.factory('myService', function($http) {
  return {
    getJs: function(path) {

      return $http.get(path).then(function(response) {
        deferred.resolve(response.data);
      });

    }
  }
});

//this adds an attribute to kick off the directive 
//for whatever script was lazy loaded
app.directive('lazy', function($compile, $q, myService) {
  var directiveReturn = {
    restrict: 'A',
    scope: {
      lazy: '='
    },
    link: function(scope, element) {
      myService.getJs(scope.lazy.file).then(function(data) {
        return addScript(scope.lazy.file, data, scope);
      }).then(function() {
        var $span = angular.element('<span></span>').attr(scope.lazy.directive, '');
        $compile($span)(scope);
        element.append($span);
      });
    }
  }

  var scriptPromises = {};
  function addScript(file, js, scope) {
    if (!scriptPromises[file]) { //if this controller hasn't already been loaded
      var deferred = $q.defer();
      //cache promise)
      scriptPromises[file] = deferred.promise;

      //inject js into a script tag
      var script = document.createElement('script');
      script.src = 'data:text/javascript,' + encodeURI(js);
      script.onload = function() {
        //now the script is ready for use, resolve promise to add the script's directive element
        scope.$apply(deferred.resolve());
      };
      document.body.appendChild(script);
      return deferred.promise;
    }
    else { //this script has been loaded before
      return scriptPromises[loadFile]; //return the resolved promise from cache
    }
  }

  return directiveReturn;
});

app.directive('myFooDirective', function() {
  return {
    restrict: 'A',
    link: function(scope, element) {
      //put the logic from your lazy loaded "foo.js" script here
      element.text(foo.someFunction());
    }
  }
});

Sample lazy loaded script:

var foo = {
  someFunction: function() {
    return 'I am data from a lazy loaded js function!';
  }
};

There are plenty of ways that you could implement the concept I demonstrated here. Just think about how you would like to use it, and write some directives to carry it out. Angular makes most things pretty simple.

Note: Injecting the script tag is optional - but I greatly prefer that rather than executing the script directly. When using the script tag, you will be able to track all of the loaded files with the dev tools under "Resources" in the "Scripts" section.

like image 196
m59 Avatar answered Nov 16 '22 01:11

m59


Actually i don't know much about angular's directives and it's modular things, but with my basic knowledge i build some preloading function for loading my JS files.... loadScript function is defined in my app.js which is included in main html page.

function loadScript(src,callback){

    var script = document.createElement("script");
    script.type = "text/javascript";
    if(callback)script.onload=callback;
    document.getElementsByTagName("head")[0].appendChild(script);
    script.src = src;
}

inside controller use something like this

app.controller('myCtrl', function($scope) {
  //array of things to load are saved in a JavascriptFile
  loadScript("URL_To_JavascriptFile");
});
like image 31
Sooraj ER Avatar answered Nov 16 '22 00:11

Sooraj ER