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 create an AngularJS slideshow app where users can add content within a projects/ directory.
The router already uses variable routes.
.when('/:project', {
templateUrl: function(params) {
return 'projects/' + params.project + '/project.html';
}
})
However, I can't figure out how to use variable Controller names. For example, the following does not work:
.when('/:project', {
templateUrl: function(params) {
return 'projects/' + params.project + '/project.html';
},
controller: function() { return 'SomeController'; }
})
But this does:
.when('/:project', {
templateUrl: function(params) {
return 'projects/' + params.project + '/project.html';
},
controller: 'SomeController'
})
Is it possible to associate a controller with a route using a function?
If this sounds like a silly idea, let me explain what I'm trying to do. Instead of having user's edit the MainController, I'd like to be able to write JavaScript specific to a project. If a project gets deleted, that project's specific JavaScript is also deleted. If a new project gets added that needs custom JavaScript, users can use an API (AngularJS Events) to interact with the UI.
What's the best way to do something like this?
UI Router has controllerProvider feature. ngRoute doesn't have it.
The function specified in route controller is actual controller constructor, it can't just return controller name. It is ngView directive that defines how route controllers are instantiated. Without patching the directive the behaviour can be simulated with:
controller: function ($scope, $controller) {
return $controller('SomeController', { $scope: $scope });
}
I want to show a laravel blade view file in angular JS directive by
var commentsApp = angular.module('CommentApp',[]);
commentsApp.directive('commentForm',function(){
return {
restrict: 'EA',
replace: 'true'
templateURL: 'views/comments/comment-form.blade.php'
}
});
I want to use it by angular directive instead of
#include('comments.comment-form')
Where is my problem? How to solve this.
Thank you.
First you must define a route in laravel
Route::get('comment-form', function() {
return view('comments.comment-form');
});
Then you can use it in AngularJS
var commentsApp = angular.module('CommentApp', []);
commentsApp.directive('commentForm', function() {
return {
restrict: 'EA',
replace: 'true',
templateURL: 'comment-form'
}
});
Answer above is a good idea, however i dont like the idea of asking for a template by routing, We would create a route for each component :c . I leave my solution here:
In gulpfile.js inside elixir function add this line:
var elixir = require('laravel-elixir');
elixir(function(mix) {
mix.copy('resources/assets/js/angular/components/**/*.template.html', public/angular-templates');
//Find all files with suffix .template.html
});
As you notice it, i created a folder called 'angular' and then another one called 'components', there we will have our components
Angular-----------------------------
--Components------------------------
----my-component.directive.js-------
----my-component.template.html------
We have to create a global angular variable taking our browser window origin (www.myapp.com, localhost:8000, etc) by doing:
angular.module('myModule',[])
.value('pathAssets', location.origin + '/angular-templates/')
In our templateUrl we will call the template by writting:
templateUrl: pathAssets + '../angular-templates/my-template.html',
I have to say we have to concat our angular files in a file, otherwise it won't work D: if you don't know how to do it, add these lines in your gulpfile.js
mix.scripts([
'../../../bower_components/angular/angular.min.js',
'angular/app.js',
'angular/controllers/**/*.controller.js',
'angular/components/**/*.directive.js',
'angular/bootstrap.js',
], 'public/js/app.js'); //We concatenate angular files saving them in app.js
Finally execute the command 'gulp' in terminal(In our project), it should generate a new folder in public called angular-templates.
That's it :)
(function(angular) {
'use strict';
angular.module('app').component('bringTeamToEvent', {
templateUrl: '/assets/ng/app/team/bringTeamToEvent.html',
bindings: {
hero: '='
}
});
})(window.angular);
Just work from the public directory, no need to compile assets and move if you dont need to.
Then add the # symbol to tell blade to ignore and let angular do its work within the template
<span>Name: #{{ $ctrl.hero.name}}</span>
This controller works just fine:
function airlineRouter($routeProvider) {
$routeProvider
.when('/',
{
templateUrl:"partials/destinations.html",
controller: function($scope) {
$scope.setActive('destinations');
} //end controller
});
} //end airlineRouter
When I make the controller it's own JS file, it doesn't work anymore. Like this:
function airlineRouter($routeProvider) {
$routeProvider
when('/',
{
templateUrl:"partials/destinations.html",
controller: "DestinationsCtrl"
});
} //end airlineRouter
My Controller file resides in 'root/js/controllers/destinations.js', which is the same folder my 'app.js' file resides. The complete 'app.js' file looks like this:
angular
.module('airline', ['ngRoute'])
.config(airlineRouter);
function airlineRouter($routeProvider) {
$routeProvider
.when('/',
{
templateUrl:"partials/destinations.html",
controller: "DestinationsCtrl"
}
);
} //end airlineRouter
My complete controller JS file looks like this:
function DestinationsCtrl($scope) {
$scope.setActive('destinations');
} //end DestinationsCtrl
Why won't my controller load by the function name as this tut I'm doing says? The tut's file seems to work. Mine isn't.
Edit: console log shows this: Error: [ng:areq] Argument 'DestinationsCtrl' is not a function, got undefined
Edit 2: The tut I was working on was called 'Nesting Scopes', if that gives anyone a better idea of what I'm trying to do.
Ok, like I said, I'm new to AngularJS, and I'm kind of an idiot. I wasn't putting my external JS script links in my <head> on my index.html file. That's it. I can't believe I overlooked this.
Try to change your controller declaration:
angular.module('airline')
.controller('DestinationsCtrl',function($scope){
$scope.setActive('destinations');
});
Then this controller will work as expected.
Hope this is helpful for you.
I'm in the beginning stages of building a large app with AngularJS and RequireJS. Everything loads find but directives aren't manipulating the DOM as they should. No errors are being reported and rest of the app works fine: Views are loaded and $scope is bindable. Examining the console shows that all the files loaded. I'm assuming this is a lazy load issue in that my directive is simply not loading at the correct time. I'd appreciate any insight into how to properly load directives in this regard. Unless it's a part of Angular's jqLite, please refrain from suggesting jQuery.
config.js
require.config({
paths: { angular: '../vendor/angular' }
shim: { angular: { exports: 'angular' } }
});
require(['angular'], function(angular) {
angular.bootstrap(document, ['myApp']);
});
myApp.js
define(['angular', 'angular-resource'], function (angular) {
return angular.module('myApp', ['ngResource']);
});
routing.js
define(['myApp', 'controllers/mainCtrl'], function (myApp) {
return myApp.config(['$routeProvider', function($routeProvider) {
...
}]);
});
mainCtrl.js
define(['myApp', 'directives/myDirective'], function (myApp) {
return myApp.controller('mainCtrl', ['$scope', function ($scope) {
...
}]);
});
myDirective.js
require(['myApp'], function (myApp) {
myApp.directive('superman', [function() {
return {
restrict: 'C',
template: '<div>Here I am to save the day</div>'
}
}])
});
home.html
<div class="superman">This should be replaced</div>
home.html is a partial that's loaded into ng-view
Angular cannot load directives after it has been bootstrapped. My suggestion is:
Make myDirective.js do a define(), not a require()
Make sure myDirective.js is run before the require(['angular'],...) statement in config.js, e.g. do require(['angular','myDirective'],...). For this to work, myDirective should be shimmed to depend on angular - thanks # David Grinberg.
As a sidenote, take a look at this in Stackoverflow/this in GitHub, we have been trying to do RequireJS + Angular play together.