I'm making table with lots of data (using ag-grid - http://www.ag-grid.com/, but it's not important).
In that table I want to trigger angular service to open modal (with detailed info).
Problem is that I don't want to use angular's directive (I have over 10000 records) only to use something like <a on-click="angular.service('modal').method('open-modal').params({id:1})">Open modal</a>
Is it possible?
Calling something angular-related outside it is quite a bad practice (it might also affect the digest/apply loop and not work as intended). Maybe there's an alternative:
If you are inside a controller you can save your service inside a scope variable and then use ng-click to call a function inside your service.
var Controller=function($scope, YourService) {
$scope.myService=YourService;
};
Controller.$inject=['$scope', 'YourService'];
app.controller('Controller', Controller);
And then, in the HTML:
<a ng-click="myService.method('open-modal').params({id:1})">Open modal</a>
if you still need to use angular-related methods outside it then you should be able to call the "scope()" function this way:
<a on-click="angular.element('#idOfTagWithNgController').scope().myService.method('open-modal').params({id:1})">Open modal</a>
You still need to assign your service to a scope variable to be able to access it this way. idOfTagWithNgController is of course the ID of the HTML tag with the ng-controller attribute (if it has no id you can add it). Mind that this is still bad practice and should be avoided
You can write your own service and register it as a factory, than inject it in your controller and call service method from your controller when triggering ng-click
Related
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 an AngularJS app, I have a service that uses $http and returns a promise. I have a controller which requires the service. In the controller DataService.then(function(data){ scope.viewModel.data = data;})
Then I have a directive with isolate scope.
scope: { data: '='} then do something with scope.data inside the directive.
Finally the html
<div ng-controller="myCtrl">
<div my-cool-directive="" data="viewModel.data"></div>
</div>
My question is this presents a chicken before the egg scenario. When the service data is hard coded, all is well and glorious, however when using async $http to actually call a server and get data, scope.data is null inside the directive. How can I properly get the directive the data it needs when the service call finishes and the controller scope property is set. I do not want the directive to have a dependency on the service. I prefer that the controller will drive the directive model. Is $emit or $broadcast the way to go and use a $watch? Basically eventing? or is there a preferred way to do this? I'm certain others have faced this exact issue. I would like the directive to continue to have isolate scope as I may want to extend at some point. I did try to Google first but I don't think I was phrasing the question correctly.
<div my-cool-directive="" data="viewModel.data" ng-if="viewModel.data"></div>
should do the trick.
You could also create a watch in the directive, so that the callback function of the watch is called when the data changes from undefined to something else.
I just started out with Angular and have been reading through a lot of Tutorials.
Now the free one at CodeSchool which was my starting point doesn't mention $scope at all.
From what I've gathered, the controllerAs syntax is relatively new (1.2.0) but it seems to allow you to get away without using $scope directly.
Some articles say "use controllerAs" with an explanation, but most just use $scope. But I couldn't find any explanation why they'd choose it.
Is this now mainly a case of favoring one over the other or are there still reasons to use $scope?
Even many new directive plugins use it instead of allowing you to bind it to a particular controller.
edit: To clarify, I want to know when to use $scope, not the reasons not to use it :)
In the Angular documentation for ngController it explains the advantages to using 'controller as' vs. injecting $scope. Here's what it says:
Using controller as makes it obvious which controller you are accessing in the template when multiple controllers apply to an element.
If you are writing your controllers as classes you have easier access to the properties and methods, which will appear on the scope, from inside the controller code.
Since there is always a . in the bindings, you don't have to worry about prototypal inheritance masking primitives.
For my own part I've found using 'controller as' quite beneficial as it forces me to consider whether code I'm adding to a controller would be more appropriately added into a service or directive.
For example: watches. Watches are something you should avoid in controllers but having easy access to $scope allows you to set them up easily. Using 'controller as' has forced me to think more carefully about whether I really need to do a watch. Usually a watch can be accomplished with a directive. This has led me to create smaller controllers that only set up an initial state and communicate with services, a pattern I've found much more performant and maintainable.
The best answer I can give you is this:
The short:
Whenever you want to expose logic to the template, use Scope.
Whenever you want to persist logic to an element, use ngController.
If you want to expose your controller's values directly in the template (through the scope), use the "controller as syntax".
The long:
An Explanation
Scope or $scope, as you've put it, is where we maintain values (no matter the type: Function, Object, String, etc.) that may be available to the template within that scope. For example, consider the following:
The HTML:
<div ng-controller="MyCtrl">
<div>{{ message }}</div>
</div>
<div ng-controller="MyCtrl as ctrl">
<div>{{ ctrl.message }}</div>
</div>
See those interpolations? Well, guess what? They're both accessing Scope. The "controller as syntax" creates an alias for MyCtrl and publishes it on the local scope. Once the element has been linked, if you look at $scope you will actually find a property ctrl that exposes the controller.
The Javascript
function MyCtrl($scope) {
$scope.message = "controller MyCtrl";
this.message = "controller as syntax";
}
Where ever I use MyCtrl these two messages are available. But to readily access a value on the controller itself we use the "controller as alias" syntax.
They are honestly two different methodologies. The controller as * syntax allows the developer to put the controller onto the scope and more easily access said controller. So, in the end it all ends up on the scope. Otherwise, say through a directive's link function, you have to access the controller the require property. The controller's methods and properties don't necessarily need to be exposed to the template, rather just used in the logic. (In addition, you can access a controller through a jqLite's data() function as well).
Sometimes when propagating a controller to multiple elements we want something that is available by default to every element that uses this controller. This is particularly valuable when creating directives. Take a look at ngModel and see how we have multiple methods common to every element that uses ngModel.
Scope vs. Controller
The major thing to consider is that a child controller can inherit the scope of it's parent. The cool thing is that the child scope will inherit that bit of parental controller properties from the parent.
<!-- ctrl1 -->
<div ng-controller="MyCtrl as ctrl1">
<div>{{ ctrl1.message }}</div>
<!-- ctrl2 -->
<div ng-controller="MyCtrl as ctrl2">
<div>{{ ctrl2.message }}</div>
</div>
</div>
Notice that both are using the same controller but they have different aliases. Now, the controller properties are being passed down to the children through Scope. So the child can access the parent through it's alias. So, through this syntax you can clearly see the separation of the two instances of MyCtrl. They both have a message property on their scopes, but they are easily distinguished without digging through parents, children, siblings, etc.
In Conclusion
If you want to expose values to the template use scope. If you want to bind values to an element that don't necessarily need to be exposed in the template, use the controller. If you need to access values from your controller in your template, use the controller as syntax. Using the controller as * syntax places the controller's values on the scope under the alias created in the syntax. So, in that case, you are using both the controller and the scope together.
As stated in the Angular documentation the benefits are
Using controller as makes it obvious which controller you are
accessing in the template when multiple controllers apply to an
element.
If you are writing your controllers as classes you have easier access to the properties and methods, which will appear on the scope,
from inside the controller code.
Since there is always a . in the bindings, you don't have to worry about prototypal inheritance masking primitives.
I really like it since it makes it easy to differentiate between which controller I am currently accessing.
I read a few blogs and came to a conclusion for usage purpose do not mix $scope and this.
There is a reason for that , "this" and $scope can be different they are not always the same for example-
If I have defined a controller on "this" and I call another controller in it then the $scope will be set to the controller I called but "this" will always be the current context that is the controller in which I called the other controller.
So in this case $scope and "this" will not be same and using them interchangeably here may lead to some unexprcted behaviour.
Please correct me If I am wrong.
Learning angularjs, the curve is steep but I can see that it's going somewhere.
Given the following:
var app = angular.module('theApp',[]);
app.controller('Controller1', function($scope) {
var self=this;
$scope.thing = "hi.";
this.thang = "yo.";
$scope.doIt=function(){
return self.thang;
};
});
thing and doIt() are exposed through $scope, but thang is not:
<body ng-app="theApp">
<div ng-controller="Controller1">
<div>{{thing}}</div>
<div>{{thang}}</div>
<div>{{doIt()}}</div>
</div>
</body>
Is there any way to bind or otherwise expose thang? Or is this. completely pointless inside an ngController?
No problem if it is, just trying to get it straight in my head. I'm getting the idea that and ngController only communicates through $scope (and other ng service objects), they aren't independently accessible to other code; and I see that as a basically good thing, within anjularjs.
But it means you have to go all-in with angularjs; legacy/external code has to be wrapped in angularjs services and stuff in order to communicate across an ngController, would that be a fair statement?
I'm using angularjs 1.2.13, does the behavior change across versions?
Is there any way to bind or otherwise expose thang?
It depends how you want it to be exposed. If you want it to be available in the view; you'll have to put it into the $scope.
If you want to share the value with other controllers, you'll probably want to use a service or to store the value.
Or is this. completely pointless inside an ngController?
I consider variables, or methods, not in the $scope to be similar to a protected variable [or method]. Depending what your building their can be uses for that. I would not consider it pointless.
But it means you have to go all-in with angularjs; legacy/external
code has to be wrapped in angularjs services and stuff in order to
communicate across an ngController, would that be a fair statement?
No, it doesn't mean that. For example, I once wrote an app with a login form, and needed to hash the password before calling a remote service. I just Googled and found a JAvaScript hash library; then imported it into the html page (AKA Used the script tag), and was able to access the hash function from within the controller without doing any other work. This is due to the nature of JavaScript and how browsers work.
However, by doing this I added an external dependency into my controller which would make it difficult to write tests against. If I had wrapped the library in an Angular service, then passed it into the controller I would have built a semi-self-documenting API for the controller that could be tested with known dependencies.
So, you don't have to wrap external services in AngularJS. But, you may want to. As long as you understand the trade offs your making, you can make good decisions for your use case. (Just like any 'best practice').
I'm using angularjs 1.2.13, does the behavior change across versions?
All versions of Angular I've used have the same "Controller / Scope / Dependency Injection" approach.
The ng-controller tag can put the controller on the scope. These are equivalent:
<body ng-app="theApp">
<div ng-controller="Controller1 as c">
<div>{{thing}}</div>
<div>{{c.thang}}</div>
<div>{{doIt()}}</div>
</div>
</body>
And:
app.controller('Controller1', function($scope) {
var self=this;
$scope.thing = "hi.";
$scope.c = this;
this.thang = "yo.";
$scope.doIt=function(){
return self.thang;
};
});
Personally I think putting the name in the tag usually makes more sense as the template reads better.
Remember you can also access controllers which aren't on $scope from the link function of a directive. When you do that you refer to the name you gave the controller when you registered it, Controller1 in this case. See the require attribute here: http://docs.angularjs.org/guide/directive
I have a service that returns a json object that it makes, for brevity lets say it looks like this:
.service ('levelService', function () {
// service to manage levels.
return {
levels : [{name:'Base', href:'base'},{name:'Level 1', href:'level1'},{name:'level2', href:'level2'}]
};
})
I think that is fine, but I want to use it now, in a template. Currently I have something like this:
<ul class="dropdown-menu" ng-init="levels = [{name:'Base', href:'base'},{name:'Level 1', href:'level1'},{name:'level2', href:'level2'}];">
<li ng-repeat="level in levels">
<a ng-href="#/modeling/level/{{level.href}}">{{level.name}}</a></li>
</ul>
How can I get the ng-init to now use the service? I feel like the right thing to do, is add the service to the controller, but this is outside of any controller. Do i need to create a new controller for this space, or can i directly reference the service?
Yes, it would be best practice to create a controller.
The idea behind the MVC app architecture is that you don't tightly couple your objects/classes together. Injecting a service into a controller, then subsequently your controller adding levels to $scope means that your HTML doesn't have to worry about where it gets the data from.
Also, using ng-init in that way is arguably fine for knocking up a very quick prototype, but that approach shouldn't be used in production code (as your model's data itself is tightly coupled to your view's HTML).
Tip: It might be a good idea to use a controller for the parent container of your dropdown-menu (ie. the page/section) and then use a directive for your dropdown-menu itself. Think of a directive as a view component.
In general, you might find the video tutorials at egghead.io helpful.
put the service in a controller..
Then you can call service in your template..
app.controller('yourCtrl', function($scope, levelService) {
$scope.levelService= levelService;
});