In AngularJS, I have two lengthy controllers, which are identical - except for one variable definition at the top (which is used in multiple places throughout the controller to decide what to process).
How can I consolidate these two controllers into only one controller?
Is there something like a controller factory that can take in an argument (my variable)?
Can you pass a variable to a controller, when specifying it in the router? Can you pass a variable when manually calling a controller via ng-controller in the html?
Something like this:
ControllerA
{
mode = 'A';
MakeControllerRealAndUseItInsteadOfCurrentController(mode);
}
ControllerB
{
mode = 'B';
MakeControllerRealAndUseItInsteadOfCurrentController(mode);
}
ControllerReal( mode )
{
mode = mode;
// actual controller content
}
Controller in Angular are tightly bound to the view they interact with and in fact the view has declaration of ng-controller based on which controller is instantiated. Controllers are not utility functions that can be shared across the app\views. Since the controller are doing similar function, it maybe the case that the views are pretty similar.
Some of the ways that you can reuse the controller code would be
Refactor any code that does not directly affect the view into service and call the service from your controllers.
If the view are similar may be look at reusable directives, with their own controllers, for create a specific part of your view, which can be used across views.
For the point above you can also create partial templates which can have their own controller, and reuse them using ng-include.
Related
I am programing in Angularjs, and have have a page that is divided into 3 teplates which one has an html file and js file.
The js file of which template is characterized for having the controller of the html of that template.
At the end i want to pass a variable from a certain controller to another how can i do that once they are in different controllers. Thanks
Mr.Avraam Mavridis, here is some code:
Basically down here is the controller that i want to get the value of the parameter (clientPosition) to pass it to another controller:
crm.controller("clientsModule", ["$scope", "$http", function ($scope, $http {
$scope.changeTemplate = function (index, clientPosition) {
$scope.panelTemplate = index;
$scope.clientPosition = clientPosition;
};
}]);
There are various ways you can achieve this. The easiest way to do this is using a broadcast and watch. You basically set the value in the controller you want to and create a broadcast event for it. In the controller that you would like to access the value, you put a watch and listen to any updates for the value.
The above way is also the dirtiest way to achieve what you want
A more elegant solution would involve create a model binding on the view and accessing those properties via logical progression on the controller you want to access that value in. You essentially create an angular service for your models and then access those services in the controllers.
All of the controllers in my app share a dependency on a data provider type service I created. This service consists of functions to retrieve various bits of data, and almost all of these methods allow for an optional parameter. The ability to enter in this optional parameter is role based. The problem is my controllers are now full of code similar to:
// Initializing controller.
dataservice.getRole().then(function(role) { $scope.isAdmin = role.isAdmin; });
// After a button press or some other event.
if($scope.isAdmin) {
dataservice.getData($scope.param1, $scope.param2, $scope.optionalText);
} else {
dataservice.getData($scope.param1, $scope.param2);
}
It seems a code smell to me that I have to keep repeating this code throughout the controllers, but I can't think of a way to construct my controllers where I don't have to.
That's how I've realized that: Controller Inheritance.
Basically:
Create your service (factory) with your Base Controller logic and
return the Constructor (example)
Use $injector.invoke in your derived Class (to implement your
base controller) in order to enrich the actual controller $scope
(example)
There's no an official "angular way" to implement this features, but I hope the angular team will work on it
Use case
For use in a form, I created a directive that tracks changes in an array. It allows changes to be reverted and deletions and additions to be stored separately. It allows for an array (one to many mapping in the database) to be updated incrementally (rather than requiring the server to either diff, or rewrite the entire list).
Problem?
My question is about the way I expose the functionality to the controller's scope. I currently use an two-way databound attribute on the directive's scope. This works, and it seems reliable (of course you can easily break it by reassigning the scope's value, but intentionally you can break anything).
Code
You can see this plunk to see this in action. It allows methods on the directive's controller to be called from the view and the view's controller. (I am using the directive controller intentionally because that's what I do in my actual code for the directive to directive communication, but this could also just be placed in the linking function.)
Question
Is this way of doing it bad design? Am I completely throwing AngularJS out of the window now and hacking in my own code. Are there any better ways to expose functions from a directive (keep in mind that there'll be multiple of these in a single form).
It's very easy to pass in my-attribute="someFunction()" to have the directive be a consumer of the view controller. I can't find a better way to do the opposite and have the view controller consume from the directive.
Alternative?
I've been thinking about using a service here, in which the service will provide an object that is instanciated in the view, passed to the directive, and have the directive blurp out it's results to that object. Then in turn have the view controller consume the information from that service's object. Would this be a better approach?
There's nothing wrong with your approach. In fact built-in angular directives such as ng-form use this approach to store the controller in the scope (see the name property of ng-form) http://docs.angularjs.org/api/ng.directive:ngForm
For more re-usability though I would put the api methods on the controller and then put the controller itself in the api:
this.getChanges = function () {};
this.resetChanges = function(){};
$scope.api = this;
In directives, the main purpose of the controller is to serve as an api for other directives (if you didn't need an api for other directives you could just do everything in the link function). Doing it this way ensures the api is available both on the scope as well as to any directive that 'requires' the oneToMany directive.
For a dashboard type project (one page, multiple smaller views of different models) we are using among other tools the Angular framework.
The problem consists of the following:
I have a controller, which is responsible for retrieving workitem data. For now the workitem data consists of bugamount and backlogitems.
I have 2 functions in this controller responsible for updating the scope variables (bugAmount and backLogItems)
I have 2 html files which serve as views. These are included in out main dashboard.html (at basically a random place).
The problem I have now is that the views don't share a common ancestor which has the controller directive which links to the workItemController. I could do that and it would work perfectly, however this seriously reduces the layout possibilities.
Basically the problem: Each view now has the ng-controller attribute because they both need access to the scope. This doesn't work and only one of the views gets updated.
For reference these two divs should be able to be locatd anywhere on the page, and should have access to their shared data (which the ontroller supplies)
<div class="backLogView" ng-controller="WorkItemsController">
...
<tr data-ng-repeat="b in backLogItems">
<td class="text-left">{{b.title}}</td>
<td class="text-left">{{b.status}}</td>
<td class="text-left">{{b.assignedTo}}</td>
</tr>
...
</div>
<div class="bugAmountView" ng-controller="WorkItemsController">
...
{{BugAmount}}
...
</div>
So far I've considered:
Creating a mutual parent where the ng-controller directive is used, instead of in the separate views. This is an option but will severely reduce the placement of the views (which as far as I'm concerned is a big no-no)
Using the $rootscope for the controller, for issuance update the current scope to $rootscope.workitems.. This will probably work but means that every controller can reach every other controllers data. Seems to me that can't be right either
Is there a good way to still have the linked functionality in one controller (as it should) but display the views for it's data (bugamount and backlog) in the webpage where they don't have a common ancestor.
You could consider using a service that can be injected into each controller. As the service would essentially be a singleton, the data contained in that service would be shared across the controllers.
You could even assign it to the local $scope for the controllers.
e.g.
var YourApp = angular.module['YourAppName'];
YourApp.factory('YourServiceName', [function(){
return {
var1: '',
var2: '',
var3: ''
}]);
So in your controllers, you can inject 'YourServiceName'. You can assign data to the properties and they would be accessible in other controllers that also have the service injected. If you add to the $scope, changes would be reflected in your view.
so, for 'WorkItemsController', you could have something like:
YourApp.controller('WorkItemsController', ['$scope', 'YourServiceName', function($scope, YourServiceName){
$scope.ServiceData = YourServiceName;
}]);
Therefore in your views you could access ServiceData.var1 etc. and if this data is updated elsewhere via the service, it should automatically reflect in your view.
This way you would be sharing data between scopes without relying on having a shared parent or using $rootscope.
Using the service, you could even move the functionality for retrieving the original data into the service and keep it all together. Data retrieval could still be triggered by a controller, but via a method call on the service, with the service updating its own properties that are accessible in the views.
I'm adding another possibility. If you want to refresh your view dynamically, the service answer won't do it. You have to use events : $emit/$broadcast so you can refresh all the views that depends of those datas.
Check Working with $scope.$emit and $scope.$on for more information.
Here my app.js code :
Ext.Loader.setConfig({
enabled: true
});
Ext.application({
name: 'KP',
appFolder: 'scripts/app',
controllers: [
'login.Login'
],
views: [
'login.Login'
],
launch: function () {
var view = Ext.widget('login')
}
});
If I want to use some others views, controllers, models and stores in my application, should I define them in app.js? (like this : controllers[....all of my controllers.....])
Are there other way to get to work init function in my controllers? Thanks!
There are many ways...
Following some basics first:
All controllers that are listed within the controllers array of the application controller get instantiated at startup (application get initialized with the onReady event. Also the init() and onLaunch() methods of the listed controllers get called. See the linked API for details when this occurs). Now each instantiated controller initialize its stores,views and models (creates getter for all and over that creates instances of each store while overwriting the storeId and append them to the Ext.StoreMgr). Note that each controller will contains his own models, stores and views.
The simplest way to receive a controller that is not listed in the application controller is by using a reference of the application controller and calling getController(name)
It is Important to know that while using that way the receive a controller instance the getter method will not call the init() or onLaunch() method for the invoked controller, it will just try to get the controller or create it. You should also note the API for those methods in these controllers are no longer correct. You need to set an init bool to these controllers and check it on the reference that the getController(name) method hands back to you. If it is undefined/false you call the init() / onLaunch() by yourself and set it after that. I use these technique by myself to reduce intial load for bigger application. I guess you don't really need this for just a handful of controllers.
Update
Due to some changes in the 4.1.x release it is no longer required to init the controller
manually. This is now done for us by the getController method
You can define as many controllers as you want. To implement hierarchy you can define views and stores related to certain controller within it.
For example:
controller/
artists.js (inside: artistsView1.js, artistsView2.js, artistsStore.js)
paintings.js (inside: paintingsView1.js, paintingsStore.js)
All of your views and stores used in controllers would be loaded and initializadduring Applocation load.
The controllers you define in your application are loaded before the application launch() is called. As each controller is loaded its models, views and stores (MVS) are also loaded.
Although you can define the MVSs in your application, I'd recommend that each controller defines its related MVSs, as it is possible for the programmer to load controllers (and their related MVSs) upon request rather than by default. It is also a good practice from reusability point of view (so if you ever to reuse the controller, you know what MVSs come with it).
You may want to include some views in the application if, say, they are used without controllers, like some sort of a custom window you display if there was some error in the system.