Instantiate custom angular controller - javascript

How to instantiate a custom controller from code and preserve scope inheritance. In other words I want to do something like this:
var controller = 'myCtrl';
var html = '<p>{{value}}</p>';
var validScope= $scope.$new({
value : 'Hello, custom controllers'
}); // Or something like this to get valid scopes inheritance
$(document.body).append(instantiate(controller, html, validScope));
So i need two answers: how to instantiate custom controller and how to do it like angular do.
UPD. I've tried do it this way:
$compile('<div ng-controller="myCtrl">'+html+'</div>')(validScope);
Controller was instantiated. But placeholded values was not binded.

I do not know the context of where you are trying to all this controller from but I am going to assume you are wither in another controller, a service, or a directive.
The code below will show how to create a controller from a service.
The example may cover more than what you would need to do but this is a pattern that will work.
Create an abstract controller, this sets the constructor parameters of the controller and insulates the rest of the dependencies.
module.factory('AbstractCtrl', ['dependencies...', function (dependencies...) {
var ctrl = function($scope) {
// Do controller setup.
};
return ctrl;
}]);
Now create a controller implementation based on the abstract
module.controller('CtrlImpl', ['$scope', 'AbstractCtrl', function ($scope, AbstractCtrl) {
// Initialize the parent controller and extend it.
var AbstractCtrlInstance = new AbstractCtrl($scope);
$.extend(this, AbstractCtrlInstance);
// … Additional extensions to create a mixin.
}]);
Now that you have a controller with a minimally defined constructor to create an instance of the controller you just need to call inject the $controller and do the following:
$controller('CtrlImpl', {$scope: $scope}));

I think that the best approach is to expose a function on the scope for retrieving your controller. (ngController can take a string or a function) Lets say you have different values which need different constructors... something vaguely like this:
<div ng-repeat="item in items">
<div ng-controller="controllerFor(item)">
// whatever
</div>
</div>
That controllerFor function will know how to do the mapping for you. Hopefully, you can avoid using $compile all together.

Related

Pass data to different controllers using Angularjs

I have the below code in one controller.
$scope.DataModel= [];
$scope.DataModelTexts= { buttonDefaultText: '' };
I will be using the same code in one more controller. Now instead of writing the same code in 2nd controller too, i want to know if there is a way to put this in a common code in some factory or service and use that in both the controllers. I have tried to read about factory and services and i am getting a bit confused of how to use any one of these in my scenario.
Any information would help me gain more knowledge about factory and services in angularjs. Thanks.
You're on the right track, use can use a factory or a service to share code between controllers. Note that in angular services(and factories) are singletons; they are instantiated once when the app starts and then anytime you inject it into a controller, you are referencing the same instance. Consider the following code:
var myApp = angular.module('myApp',[]);
myApp.service('MyService', function() {
let _someValue = 'Initial Value';
this.setValue = function(value){
_someValue = value;
}
this.getValue = function(){
return _someValue;
}
});
//First Controller Run
myApp.controller('ControllerA', function($scope, MyService) {
MyService.getValue(); //Initial Value
MyService.setValue("BRAND NEW VALUE!!!");
});
//Run after ControllerA
myApp.controller('ControllerB', function($scope, MyService) {
MyService.getValue(); //BRAND NEW VALUE!!!
});
Her you'll see that MyService holds the state of someValue. ControllerA get MyService injected to it and can use the methods of that service to set a new value. Now for any subsequent call for that same state, like for instance by ControllerB, the updated value will be returned.
You can use the .config() or a run() blocks (good SO on these here: AngularJS app.run() documentation?) to bind these reused variables to $rootScope, then call them from $rootScope.DataModel and $rootScope.DataModelTexts from within your controllers or services (as long as you inject $rootScope into these controllers and services).

How do I implement a global function that can be used across multiple ng-click functions in angular

I have a simple example function checkIfValueIsGreaterThan0
If I have an ng-click function I can use it like this:
<div ng-click="checkIfValueIsGreaterThan0()"></div>
But I then need this function declared in my $scope. Is there a way to add this to a global library that can be used across my application and be able to inject that library and have it work from my view without having to declare it explicitly like so in my controller?
$scope.checkIfValueIsGreaterThan0 = myLibrary.checkIfValueIsGreaterThan0
In your app bootstrap, in a run or config block, set it on $rootScope (which itself should be injected):
$rootScope.checkIfValueIsGreaterThan0 = myLibrary.checkIfValueIsGreaterThan0;
As $rootScope is the parent of all scopes within your application, you will be able to use checkIfValueIsGreaterThan0() in any template or $scope.checkIfValueIsGreaterThan0() in any controller..
Yes it is most certainly possble, u can make a factory service out of it and use it across ur app. You may need to pass $scope or $http service to ur factory but it depends on what are u trying to accomplish. In case if u do then u can do it like i did it in the controller below
var app = angular.module( 'myApp' ,[]);
app.factory(' checkIfValueIsGreaterThan0', function()
{
var checkIfValueIsGreaterThan0 = {};
checkIfValueIsGreaterThan0.test = function()
{
// Do ur stuff here and return it
}
return checkIfValueIsGreaterThan0;
});
When u want to use this factory simply inject it into ur controller and call it, and as u said u wana assign it to the $scope u can do that as following.
app.controller( 'myCtrl' , ['$scope', 'checkIfValueIsGreaterThan0', function($scope, checkIfValueIsGreaterThan0)
{
$scope.whatever = checkIfValueIsGreaterThan0.test();
}]);
I hope u'll get the idea, If there is any typo error i'm sorry for that cuz i'm tryping this from mobile fone.

Angular 1.4 - get parent controller property

I have the following HTML structure:
<div class="order-table-page" ng-controller="SummaryController as summaryCtrl">
<h3>Summary</h3>
<!-- Statutes summary information -->
...
<!--List of orders with selected statuses-->
<div ng-controller="OrderTableController as orderTableCtrl">
...
</div>
</div>
So, OrderTableController is a child of SummaryController. Now, in child controller I want to get access to the parent property. In parent class I define:
orderApp.controller('SummaryController', ['$location', 'ordersApi', function($location, ordersApi){
var ctrl = this;
ctrl.summary = [];
ctrl.test = 'test';
...
}]);
And in child controller I try to get "test" property:
orderApp.controller('OrderTableController', ['$location', '$scope', 'ordersApi', function($location, $scope, ordersApi){
var table = this;
table.orders = [];
console.log("Table orders 1");
console.log($scope.$parent.test);
...
}]);
I expect that $scope.$parent will contain SummaryController scope. But I'm not sure what it contains, because $scope.$parent.test is undefined and $scope.$parent has property named summaryCtrl.
My question is how to get parents property "test" form OrderTableController?
As your are using Controller As feature, it creates a property inside the $scope which will represent the controller itself.
So, in your SummaryController you have a test property. And in scope of this SummaryController it will be like $scope.summaryCtrl.test - because you defined it as SummaryController as summaryCtrl.
Therefore, you need to go in the same path from you child controller to get test property (it will be more elegant than working with $scope.$parent).
If you need to share some data between controllers, you can try to use shared services (as they are singletons) and use them in related controllers.
You simply have to add a refference in OrderTableController of SummaryController and you'll get everything from SummaryController in OrderTableController :)
Using $scope.$parent is not very elegant. Not neccesary wrong, but not elegant.
This may be because you are using this instead of $scope in the parent controller, if you do $scope.test='test' you could get it in the way you want $scope.$parent.test. See this fiddle: http://jsfiddle.net/f2zyvf17/
PD: You can see the difference of using $scope or this in this question:
'this' vs $scope in AngularJS controllers

Where do I put regular functions, Angularjs

I'm getting into Angularjs. I'm want to re-use a function, resetForm() function. My question is, do I still put that inside my controller $scope or create a factory or service?
app.controller('testController', [
'$scope',
'testService',
function($scope, testService) {
$scope.addTestForm = function() {
var body = document.getElementsByTagName('body')[0];
if (!body.classList.contains('test__add')) {
body.classList.add('test__add');
}
};
//do I add my function here?
function name() {};
}]);
if it is a resetForm() function then I assume it is dealing with DOM. I would suggest you to declare this function inside your controller since you will need access to $scope to reset form fields (direct DOM access is strictly prohibited in AngularJS). You can refer to below sample code
app.controller('testController', [
'$scope',
'testService',
function($scope, testService) {
var resetForm = function() {
// your logic to reset form with help of $scope
};
$scope.addTestForm = function() {
var body = document.getElementsByTagName('body')[0];
if (!body.classList.contains('test__add')) {
body.classList.add('test__add');
}
};
}]);
Note: You don't need to declare resetForm function as $scope.resetForm if you don't plan to call it from your template file.
If you want to re-use it across multiple controllers, a Factory or a Service is probably the best way to share it without duplication of code. You can then call on either one of these from all your controllers.
The added benefits to this pattern are that, not only do you save yourself from duplicating code, but you can also store variables and share those as well.
Both will work, but you can read some interesting discussion on Factory vs Service if you have trouble with which one to choose.
The things goes like this:
We will write functions in controllers if that function is normally manipulating model and is only relevant to that controller.
We write services normally for giving data to controllers such as from a asynchronous API call, and for sharing data in between controllers.
In your case, if you want a utility function you can use a service, but resetForm function is more like controller specific, because it's gonna clear some model values. In future you may want to add more conditions and operations in that function which may produce complex code, if you use a service for that.
If that function is a 'non-gonna change function' using a service is good way to go. (code re-usability and all), but otherwise, wrap all logic in one place is more good.
(write it in controller)

AngularJS: dynamically assign controller from ng-repeat

I'm trying to dynamically assign a controller for included template like so:
<section ng-repeat="panel in panels">
<div ng-include="'path/to/file.html'" ng-controller="{{panel}}"></div>
</section>
But Angular complains that {{panel}} is undefined.
I'm guessing that {{panel}} isn't defined yet (because I can echo out {{panel}} inside the template).
I've seen plenty of examples of people setting ng-controller equal to a variable like so: ng-controller="template.ctrlr". But, without creating a duplicate concurrant loop, I can't figure out how to have the value of {{panel}} available when ng-controller needs it.
P.S. I also tried setting ng-controller="{{panel}}" in my template (thinking it must have resolved by then), but no dice.
Your problem is that ng-controller should point to controller itself, not just string with controller's name.
So you might want to define $scope.sidepanels as array with pointers to controllers, something like this, maybe:
$scope.sidepanels = [Alerts, Subscriptions];
Here is the working example on js fiddle http://jsfiddle.net/ADukg/1559/
However, i find very weird all this situation when you might want to set up controllers in ngRepeat.
To dynamically set a controller in a template, it helps to have a reference to the constructor function associated to a controller. The constructor function for a controller is the function you pass in to the controller() method of Angular's module API.
Having this helps because if the string passed to the ngController directive is not the name of a registered controller, then ngController treats the string as an expression to be evaluated on the current scope. This scope expression needs to evaluate to a controller constructor.
For example, say Angular encounters the following in a template:
ng-controller="myController"
If no controller with the name myController is registered, then Angular will look at $scope.myController in the current containing controller. If this key exists in the scope and the corresponding value is a controller constructor, then the controller will be used.
This is mentioned in the ngController documentation in its description of the parameter value: "Name of a globally accessible constructor function or an expression that on the current scope evaluates to a constructor function." Code comments in the Angular source code spell this out in more detail here in src/ng/controller.js.
By default, Angular does not make it easy to access the constructor associated to a controller. This is because when you register a controller using the controller() method of Angular's module API, it hides the constructor you pass it in a private variable. You can see this here in the $ControllerProvider source code. (The controllers variable in this code is a variable private to $ControllerProvider.)
My solution to this issue is to create a generic helper service called registerController for registering controllers. This service exposes both the controller and the controller constructor when registering a controller. This allows the controller to be used both in the normal fashion and dynamically.
Here is code I wrote for a registerController service that does this:
var appServices = angular.module('app.services', []);
// Define a registerController service that creates a new controller
// in the usual way. In addition, the service registers the
// controller's constructor as a service. This allows the controller
// to be set dynamically within a template.
appServices.config(['$controllerProvider', '$injector', '$provide',
function ($controllerProvider, $injector, $provide) {
$provide.factory('registerController',
function registerControllerFactory() {
// Params:
// constructor: controller constructor function, optionally
// in the annotated array form.
return function registerController(name, constructor) {
// Register the controller constructor as a service.
$provide.factory(name + 'Factory', function () {
return constructor;
});
// Register the controller itself.
$controllerProvider.register(name, constructor);
};
});
}]);
Here is an example of using the service to register a controller:
appServices.run(['registerController',
function (registerController) {
registerController('testCtrl', ['$scope',
function testCtrl($scope) {
$scope.foo = 'bar';
}]);
}]);
The code above registers the controller under the name testCtrl, and it also exposes the controller's constructor as a service called testCtrlFactory.
Now you can use the controller in a template either in the usual fashion--
ng-controller="testCtrl"
or dynamically--
ng-controller="templateController"
For the latter to work, you must have the following in your current scope:
$scope.templateController = testCtrlFactory
I believe you're having this problem because you're defining your controllers like this (just like I'm used to do):
app.controller('ControllerX', function() {
// your controller implementation
});
If that's the case, you cannot simply use references to ControllerX because the controller implementation (or 'Class', if you want to call it that) is not on the global scope (instead it is stored on the application $controllerProvider).
I would suggest you to use templates instead of dynamically assign controller references (or even manually create them).
Controllers
var app = angular.module('app', []);
app.controller('Ctrl', function($scope, $controller) {
$scope.panels = [{template: 'panel1.html'}, {template: 'panel2.html'}];
});
app.controller("Panel1Ctrl", function($scope) {
$scope.id = 1;
});
app.controller("Panel2Ctrl", function($scope) {
$scope.id = 2;
});
Templates (mocks)
<!-- panel1.html -->
<script type="text/ng-template" id="panel1.html">
<div ng-controller="Panel1Ctrl">
Content of panel {{id}}
</div>
</script>
<!-- panel2.html -->
<script type="text/ng-template" id="panel2.html">
<div ng-controller="Panel2Ctrl">
Content of panel {{id}}
</div>
</script>
View
<div ng-controller="Ctrl">
<div ng-repeat="panel in panels">
<div ng-include src="panel.template"></div>
</div>
</div>
jsFiddle: http://jsfiddle.net/Xn4H8/
Another way is to not use ng-repeat, but a directive to compile them into existence.
HTML
<mysections></mysections>
Directive
angular.module('app.directives', [])
.directive('mysections', ['$compile', function(compile){
return {
restrict: 'E',
link: function(scope, element, attrs) {
for(var i=0; i<panels.length; i++) {
var template = '<section><div ng-include="path/to/file.html" ng-controller="'+panels[i]+'"></div></section>';
var cTemplate = compile(template)(scope);
element.append(cTemplate);
}
}
}
}]);
Ok I think the simplest solution here is to define the controller explicitly on the template of your file. Let's say u have an array:
$scope.widgets = [
{templateUrl: 'templates/widgets/aWidget.html'},
{templateUrl: 'templates/widgets/bWidget.html'},
];
Then on your html file:
<div ng-repeat="widget in widgets">
<div ng-include="widget.templateUrl"></div>
</div>
And the solution aWidget.html:
<div ng-controller="aWidgetCtrl">
aWidget
</div>
bWidget.html:
<div ng-controller="bWidgetCtrl">
bWidget
</div>
Simple as that! You just define the controller name in your template. Since you define the controllers as bmleite said:
app.controller('ControllerX', function() {
// your controller implementation
});
then this is the best workaround I could come up with. The only issue here is if u have like 50 controllers, u'll have to define them explicitly on each template, but I guess u had to do this anyway since you have an ng-repeat with controller set by hand.

Categories

Resources