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.
Related
This is somewhat a follow up on my "Is it bad practice for an angular directive to request data" Q.
My Q Is where would be the appropriate place to keep application data?
for example - information about the current user such as his name and his roles in the app?
differrent areas (on the screen) and components will depend on this data (e.g - side bar will want to know if the curentUser.isAnAdmin and a helloUser Directive would like to know the currentUser.name
Does this mean that the currentUser should be placed on the $rootScope?
and what should be the trigger for retrieving the initial data for the currentUser and for refreshing this information?
I was thinking of having several ngControllers responsible for setting up this data on the scope of the same html node as that of the ngApp, but found out that it is not possible to have multiple ngControllers on a single HTML Item.
I am now thinking of having multiple services with methods that get a scope object and assign the data they are responsible to onto that scope.
It would allow me to separate code for currentUser from code for someOtherSharedAppData into two different services and call both of them from the applications's main controller thus assiging the data to the scope associated with the top-most element in the app - does that make sense?
In fact you asked two questions here:
Where to store and manipulate data?
When and how should I use the $rootScope (compared to $scope)?
1)
I will refer to this article:
Whenever data and methods need to be reusable I would write a service like this for example.
angular.module('modelDemo').service("myModel", [function() {
this.list = [what, ever, items, you, have];
this.property= null;
this.setProperty = function(value) {
this.property= value;
};
}]);
Note, that I'm not passing the $scope as you considered. Instead I would inject the service in my controller and bind the $scope variables like this:
$scope.list = myModel.list;
If you need, you can even bind to the full model
$scope.myModel = myModel;
myModel.setPropery(value)
Got the idea? This way all model changes will be directly available to the corresponging view
{{myModel.property}}
ng-repeat="item in myModel.list"
ng-click="myModel.setProperty(item)"
Conclusion: Yes, you should have different services for your user model and your someOtherSharedAppData models.
2)
I will refer to this SO Question.
In short: If you have data that should be available in many views, it is OK to bind your (service) model to $rootScope variables. As you can see in the mentioned discussion there are also other opinions but I think the conclusion is: It depends on the structure and needs of your application.
I'm adding features to a Web application that I did not write myself. I feel that a swipe feature cannot be added.
I saw somewhere "$scope cannot be accessed in a factory", but I somehow managed to store all scopes created by an ng-repeat in a factory. Since JavaScript passes by value I'm probably not "storing the scope" but still if I set the scopes ng-class within the factory I can clearly see the difference. I thought it was a good idea since scope traversing is a pain. Did I strike gold or am I wasting speed and space on storing the massive data which is scope?
HTML
main.html
<div ng-controller="someCtrlWhichWasDeclaredALongTimeAgo">
.
.
.
<div class="items-container"
ui-sortable="sortableOptions"
ng-model="selectedSession.items">
<div ng-repeat="item in selectedSession.items"
ng-controller="itemEditorCtrl"
ng-class="{'item-last':$last}">
<div ng-if="item._value !== undefined"
class="item inline-comment">
<span class="handle"></span>
</div>
<div ng-if="item._value === undefined"
class="item"
ng-include="'/views/item-editor.html'"></div>
</div>
</div>
<div class="stopper"></div>
</div>
</div>
item-editor.html
<div item-editor
swipe-function="swipeMe"
ng-swipe-left="swipeLeft(item)"
ng-swipe-right="swipeRight(item)">
...
</div>
This will render a list with my magic code. If an item in list is clicked it will expand to full screen. You should be able to swipe between the items from there.
I don't think I have to post more than the HTML to explain the problem. I did manage to make the swipe happen with $rootScope.$broadcast but what if I have hundred items in my list? Will it not grow slower with more and more items receiving message and processing them? I'd rather just load all of the scopes into one factory on selection of this session and then get rid of it when a session is deselected. It might need some time to start but it doesn't take 5 minutes to swipe.
What you're doing is totally fine. You can pass scope objects around your application at will. Just keep in mind what they represent. The $scope object is your interface with the view. Most of the time your services are going to have little to do with the view and should ideally be used to share data between controllers and directives. That said, there are several examples where it makes total sense for a service to indirectly affect a view. For example, we wrote a spinner service that allows spinner directives to register themselves with the service.
The spinner directive injects the spinnerService into it, calls the spinnerService.register function and passes in its isolate scope so the service can keep track of it and use it to toggle the spinner on and off. Then our devs can inject the service wherever they need it and hide/show loading spinners or groups of spinners when they need to.
What they mean by "$scope cannot be accessed in a factory" is that you cannot inject $scope inside a factory. $scope is tied to a DOM element (the element the controller or isolate directive was invoked on), so injecting a fresh $scope into a factory doesn't make much sense and is in fact impossible. You can't do app.factory('myFactory', function ($scope) { ... }); but you can definitely call your factory/service from a controller or directive and pass the scope object from there into a method you provide on your service.
There is nothing wrong with tracking scopes within services as long as you know what you're doing. People will tell you that you can't pass your scope into a service because it's an anti-pattern, but then they'll suggest putting something like this in your directive/controller and then passing it to the service:
var api = {
hideSpinner: function () {
$scope.show = false;
},
showSpinner: function () {
$scope.show = true;
}
};
Sure, you're not directly passing your $scope object to the service, but if the argument is that your scope object could get stuck in memory and never garbage collected, this solution doesn't help either. The API object you build and pass around takes its JavaScript scope chain with it, keeping in memory all the variables within the scope that it was defined, including the $scope of your controller/directive. It has to, or else those API methods wouldn't work. Your $scope is a sort of API for that component and it's fine to use it that way.
A better argument for doing something like the above example is that perhaps your $scope object contains information or functions that you don't need/want to share with the service. Making a little API object with just the things you want to expose to the service makes perfect sense there. Then your service only has access to the things you want it to have access to and can still affect the controller/directive $scope, but only in the ways outlined by the API object you passed in.
Please stay away from storing $scope objects in any type of global store. This includes $rootScope and Angular Services.
Since services in Angular are singleton, if any service is tracking $scope object (in any service variable) then the $scope may never get release, and there are chances of memory leaks.
Also the concept of scope is tightly tied to view and should not be used outside that context.
Maybe you can share some code around your implementation so we can suggest better solution.
"$scope cannot be accessed in a factory"
You're almost correct. You can't inject the $scope service into another service. There's nothing stopping you from creating a service that stores data, and putting $scope in as that data.
THIS IS A BAD IDEA. Services are used to share data between components. $scope is used to communicate with the view. Think of this as bananas and toasters. Both have purposes, no one will fault you for using one or the other, but using a service to store $scope is like stuffing a banana in a toaster.
Each component using $scope should only be concerned with its own scope - and no other (there are very rare exceptions but you shouldn't use them unless you know what you're doing).
Instead, put data in services and then put the service's data onto the scope:
.controller('ExampleController', function($scope, myService) {
$scope.names = myService.names;
});
Even better, you can look into using controllerAs syntax
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.
I just started to use AngularJS, so I'm not an expert.
I have a div that represent the right area of my html view. In that div I have a controller, i.e.
<div class="rightContainer" ng-controller="rightContainerCtrl">...</div>
Inside that div I have a table, a search region, etc. Each region inside that div has its own controllers, it looks like this:
<div class="rightContainer" ng-controller="rightContainerCtrl">
...
<div class="search" ng-controller="searchCtrl">...</div>
...
<div class="table" ng-controller="tableCtrl">...</div>
</div>
the search region for example has its own controller and it is a child of rightContainerCtrl because it needs to alter some content in the parent (rightContainerCtrl), but the rightContainer div is growing and now it's big, and contain several nested controllers.
I think that using this nested controllers it's bad in this context because all the nested controllers share the parent scope, and not all controllers needs to access all the parent scope variables, also all the controllers are "prisoners" of the rightContainerCtrl, so they are highly coupled with their parent controller.
It looks like a God object anti-pattern (God controller in this case), so I think that instead of using nested controllers I can refactor my code to eliminate the rightContainerCtrl controller and use a service instead (like in a facade design pattern), that service then will be used by the controllers instead of sharing scope variables.
but since I'm not an AngularJs expert I'm not sure if I'm right or if it's better to leave this parent controller, maybe I'm missing something, so my question is
When is better to use nested controllers (nested scopes) and when it's better to use services instead in angularjs?
Controller/scope hierarchy should not dictate how data/models are shared in an application. When you think of data sharing in Angular, think dependency injection.
In the video that is referenced in #shaunhusain's answser, Misko states that scope should refer to the model, not be the model -- so don't model/put your data into scopes. Your models/data should normally be in a service.
When writing an Angular app, first think about your models. Put them in services with APIs to get/edit/manipulate the models. Then design your views. Each view should project/use/manipulate some subset of your models. Define a controller for each view that simply glues the needed subset of the models to the view. Make your controllers as thin as possible.
(Naming a controller rightContainerCtrl is also not recommended. The controller should not know about presentation/layout.)
This is 100% judgement call, and should be based on a couple of points.
Using events creates extremely loosely coupled components, they literally do not need to be aware of one another, if you have a situation where some parent controller would alleviate the need to communicate between a bunch of controllers (via services) then it is probably a better solution.
However if you're okay with the controllers each depending on the service (not really a problem) then you could just use the service as a means of communicating changes between the controllers. I've seen tons of arguments against the singleton (of which the service is a flavor, an injected singleton, but singleton nonetheless) I find these arguments to mostly be moot and generally lack a truly elegant and concise solution. If an argument spouts on and on about how when you go from A - D you don't want to take road B but they never seem to offer road C I don't really see the point.
http://www.youtube.com/watch?v=ZhfUv0spHCY&t=30m34s
I couldn't find the exact point in the video, but somewhere at the end here he discusses the usage of controllers vs services (he also reviews bi-directional data binding which will stop you from needing to pollute the global scope so to speak).
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.