2013-09-03 37 views
42

Tôi đang sử dụng Require.js kết hợp với Angular.js.Mô-đun nạp động, chỉ khi được yêu cầu

Một số bộ điều khiển cần phụ thuộc lớn bên ngoài mà những người khác không cần, ví dụ: FirstController yêu cầu Angular UI Codemirror. Đó là thêm 135 kb, ít nhất:

require([ 
    "angular", 
    "angular.ui.codemirror" // requires codemirror itself 
], function(angular) { 
    angular.module("app", [ ..., "ui.codemirror" ]).controller("FirstController", [ ... ]); 
}); 

Tôi không muốn bao gồm chỉ thị và lib Codemirror mỗi khi trang của tôi tải chỉ để làm cho Angular hạnh phúc.
Đó là lý do tại sao tôi hiện đang tải bộ điều khiển chỉ khi tuyến đường bị trúng, like what's done here.

Tuy nhiên, khi tôi cần một cái gì đó giống như

define([ 
    "app", 
    "angular.ui.codemirror" 
], function(app) { 
    // ui-codemirror directive MUST be available to the view of this controller as of now 
    app.lazy.controller("FirstController", [ 
    "$scope", 
    function($scope) { 
     // ... 
    } 
    ]); 
}); 

Làm thế nào tôi có thể nói kiễu góc phải tiêm ui.codemirror mô-đun (hoặc bất kỳ thành phần khác) trong mô-đun là tốt ứng dụng?
Tôi không quan tâm nếu đó là một cách hackish để thực hiện điều này, trừ khi nó liên quan đến việc sửa đổi mã phụ thuộc bên ngoài.

Nếu nó hữu ích: Tôi đang chạy Angular 1.2.0.

Trả lời

33

Tôi đã cố gắng kết hợp requirejs + Angular trong một thời gian. Tôi đã xuất bản một dự án nhỏ trong Github (angular-require-lazy) với nỗ lực của tôi cho đến nay, vì phạm vi quá lớn đối với mã nội tuyến hoặc câu đố. Dự án thể hiện các điểm sau:

  • Mô-đun AngularJS được tải chậm.
  • Chỉ thị cũng có thể được tải quá tải.
  • Có một "module" phát hiện và cơ chế siêu dữ liệu (xem dự án của tôi khác con vật cưng: require-lazy)
  • Ứng dụng này được chia thành các gói tự động (tức là xây dựng với r.js công trình)

Làm thế nào là nó thực hiện:

  • Các nhà cung cấp (ví dụ $controllerProvider, $compileProvider) được chụp từ một config chức năng (kỹ thuật đầu tiên tôi nhìn thấy trong angularjs-requirejs-lazy-controllers).
  • Sau khi khởi động, angular được thay thế bằng trình bao bọc riêng của chúng tôi có thể xử lý các mô-đun được nạp lười.
  • Đầu phun được chụp và cung cấp như một lời hứa.
  • Mô-đun AMD có thể được chuyển đổi thành mô-đun Góc.

Triển khai này thỏa mãn nhu cầu của bạn: mô-đun góc tải lười (ít nhất là lưới ng tôi đang sử dụng), chắc chắn là hackish :) và không sửa đổi thư viện bên ngoài.

Nhận xét/ý kiến ​​được hoan nghênh hơn.


(EDIT) Sự khác biệt của giải pháp này từ những người khác là nó không làm động require() cuộc gọi, do đó có thể được xây dựng với r.js (và tôi yêu cầu-lười biếng dự án). Khác hơn là những ý tưởng được nhiều hơn hoặc ít hội tụ trên các giải pháp khác nhau.

Chúc bạn may mắn!

+0

Giải pháp tuyệt vời bro - Tôi sẽ nghiên cứu nó sớm Tôi có thời gian. Cảm ơn! – gustavohenke

+1

Bạn có kế hoạch làm cho Angular + Require.js có thể kiểm tra qua E2E? – gustavohenke

+1

Tôi chắc chắn sẽ làm cho nó có thể testable một số cách.Để trung thực, tôi đã không thử Angular E2E thử nghiệm được nêu ra –

4

Chìa khóa cho điều này là bất kỳ mô-đun nào mô-đun app của bạn cũng phụ thuộc vào cũng cần phải là mô-đun tải chậm. Điều này là do nhà cung cấp và cá thể lưu trữ rằng sử dụng góc cho dịch vụ $ injector của nó là riêng tư và họ không vạch trần một phương thức để đăng ký các mô-đun mới sau khi khởi tạo xong. Vì vậy, cách 'hacky' để thực hiện việc này là chỉnh sửa từng mô-đun bạn muốn tải xuống để yêu cầu một đối tượng tải mô-đun lười (Trong ví dụ bạn đã liên kết, mô-đun nằm trong tệp 'appModules'. js '), sau đó chỉnh sửa từng bộ điều khiển, chỉ thị, nhà máy, v.v. để sử dụng app.lazy.{same call} thay thế.

Sau đó, bạn có thể tiếp tục theo dõi dự án mẫu mà bạn đã liên kết bằng cách xem các tuyến đường ứng dụng được tải như thế nào (tệp 'appRoutes.js' hiển thị cách thực hiện điều này).

Không quá chắc chắn nếu điều này có ích, nhưng chúc may mắn.

+0

Yep, điều này sẽ giúp một cách nào đó. Cảm ơn. – gustavohenke

+0

Mặc dù tôi thích cách tôi giải quyết vấn đề này (xem câu trả lời), bạn cũng rất tốt. Cảm ơn. – gustavohenke

7

Chú ý: sử dụng giải pháp của Nikos Paraskevopoulos, vì nó đáng tin cậy hơn (tôi đang sử dụng nó) và có nhiều ví dụ hơn.


Được rồi, tôi cuối cùng đã tìm ra cách để đạt được điều này với sự giúp đỡ ngắn với answer này.

Như tôi đã nói trong câu hỏi của mình, điều này đã trở thành một cách rất khó khăn. Nó envolves áp dụng mỗi chức năng trong mảng _invokeQueue của mô-đun phụ thuộc trong bối cảnh của các mô-đun ứng dụng.

Đó là một cái gì đó như thế này (chú ý hơn trong hàm moduleExtender xin vui lòng):

define([ "angular" ], function(angular) { 
    // Returns a angular module, searching for its name, if it's a string 
    function get(name) { 
     if (typeof name === "string") { 
      return angular.module(name); 
     } 

     return name; 
    }; 

    var moduleExtender = function(sourceModule) { 
     var modules = Array.prototype.slice.call(arguments); 

     // Take sourceModule out of the array 
     modules.shift(); 

     // Parse the source module 
     sourceModule = get(sourceModule); 
     if (!sourceModule._amdDecorated) { 
      throw new Error("Can't extend a module which hasn't been decorated."); 
     } 

     // Merge all modules into the source module 
     modules.forEach(function(module) { 
      module = get(module); 
      module._invokeQueue.reverse().forEach(function(call) { 
       // call is in format [ provider, function, args ] 
       var provider = sourceModule._lazyProviders[ call[ 0 ] ]; 

       // Same as for example $controllerProvider.register("Ctrl", function() { ... }) 
       provider && provider[ call[ 1 ] ].apply(provider, call[ 2 ]); 
      }); 
     }); 
    }; 

    var moduleDecorator = function(module) { 
     module = get(module); 
     module.extend = moduleExtender.bind(null, module); 

     // Add config to decorate with lazy providers 
     module.config([ 
      "$compileProvider", 
      "$controllerProvider", 
      "$filterProvider", 
      "$provide", 
      function($compileProvider, $controllerProvider, $filterProvider, $provide) { 
       module._lazyProviders = { 
        $compileProvider: $compileProvider, 
        $controllerProvider: $controllerProvider, 
        $filterProvider: $filterProvider, 
        $provide: $provide 
       }; 

       module.lazy = { 
        // ...controller, directive, etc, all functions to define something in angular are here, just like the project mentioned in the question 
       }; 
       module._amdDecorated = true; 
      } 
     ]); 
    }; 

    // Tadaaa, all done! 
    return { 
     decorate: moduleDecorator 
    }; 
}); 

Sau này đã được thực hiện, tôi chỉ cần, ví dụ, để làm điều này:

app.extend("ui.codemirror"); // ui.codemirror module will now be available in my application 
app.controller("FirstController", [ ..., function() { }); 
+0

Bạn có thể đưa ra ví dụ bên trong 'module.lazy = {...}' không? – Freewind

+2

Đi với giải pháp của Nikos. Tôi không còn sử dụng cái này nữa. – gustavohenke

+0

Dự án của Nikos quá comples, tôi không thể có được ý tưởng: ( – Freewind

0

Vấn đề với kỹ thuật tải lười hiện có là chúng làm những việc mà tôi muốn tự mình làm.

Ví dụ, sử dụng requirejs, tôi muốn chỉ cần gọi:

require(['tinymce', function() { 
    // here I would like to just have tinymce module loaded and working 
}); 

Tuy nhiên nó không hoạt động theo cách đó. Tại sao? Theo tôi hiểu, AngularJS chỉ đánh dấu mô-đun là 'được tải trong tương lai', và nếu, ví dụ, tôi sẽ đợi một chút, nó sẽ hoạt động - tôi sẽ có thể sử dụng nó.Vì vậy, trong hàm ở trên, tôi muốn gọi một số hàm như loadPendingModules();

Trong dự án của tôi, tôi tạo ra cung cấp dịch vụ đơn giản ('lazyLoad') mà thực hiện chính xác điều này và không có gì hơn, vì vậy bây giờ, nếu tôi cần phải có một số mô-đun hoàn toàn tải, tôi có thể làm như sau:

myApp.controller('myController', ['$scope', 'lazyLoad', function($scope, lazyLoad) { 

    // ........ 

    $scope.onMyButtonClicked = function() { 

     require(['tinymce', function() { 
      lazyLoad.loadModules(); 

      // and here I can work with the modules as they are completely loaded 
     }]); 
    }; 

    // ........ 

}); 

ở đây là liên kết đến tập tin nguồn (MPL giấy phép): https://github.com/lessmarkup/less-markup/blob/master/LessMarkup/UserInterface/Scripts/Providers/lazyload.js

0

Tôi gửi cho bạn mã mẫu. Nó làm việc tốt cho tôi. Vì vậy, hãy kiểm tra này:

var myapp = angular.module('myapp', ['ngRoute']); 

/* Module Creation */ 
var app = angular.module('app', ['ngRoute']); 

app.config(['$routeProvider', '$controllerProvider', function ($routeProvider, $controllerProvider) { 

app.register = { 
    controller: $controllerProvider.register, 
    //directive: $compileProvider.directive, 
    //filter: $filterProvider.register, 
    //factory: $provide.factory, 
    //service: $provide.service 
}; 


// so I keep a reference from when I ran my module config 
function registerController(moduleName, controllerName) { 
    // Here I cannot get the controller function directly so I 
    // need to loop through the module's _invokeQueue to get it 
    var queue = angular.module(moduleName)._invokeQueue; 
    for (var i = 0; i < queue.length; i++) { 
     var call = queue[i]; 
     if (call[0] == "$controllerProvider" && 
      call[1] == "register" && 
      call[2][0] == controllerName) { 
      app.register.controller(controllerName, call[2][1]); 
     } 
    } 
} 


var tt = { 
    loadScript: 
function (path) { 
    var result = $.Deferred(), 
    script = document.createElement("script"); 
    script.async = "async"; 
    script.type = "text/javascript"; 
    script.src = path; 
    script.onload = script.onreadystatechange = function (_, isAbort) { 
     if (!script.readyState || /loaded|complete/.test(script.readyState)) { 
      if (isAbort) 
       result.reject(); 
      else { 
       result.resolve(); 
      } 
     } 
    }; 
    script.onerror = function() { result.reject(); }; 
    document.querySelector(".shubham").appendChild(script); 
    return result.promise(); 
} 
} 

function stripScripts(s) { 
    var div = document.querySelector(".shubham"); 
    div.innerHTML = s; 
    var scripts = div.getElementsByTagName('script'); 
    var i = scripts.length; 
    while (i--) { 
     scripts[i].parentNode.removeChild(scripts[i]); 
    } 
    return div.innerHTML; 
} 


function loader(arrayName) { 
    return { 
     load: function ($q) { 
      stripScripts(''); // This Function Remove javascript from Local 
      var deferred = $q.defer(), 
      map = arrayName.map(function (obj) { 
       return tt.loadScript(obj.path) 
       .then(function() { 
        registerController(obj.module, obj.controller); 
       }) 
      }); 

      $q.all(map).then(function (r) { 
       deferred.resolve(); 
      }); 
      return deferred.promise; 
     } 
    }; 
}; 



$routeProvider 
    .when('/first', { 
     templateUrl: '/Views/foo.html', 
     resolve: loader([{ controller: 'FirstController', path: '/MyScripts/FirstController.js', module: 'app' }, 
      { controller: 'SecondController', path: '/MyScripts/SecondController.js', module: 'app' }]) 
    }) 

    .when('/second', { 
     templateUrl: '/Views/bar.html', 
     resolve: loader([{ controller: 'SecondController', path: '/MyScripts/SecondController.js', module: 'app' }, 
     { controller: 'A', path: '/MyScripts/anotherModuleController.js', module: 'myapp' }]) 
    }) 
    .otherwise({ 
     redirectTo: document.location.pathname 
     }); 
}]) 

Và trong HTML Page:

<body ng-app="app"> 

<div class="container example"> 
    <!--ng-controller="testController"--> 

    <h3>Hello</h3> 

    <table> 
     <tr> 
      <td><a href="#/first">First Page </a></td> 
      <td><a href="#/second">Second Page</a></td> 
     </tr> 
    </table> 




     <div id="ng-view" class="wrapper_inside" ng-view> 
     </div> 
    <div class="shubham"> 
    </div> 
</div> 

Các vấn đề liên quan