AngularJS: ленивая загрузка контроллеров и контента
В этом упрощенном сценарии у меня есть два файла: index.htm, lazy.htm.
index.htm:
var myApp = angular.module('myApp', []);
myApp.controller('embed',function($scope){
$scope.embed = 'Embedded Controller';
});
<div ng-controller="embed">{{embed}}</div>
<div ng-include="'lazy.htm'"></div>
lazy.htm
myApp.controller('lazy',function($scope){
$scope.lazy = 'Lazy Controller';
});
<div ng-controller="lazy">
{{lazy}}
</div>
В результате возникает ошибка: "Аргумент" ленивый "не является функцией, получил неопределенное значение"
Использование функции вместо
lazy.htm
function lazy($scope) {
$scope.lazy = 'Lazy Controller';
}
<div ng-controller="lazy">
{{lazy}}
</div>
Это работает до версии 1.3 бета 14. В бета 15 были удалены глобальные функции контроллера: https://github.com/angular/angular.js/issues/8296
Итак, что является лучшим способом для динамического получения англоязычного содержимого lazy.htm?
ОБНОВИТЬ:
В этой статье ( http://ify.io/lazy-loading-in-angularjs) я нашел другое возможное решение. $ ControllerProvider позволяет нам регистрировать новые контроллеры после угловой загрузки. Работает как шарм. Протестировано в v1.3.0-beta.18
index.htm:
var myApp = angular.module('myApp', [])
.controller('embed',function($scope){
$scope.embed = 'Embedded Controller';
})
.config(function($controllerProvider) {
myApp.cp = $controllerProvider;
});
<div ng-controller="embed">{{embed}}</div>
<div ng-include="'lazy.htm'"></div>
lazy.htm
myApp.cp.register('lazy',function($scope){
$scope.lazy = 'Lazy Controller';
});
<div ng-controller="lazy">
{{lazy}}
</div>
ОБНОВЛЕНИЕ 2:
Две другие альтернативы, которые работают:
lazy.htm
_app = $('[ng-app]').scope();
_app.lazy = function($scope) {
$scope.lazy = 'Lazy Controller';
};
ИЛИ ЖЕ
var $rootScope = $('[ng-app]').injector().get('$rootScope');
$rootScope.lazy = function($scope) {
$scope.lazy = 'Lazy Controller';
};
Но я считаю, что эти два последних примера не должны использоваться в производстве.
9 ответов
Вы также можете использовать jquery с разрешением $ routeProvider
app.js
/* Module Creation */
var app = angular.module ('app', ['ngRoute']);
app.config(['$routeProvider', '$controllerProvider', function($routeProvider, $controllerProvider){
/*Creating a more synthesized form of service of $ controllerProvider.register*/
app.registerCtrl = $controllerProvider.register;
function loadScript(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("head").appendChild(script);
return result.promise();
}
function loader(arrayName){
return {
load: function($q){
var deferred = $q.defer(),
map = arrayName.map(function(name) {
return loadScript('js/controllers/'+name+".js");
});
$q.all(map).then(function(r){
deferred.resolve();
});
return deferred.promise;
}
};
}
$routeProvider
.when('/', {
templateUrl: 'views/foo.html',
resolve: loader(['foo'])
})
.when('/bar',{
templateUrl: 'views/bar.html',
controller: 'BarCtrl',
resolve: loader(['bar'])
})
.otherwise({
redirectTo: document.location.pathname
});
}]);
/views/foo.html
<section ng-controller='FooCtrl'>
{{text}}
</section>
JS / Контроллеры / foo.js
/*Here we use the synthesized version of $controllerProvider.register
to register the controller in view*/
app.registerCtrl('FooCtrl',function($scope){
$scope.text = 'Test';
});
/views/bar.html
<section>
{{text2}}
</section>
JS / Контроллеры /bar.js
app.registerCtrl('BarCtrl',function($scope){
$scope.text2 = 'Test';
});
Вы можете иметь чистую ленивую загрузку AngularJS.
Создать "LazyService":
var ng = angular.module('app');
ng.factory('lazyService', [ '$http', function($http) {
var jsPath = 'js/${ name }.js';
var promisesCache = {};
return {
loadScript: function(name) {
var path = jsPath.replace('${ name }', name);
var promise = promisesCache[name];
if (!promise) {
promise = $http.get(path);
promisesCache[name] = promise;
return promise.then(function(result) {
eval(result.data);
console.info('Loaded: ' + path);
});
}
return promise;
}
}
}]);
Затем определите ваш конфиг:
var ng = angular.module('app', [ 'ngRoute' ]);
ng.config([ '$routeProvider', '$controllerProvider', '$provide', function($routeProvider, $controllerProvider, $provide) {
// Lazy loading
ng.lazy = {
controller: $controllerProvider.register,
//directive: $compileProvider.directive,
//filter: $filterProvider.register,
factory: $provide.factory,
service: $provide.service
}
$routeProvider
.when('/', {
templateUrl: 'view/home.html'
})
.when('/vendor', {
templateUrl: 'view/vendor.html',
resolve: {
svc: [ 'lazyService', function(lazyService) {
return lazyService.loadScript('services/vendor');
}],
ctrl: [ 'lazyService', function(lazyService) {
return lazyService.loadScript('controllers/vendor');
}]
}
});
. . .
В "js / services / vendor.js" создайте службу как:
var ng = angular.module('app');
ng.lazy.service('vendorService', [ function() {
. . .
В "js / controllers / vendor.js" создайте контроллер как:
var ng = angular.module('app');
ng.lazy.controller('vendorController', [ function() {
. . .
Свойство "resol" в том, когда определяет, какие обещания должны быть разрешены до загрузки маршрута.
//// Файл JConfig --------
window.angularApp.config(function ($routeProvider,$controllerProvider,$compileProvider,$provide, azMessages) {
$routeProvider.when('/login', {
resolve: {
load: ['$q', '$rootScope', function ($q, $rootScope) {
var deferred = $q.defer();
require([
//load required Js file here
], function () {
$rootScope.$apply(function () {
deferred.resolve();
});
});
return deferred.promise;
} ]
}
});
$routeProvider.otherwise({ redirectTo: '/login' });
window.angularApp.components = {
controller: $controllerProvider.register,
service: $provide.service,
directive: $compileProvider.directive
}
// объявление контроллера
angularApp.components.controller('DiscussionController',[function(){
}]);
Сначала я воспользовался ответом Андре Бетиоло. Однако, это не всегда работает, потому что загрузка ajax не блокирует, заставляя представление иногда запрашивать контроллер перед загрузкой сценария.
В качестве решения я заставил функцию не возвращаться, пока все скрипты не будут успешно загружены. Это отчасти хакерский, но гарантирует, что загрузки успешны до завершения решения. Это также позволяет загружать несколько контроллеров.
app.js
var app = angular.module ('app', ['ngRoute']);
app.config(['$routeProvider', '$controllerProvider', function($routeProvider, $controllerProvider){
/*Creating a more synthesized form of service of $ controllerProvider.register*/
app.registerCtrl = $controllerProvider.register;
//jquery to dynamically include controllers as needed
function controllers(controllers){
var numLoaded = 0;
for (i = 0; i < controllers.length; i++) {
$.ajaxSetup({async:false});
$.getScript('js/controllers/' + controllers[i] + '.js').success(function(){
numLoaded++;
if (numLoaded == controllers.length) {
return true; //only return after all scripts are loaded, this is blocking, and will fail if all scripts aren't loaded.
}
});
}
}
$routeProvider
.when('/', {
templateUrl: 'views/foo.html',
resolve: {
load: function () {
controllers(['foo'])
}
}
})
.when('/bar',{
templateUrl: 'views/bar.html',
controller: 'BarCtrl',
resolve: {
load: function () {
controllers(['bar','foo']) //you can load multiple controller files
}
}
})
.otherwise({
redirectTo: document.location.pathname
});
}]);
/views/foo.html
<section ng-controller='FooCtrl'>
{{text}}
</section>
/views/bar.html
<section ng-controller='BarCtrl'>
{{text2}}
</section>
<section ng-controller='FooCtrl'>
{{text}}
</section>
/controllers/bar.js
app.registerCtrl('BarCtrl',function($scope){
$scope.text2 = 'Test';
});
Лучший способ сделать то, что вы просите, - это вместо этого использовать директиву и связать контроллер и шаблон таким образом, чтобы они связывались в соответствующее время. В настоящее время связывание этого не происходит в lazy.htm
в нужное время, если только вы не объявите глобальную функцию, как показано во втором примере.
Попробуйте этот плагин ARI для Angular JS. Это помогает вам лениво загружать скрипты контроллера по требованию.
В идеале - Angular заставит вас разделять HTML и JS, так как в более новых версиях это может применяться чаще.
Возможно, вам придется использовать requireJS http://solutionoptimist.com/2013/09/30/requirejs-angularjs-dependency-injection/
Ради хитрости вы можете попробовать
ng-controller-controller="'lazy'"
или же
В HTML
нг-контроллер-контроллер ="myObject.controller"
Где-то залить
$scope.myObject.controller = $controller('lazy', {$scope: $scope})
Вы также можете использовать директивы для загрузки вашего контроллера!
Пример здесь:
Я отправляю вам пример кода. Это работает нормально для меня. Поэтому, пожалуйста, проверьте это:
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
});
}])
И на странице HTML:
<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>
Спасибо