javascript - que - ng-hide
Cargando un controlador AngularJS dinĂ¡micamente (8)
¿Por qué no usar config y ui-router?
se carga en tiempo de ejecución y no tiene que mostrar sus controladores en código html
por ejemplo, algo así como el siguiente
var config = {
config: function(){
mainApp.config(function ($stateProvider, $urlRouterProvider){
$urlRouterProvider.otherwise("/");
$stateProvider
.state(''index'',{
views:{
''main'':{
controller: ''PublicController'',
templateUrl: ''templates/public-index.html''
}
}
})
.state(''public'',{
url: ''/'',
parent: ''index'',
views: {
''logo'' : {templateUrl:''modules/header/views/logo.html''},
''title'':{
controller: ''HeaderController'',
templateUrl: ''modules/header/views/title.html''
},
''topmenu'': {
controller: ''TopMenuController'',
templateUrl: ''modules/header/views/topmenu.html''
},
''apartments'': {
controller: ''FreeAptController'',
templateUrl:''modules/free_apt/views/apartments.html''
},
''appointments'': {
controller: ''AppointmentsController'',
templateUrl:''modules/appointments/views/frm_appointments.html''
},
}
})
.state(''inside'',{
views:{
''main'':{
controller: ''InsideController'',
templateUrl: ''templates/inside-index.html''
},
},
resolve: {
factory:checkRouting
}
})
.state(''logged'', {
url:''/inside'',
parent: ''inside'',
views:{
''logo'': {templateUrl: ''modules/inside/views/logo.html''},
''title'':{templateUrl:''modules/inside/views/title.html''},
''topmenu'': {
// controller: ''InsideTopMenuController'',
templateUrl: ''modules/inside/views/topmenu.html''
},
''messages'': {
controller: ''MessagesController'',
templateUrl: ''modules/inside/modules/messages/views/initial-view-messages.html''
},
''requests'': {
//controller: ''RequestsController'',
//templateUrl: ''modules/inside/modules/requests/views/initial-view-requests.html''
},
}
})
});
},
};
Tengo una página existente en la que necesito colocar una aplicación angular con controladores que se pueden cargar dinámicamente.
Aquí hay un fragmento que implementa mi mejor estimación sobre cómo se debe hacer en función de la API y algunas preguntas relacionadas que he encontrado:
// Make module Foo
angular.module(''Foo'', []);
// Bootstrap Foo
var injector = angular.bootstrap($(''body''), [''Foo'']);
// Make controller Ctrl in module Foo
angular.module(''Foo'').controller(''Ctrl'', function() { });
// Load an element that uses controller Ctrl
var ctrl = $(''<div ng-controller="Ctrl">'').appendTo(''body'');
// compile the new element
injector.invoke(function($compile, $rootScope) {
// the linker here throws the exception
$compile(ctrl)($rootScope);
});
JSFiddle . Tenga en cuenta que esto es una simplificación de la cadena de eventos real, hay varias llamadas asíncronas y entradas de usuario entre las líneas de arriba.
Cuando trato de ejecutar el código anterior, arroja el enlazador devuelto por $ compile: Argument ''Ctrl'' is not a function, got undefined
. Si entendí el bootstrap correctamente, el inyector que devuelve debería saber sobre el módulo Foo
, ¿verdad?
Si en cambio hago un nuevo inyector usando angular.injector([''ng'', ''Foo''])
, parece funcionar pero crea un nuevo $rootScope
que ya no es el mismo alcance que el elemento donde el módulo Foo
fue bootstrap .
¿Estoy usando la funcionalidad correcta para hacer esto o hay algo que me he perdido? Sé que esto no lo está haciendo de forma angular, pero necesito agregar nuevos componentes que usan Angular a páginas antiguas que no lo tienen, y no conozco todos los componentes que podrían ser necesarios cuando arranque el módulo.
ACTUALIZAR:
He actualizado el fiddle para mostrar que necesito poder agregar varios controladores a la página en puntos indeterminados en el tiempo.
Acabo de mejorar la función escrita por Jussi-Kosunen para que todo se pueda hacer con una sola llamada.
function registerController(moduleName, controllerName, template, container) {
// Load html file with content that uses Ctrl controller
$(template).appendTo(container);
// 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) {
controllerProvider.register(controllerName, call[2][1]);
}
}
angular.injector([''ng'', ''Foo'']).invoke(function($compile, $rootScope) {
$compile($(''#ctrl''+controllerName))($rootScope);
$rootScope.$apply();
});
}
De esta forma, podría cargar su plantilla desde cualquier lugar e instanciar controladores programáticamente, incluso anidados.
Aquí hay un ejemplo de trabajo cargando un controlador dentro de otro: http://plnkr.co/edit/x3G38bi7iqtXKSDE09pN
Esto es lo que hice, realmente 2 partes, usando ng-controller con su función definida de alcance y luego $ servicio de controlador para crear el controlador dinámico: -
En primer lugar, el HTML: necesitamos un controlador estático que instanciará un controlador dinámico.
<div ng-controller=''staticCtrl''>
<div ng-controller=''dynamicCtrl''>
{{ dynamicStuff }}
</div>
</div>
El controlador estático ''staticCtrl'' define un miembro de ámbito llamado ''dynamicCtrl'' que se llama para crear el controlador dinámico. ng-controller tomará un controlador predefinido por su nombre o revisará el alcance actual para la función del mismo nombre.
.controller(''staticCtrl'', [''$scope'', ''$controller'', function($scope, $controller) {
$scope.dynamicCtrl = function() {
var fn = eval(''(function ($scope, $rootScope) { alert("I am dynamic, my $scope.$id = " + $scope.$id + ", $rootScope.$id = " + $rootScope.$id); })'');
return $controller(fn, { $scope: $scope.$new() }).constructor;
}
}])
Usamos eval () para tomar una cadena (nuestro código dinámico que puede venir de cualquier parte) y luego el servicio $ controller que tomará un nombre de controlador predefinido (caso normal) o un constructor de función seguido de parámetros de constructor (pasamos en un nuevo alcance) - Angular inyectará (como cualquier controlador) en la función, estamos solicitando solo $ scope y $ rootScope arriba.
He encontrado una posible solución donde no necesito saber sobre el controlador antes de la inicialización:
// Make module Foo and store $controllerProvider in a global
var controllerProvider = null;
angular.module(''Foo'', [], function($controllerProvider) {
controllerProvider = $controllerProvider;
});
// Bootstrap Foo
angular.bootstrap($(''body''), [''Foo'']);
// .. time passes ..
// Load javascript file with Ctrl controller
angular.module(''Foo'').controller(''Ctrl'', function($scope, $rootScope) {
$scope.msg = "It works! rootScope is " + $rootScope.$id +
", should be " + $(''body'').scope().$id;
});
// Load html file with content that uses Ctrl controller
$(''<div id="ctrl" ng-controller="Ctrl" ng-bind="msg">'').appendTo(''body'');
// Register Ctrl controller manually
// If you can reference the controller function directly, just run:
// $controllerProvider.register(controllerName, controllerFunction);
// Note: I haven''t found a way to get $controllerProvider at this stage
// 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) {
controllerProvider.register(controllerName, call[2][1]);
}
}
}
registerController("Foo", "Ctrl");
// compile the new element
$(''body'').injector().invoke(function($compile, $rootScope) {
$compile($(''#ctrl''))($rootScope);
$rootScope.$apply();
});
Fiddle . El único problema es que necesita almacenar $controllerProvider
y usarlo en un lugar donde realmente no debería usarse (después del arranque). Además, no parece haber una manera fácil de acceder a una función utilizada para definir un controlador hasta que esté registrado, por lo que es necesario recorrer el _invokeQueue
del módulo, que no está documentado.
ACTUALIZACIÓN: Para registrar directivas y servicios, en lugar de $controllerProvider.register
simplemente use $compileProvider.directive
y $provide.factory
respectivamente. De nuevo, deberá guardar referencias a estos en su configuración de módulo inicial.
UDPATE 2: Aquí hay un violín que registra automáticamente todos los controladores / directivas / servicios cargados sin tener que especificarlos individualmente.
Sugeriría echar un vistazo a la biblioteca ocLazyLoad , que registra los módulos (o controladores, servicios, etc. en el módulo existente) en tiempo de ejecución y también los carga usando requireJs u otra biblioteca de ese tipo.
También necesitaba agregar varias vistas y vincularlas a los controladores en tiempo de ejecución desde una función javascript fuera del contexto de angularJs, así que esto es lo que se me ocurrió:
<div id="mController" ng-controller="mainController">
</div>
<div id="ee">
2nd controller''s view should be rendred here
</div>
ahora llamando a la función setCnt () inyectará y compilará el html, y estará vinculado al segundo controlador:
var app = angular.module(''app'', []);
function setCnt() {
// Injecting the view''s html
var e1 = angular.element(document.getElementById("ee"));
e1.html(''<div ng-controller="ctl2">my name: {{name}}</div>'');
// Compile controller 2 html
var mController = angular.element(document.getElementById("mController"));
mController.scope().activateView(e1);
}
app.controller("mainController", function($scope, $compile) {
$scope.name = "this is name 1";
$scope.activateView = function(ele) {
$compile(ele.contents())($scope);
$scope.$apply();
};
});
app.controller("ctl2", function($scope) {
$scope.name = "this is name 2";
});
aquí hay un ejemplo para probar esto: http://refork.com/x4bc
espero que esto ayude.
bootstrap () llamará al compilador AngularJS por ti, al igual que ng-app.
// Make module Foo
angular.module(''Foo'', []);
// Make controller Ctrl in module Foo
angular.module(''Foo'').controller(''Ctrl'', function($scope) {
$scope.name = ''DeathCarrot'' });
// Load an element that uses controller Ctrl
$(''<div ng-controller="Ctrl">{{name}}</div>'').appendTo(''body'');
// Bootstrap with Foo
angular.bootstrap($(''body''), [''Foo'']);
Fiddle .
''use strict'';
var mainApp = angular.module(''mainApp'', [
''ui.router'',
''ui.bootstrap'',
''ui.grid'',
''ui.grid.edit'',
''ngAnimate'',
''headerModule'',
''galleryModule'',
''appointmentsModule'',
]);
(function(){
var App = {
setControllers: mainApp.controller(controllers),
config: config.config(),
factories: {
authFactory: factories.auth(),
signupFactory: factories.signup(),
someRequestFactory: factories.saveSomeRequest(),
},
controllers: {
LoginController: controllers.userLogin(),
SignupController: controllers.signup(),
WhateverController: controllers.doWhatever(),
},
directives: {
signup: directives.signup(), // add new user
openLogin: directives.openLogin(), // opens login window
closeModal: directives.modalClose(), // close modal window
ngFileSelect: directives.fileSelect(),
ngFileDropAvailable: directives.fileDropAvailable(),
ngFileDrop: directives.fileDrop()
},
services: {
$upload: services.uploadFiles(),
}
};
})();
El código anterior es solo un ejemplo.
De esta forma, no necesita poner ng-controller="someController"
ninguna parte de la página; solo declara <body ng-app="mainApp">
La misma estructura se puede usar para cada módulo o módulos dentro de los módulos