I'm building an application using Angular-UI-Router. I have a top-level Angular module that depends on sub-modules and those sub-modules have controller with the same name as follows:
var moduleA = angular.module('moduleA', []);
moduleA.controller('SameNameCtrl', function () {
// ModuleA SameNameCtrl implementation
});
var moduleB = angular.module('moduleB', []);
moduleB.controller('SameNameCtrl', function () {
// ModuleB SameNameCtrl implementation
});
var app = angular.module('app', ['ui.route', 'moduleA', 'moduleB']);
How do I specify controller in different modules when constructing state with Angular-UI-Router?
$stateProvider
.state('app.stateA', {
url: '/stateA',
templateUrl: 'template-A.html',
controller: ???? // how to specify moduleA SameNameCtrl
})
.state('app.stateB', {
url: '/stateB',
templateUrl: 'template-B.html',
controller: ???? // how to specify moduleB SameNameCtrl
})
Sure, I can assign different controller name for controllers in different module, but I'd like to know if it's possible with controllers having the same name.
Thanks!
Unfortunatelly modules are not namespace mechanism in angularjs. You should come up / follow a naming convention to differentiate the controllers
Related
I have an existing page into which I need to drop an angular app with controllers that can be loaded dynamically.
Here's a snippet which implements my best guess as to how it should be done based on the API and some related questions I've found:
// 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. Note that this is a simplification of the actual chain of events, there are various async calls and user inputs between the lines above.
When I try to run the above code, the linker which is returned by $compile throws: Argument 'Ctrl' is not a function, got undefined. If I understood bootstrap correctly, the injector it returns should know about the Foo module, right?
If instead I make a new injector using angular.injector(['ng', 'Foo']), it seems to work but it creates a new $rootScope which is no longer the same scope as the element where the Foo module was bootstrapped.
Am I using the right functionality to do this or is there something I've missed? I know this isn't doing it the Angular way, but I need to add new components that use Angular to old pages that don't, and I don't know all the components that might be needed when I bootstrap the module.
UPDATE:
I've updated the fiddle to show that I need to be able to add multiple controllers to the page at undetermined points in time.
I've found a possible solution where I don't need to know about the controller before bootstrapping:
// 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. Only problem is that you need to store the $controllerProvider and use it in a place where it really shouldn't be used (after the bootstrap). Also there doesn't seem to be an easy way to get at a function used to define a controller until it is registered, so I need to loop through the module's _invokeQueue, which is undocumented.
UPDATE: To register directives and services, instead of $controllerProvider.register simply use $compileProvider.directive and $provide.factory respectively. Again, you'll need to save references to these in your initial module config.
UDPATE 2: Here's a fiddle which automatically registers all controllers/directives/services loaded without having to specify them individually.
bootstrap() will call the AngularJS compiler for you, just like 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.
I would suggest to take a look at ocLazyLoad library, which registers modules (or controllers, services etc on existing module) at run time and also loads them using requireJs or other such library.
I also needed to add multiple views and bind them to controllers at runtime from a javascript function outside the angularJs context, so here's what I came up with :
<div id="mController" ng-controller="mainController">
</div>
<div id="ee">
2nd controller's view should be rendred here
</div>
now calling setCnt() function will inject and compile the html, and it will be linked to the 2nd controller:
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";
});
here's an example to test this : https://refork.codicode.com/x4bc
hope this helps.
I have just improved the function written by Jussi-Kosunen so that all stuff can be done with one single call.
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();
});
}
This way you could load your template from anywhere and instanciate controllers programmatically, even nested.
Here is a working example loading a controller inside another one:
http://plnkr.co/edit/x3G38bi7iqtXKSDE09pN
why not use config and ui-router?
it is loaded at runtime and you have no need to show your controllers in html code
for example something like the following
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'
},
}
})
});
},
};
This is what I did, 2 parts really, using ng-controller with its scope defined function and then $controller service to create the dynamic controller :-
First, the HTML - we need a Static Controller which will instantiate a dynamic controller ..
<div ng-controller='staticCtrl'>
<div ng-controller='dynamicCtrl'>
{{ dynamicStuff }}
</div>
</div>
The static controller 'staticCtrl' defines a scope member called 'dynamicCtrl' which is called to create the dynamic controller. ng-controller will take either a predefined controller by name or looks at current scope for function of same name ..
.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;
}
}])
We use eval() to take a string (our dynamic code which can come from anywhere) and then the $controller service which will take either a predefined controller name (normal case) or a function constructor followed by constructor parameters (we pass in a new scope) - Angular will inject (like any controller) into the function, we are requesting just $scope and $rootScope above.
'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(),
}
};
})();
The above code is only an example.
This way you don't need to put ng-controller="someController" anywhere on a page — you only declare <body ng-app="mainApp">
Same structure can be used for each module or modules inside modules
I'm trying to modularize my first angular project. Need to make my module views relative to this module. In example below i have sample main module, that require controllers module. I can inject VIEWS_PATH to controller, but no to config(). As i know constant can be injected into config(). What is wrong with that ?
mainModule.js
angular.module('mainModule', ['app.main.controllers'])
.constant('VIEWS_PATH', 'js/modules/main/views');
controllersMain.js
angular.module('app.main.controllers', [])
.config(function($routeProvider, VIEWS_PATH) { // error
$routeProvider.when('/hello', {
templateUrl: VIEWS_PATH+'/hello.html',
controller: 'HelloController'
})
})
.controller('HelloController', function($scope, VIEWS_PATH) {
$scope.hello = 'Hello World!';
console.log('VIEWS_PATH: '+VIEWS_PATH); // ok
});
It's because you defined your VIEWS_PATH constant in one module -mainModule and you're trying to use it in a different module - app.main.controllers.
You can define that constant in app.main.controllers module if you wish to use it in the configuration of that module.
angular.module('app.main.controllers', [])
.constant('VIEWS_PATH', 'js/modules/main/views')
.config(function($routeProvider, VIEWS_PATH) {
$routeProvider.when('/hello', {
templateUrl: VIEWS_PATH+'/hello.html',
controller: 'HelloController'
});
});
But Constants from app.main.controllers module will work in mainModule as it listed as dependency in mainModule like below.
angular.module('mainModule', ['app.main.controllers']);
For example, let's say we defined two modules - MyApp & SomeModule
var someModule = angular.module('SomeModule',['someOtherModule']);
someModule.constant('SOME_CONSTANT','SomeValue');
var myApp = angular.module('MyApp',['SomeModule']);
myApp.constant('TEST_CONSTANT','Test');
myApp.config(function(SOME_CONSTANT){
console.log("from dependent module "+SOME_CONSTANT);
});
With the above setup, SOME_CONSTANT from SomeModule can be used in MyApp but TEST_CONSTANT from MyApp cannot be used in SomeModule.
Here's a sample Pen in action.
I have an Angular-Meteor application working. I would like to package Angular templates and associated controller into a Meteor package, and inject these templates into my main application by adding that package.
What is best approach?
Update 2015-08-26 - I figured out how to add a template, documented below. But how to have a Meteor package inject the template's Angular controller into the base application?
A key tie-in is Angular UI-router.
I have a base application that includes my package named packageprefix:packagename. Inside this package I have my code in the root of the package folder:
myPackagedPage.ng.html - the Angular HTML template
myPackagedPage.js - the associated Angular controller
From my main application, I tried creating a route to my Angular template like so:
angular.module('parentModule',[
'angular-meteor',
'ui.router',
'angularify.semantic.sidebar'
])
.config(['$urlRouterProvider', '$stateProvider', '$locationProvider',
function($urlRouterProvider, $stateProvider, $locationProvider){
console.log("app.js config!");
$locationProvider.html5Mode(true);
$stateProvider
.state('home', {
url: '/',
templateUrl: 'client/views/home/home.ng.html',
controller: 'HomeCtrl'
})
.state('myPackagedPage', {
url: '/myPackagedPage',
templateUrl: 'packageprefix_packagename/myPackagedPage.ng.html',
controller: 'MyPackagedPageCtrl'
})
;
$urlRouterProvider.otherwise('/');
}])
The application successfully finds the myPackagedPage.ng.html file and renders it. But how to add the controller?
I tried adding this in my package but the controller functions does not get called.
console.log("myPackagedPage.js loaded");
angular.module('parentModule')
.controller('MyPackagedPageCtrl', ['$scope',
function($scope){
console.log("MyPackagedPageCtrl");
}])
;
I get an error:
Argument 'MyPackagedPageCtrl' is not a function, got undefined
I have this working now. Here is the approach that works for me, to inject an Angular Controller + template in a Meteor package, into the containing application.
In my package.js, I have this
Package.onUse(function(api) {
api.versionsFrom('1.1.0.3');
api.use('angular:angular#1.4.4', 'client');
api.use("urigo:angular#0.9.3", 'client');
api.use("session#1.1.0", 'client');
//api.use('angularui:angular-ui-router#0.2.15', 'client');
api.addFiles('interests.js', 'client');
api.addFiles('interests.ng.html', 'client');
api.export("InterestsCtrl", "client")
});
Note you must export your controller, so that the parent application may access it.
In my package, called ramshackle:bigd-interests, I have these files at the root level: package.js, interests.ng.html, and interests.js. interests.js injects the Angular controller, the Anguilar UI-router route to the template, and a sidebar link into the parent application. It accomplishes this by using the Meteor Session. I played with other means of doing this but Session was the only thing that worked. Just be careful to properly scope your session variable names.
//add controllers
var controllers = Session.get("BIGD.controllers");
if (!controllers) controllers = {};
var interestsCtrlSpec = "'$scope', InterestsCtrl";
InterestsCtrl = function($scope){
console.log("InterestsCtrl running");
};
controllers.InterestsCtrl = interestsCtrlSpec;
Session.set("BIGD.controllers", controllers);
//add routes
var routes = Session.get("BIGD.routes");
if (!routes) routes = {};
routes.interests = {
url: '/interests',
templateUrl: 'ramshackle_bigd-interests_interests.ng.html',
controller: 'InterestsCtrl'
};
Session.set("BIGD.routes", routes);
//add sidebar links
//the key determines sorting order
var sidebar = Session.get("BIGD.sidebar");
if (!sidebar) sidebar = {};
sidebar["interests"] = {
url: '/interests',
templateUrl: 'ramshackle_bigd-interests_interests.ng.html',
controller: 'InterestsCtrl',
rank: 5
};
Session.set("BIGD.sidebar", sidebar);
var interestsItem = {label: 'Interests', link: '/interests', icon: "rocket"};
In my parent application's app.js , I dynamically loaded the controllers and routes from the session like this:
angular.module('bigdam',[
'angular-meteor',
'ui.router',
'angularify.semantic.sidebar',
'nvd3',
'leaflet-directive',
'ui.router.history'
])
.config(['$urlRouterProvider', '$stateProvider', '$locationProvider',
function($urlRouterProvider, $stateProvider, $locationProvider){
//console.log("app.js config!");
$locationProvider.html5Mode(true);
//add a static state
$stateProvider
.state('home', {
url: '/',
templateUrl: 'client/views/home/home.ng.html',
controller: 'HomeCtrl'
});
//add the dynamic routes/states from other Meteor packages
for (var stateId in routes) {
var route = routes[stateId];
$stateProvider
.state(stateId, route);
}
$urlRouterProvider.otherwise('/');
}])
;
//Declare the controllers from plugins
for (var controllerId in controllers) {
var controllerSpec = controllers[controllerId];
var controllerSpecArray = eval("[" + controllerSpec + "]")
angular.module('bigdam').controller(controllerId, controllerSpecArray);
}
So now, when I create a new Meteor package, and follow the convention described above, its controllers, routes, and sidebar links get loaded into the main application.
I'm looking for a way to "send" or "register" some configuration from several modules to one single module.
The idea is that different modules will have theirs own route configuration. And one single module controller will build the menu based on those configurations.
I want to "send" or "register" configuration instead of querying, because the menu controller cannot knows which modules are available.
Here is a solution to my need:
Each module defines its own routes using $routeProvider with a custom parameter defining if the route should be displayed in the menu and another custom parameter with the name to display. (See above Module A example).
Then the menu controller will loop the $route.routes variable (which contains all defined routes) to build de menu. (See above Module B example).
Module A:
angular
.module('module-a', ['ngRoute'])
.config(function ($routeProvider) {
$routeProvider
.when('/path-a', {
controller: 'ModuleAController',
templateUrl: 'path/to/template.html',
menuDisplay: true, // Custom parameter
displayName: 'Solution Manager' // Custom parameter
});
});
Module B:
angular
.module('module-b', [])
.controller('MenuController', ['$scope', '$route', function ($scope, $route) {
var entries = [];
angular.forEach($route.routes, function (route, path) {
if ( route.menuDisplay == true ) {
this.push({
displayName: route.displayName,
route: '#' + path
});
}
}, entries);
$scope.menu = entries;
}]);
You will probably add some more checks to ensure displayName is provided, etc ...
I came across this tutorial.
http://justinvoelkel.me/laravel-angularjs-part-two-login-and-authentication/
The author used dependency injection to inject the login controller in app.js like this.
app.js:
var app = angular.module('blogApp',[
'ngRoute',
//Login
'LoginCtrl'
]);
app.run(function(){
});
//This will handle all of our routing
app.config(function($routeProvider, $locationProvider){
$routeProvider.when('/',{
templateUrl:'js/templates/login.html',
controller:'LoginController'
});
});
The login controller file looks like this.
LoginController.js:
var login = angular.module('LoginCtrl',[]);
login.controller('LoginController',function($scope){
$scope.loginSubmit = function(){
console.dir($scope.loginData);
}
});
I don't understand why the dependency injection is required here.
Here is my version of app.js and LoginController.js which works perfectly fine.
app.js:
var app = angular.module('ilapp', ['ngRoute']);
app.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
$routeProvider.when('/login', {
controller: 'LoginController'
});
}]);
LoginController.js:
angular.module('ilapp').controller('LoginController', [function () {
this.loginSubmit = function () {
console.dir(this.loginData);
};
}]);
Is there any advantage to the author's approach? What am I missing?
First of all, both are correct way and it should work but you can choose any one method depends upon your project.
Approach 1
In your question, the first approach is modular way. Means, you can register a LoginController controller in a new module LoginCtrl. Here module name LoginCtrl is confusing. you can change the name as loginModule. This approach is helpful for you to organize the files structure for your big application. Also, look this post Angular - Best practice to structure modules
var login = angular.module('loginModule',[]);
login.controller('LoginController',function($scope){
$scope.loginSubmit = function(){
console.dir($scope.loginData);
}
});
var app = angular.module('blogApp',[
'ngRoute',
'loginModule'
]);
app.run(function(){
});
//This will handle all of our routing
app.config(function($routeProvider, $locationProvider){
$routeProvider.when('/',{
templateUrl:'js/templates/login.html',
controller:'LoginController'
});
});
Approach 2
If your application contains minimal pages and no need to split multiple modules, then you can write all your controllers in app.js itself