How to Lazy load non-angular JavaScript files usin

2020-02-09 07:21发布

问题:

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.

回答1:

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.



回答2:

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");
});


回答3:

Angular's module system is not like RequireJS. Angular only concerns itself with module definition and composition but not with module loading.

That means angular will not issue an HTTP request for loading any javascript, you are responsible for loading that yourself, by using <script> tags or another javascript loader, like requirejs.

Furthermore, angular requires that all modules be loaded and configured before the angular application is bootstrapped. This makes it difficult to support any sort of lazy-loading schemes for angular modules.

Making angular be able to deal with more dynamic loading of modules is a featured planned for future releases.