strict $scope to prevent a property jungle - angularjs

I find that when creating applications using angular the $scope starts to feel messy as there's so much added to it, and there's actually no way of restricting the properties and methods assigned to the scope. In my opinion it becomes very hard to keep it clean and overseeable.
I have created a module that wraps around the $scope object, which then takes 2 objects to predefine properties and methods that are allowed on the scope, like so (note that the exact code is not the issue here):
// set which scope properties are valid. currently returns $scope
// I was not able to properly extend $scope with new getter/setters
// note that this contains the FULL overview of valid properties on scope.
$scope = enforcer.init(
$scope,
{ "foo": null, "bar": null /* object with valid properties */ },
{ "doFoo": null, "doBar": null /* object with valid methods */}
);
// set the actual property on the scope, could be anywhere in the controller
enforcer.set('foo', 'baz');
enforcer.set('doFoo', function () { /* ... */ });
// try to set an invalid property on the scope -> error
enforcer.set('biggle', 'boggle');
However, as I'm not overwriting native getters/setters this means I have to call things like enforcer.set() to add a variable to the scope. This will in turn match the predefined properties to see if the desired property is actually predefined and will throw an error if it's not. Unfortunately this also kind of breaks my auto-completion in the editor. Next to that, scope references from the HTML are also not checked and can be pretty wild. I could bind on anything and not find a typo in the bound var for hours.
I have not been able to find a proper way of redefining the getter/setter of the scope, and even if I did I could not be sure this would not break when angular updates the original $scope object.
What are your suggestions on keeping the scope strict and clean? I'd like the scope to feel the same as any variable in javascript, requiring a declaration (i.e. var foo; before being allowed to use it.

The scope should only serve as a wiring tool which links together services, factories and resources. Thus, the rule of thumb is that the scope should do very little, and if you feel that your scope is cluttered by too much variables, it means that probably you should start thinking about refactoring your code.
Read point 3 of this article: AngularJS antipatterns and pitfalls

Related

Why might we reference $scope.model in angular code?

I'm working on some code that references $scope.model. So my understanding is that in angular the $scope essentially represents the model anyway.
In the code I'm working on certain properties have been added to a $scope.model. So is adding members under $scope.model a recognised (or sensible) angular practice (since 'model' seems to essentially be an arbitrary member that someone has simply decided to add to the scope). If using a $scope.model is a useful practice, when should it be used? There don't seem to be online resources that mention $scope.model.
actually every controller had associated $scope object (contain some properties) when you use $scope.model you are setting a model property or a function behaviour and make it available to the view (among other $scope properties in most cases you dont have to worry about them)
you can learn more about the $scope and it properties and life cycle in angularJS documentation its a usefull resource
https://docs.angularjs.org/guide/scope

Opinions agains using $rootScope excesivelly

I would like to persuade my co-worker that it is a better approach to use component directives than to use $rootScope everywhere. I need arguments against his ones because he is very stubborn and a very good speaker (which I am not). He thinks that $rootScope prevents spaghetti code. This week I have refactored the project and there are no more spaghetti but I don't want him to rework everything to $rootScope.
Please tell me about problems and issues that can arise when using $rootScope. Thank you.
EDIT
Are there any security issues with $rootScope?
EDIT 2
My friend came with this construct and wants to put it in every component:
function Controller(service, $rootScope, $scope) {
var vm = this;
$scope.a = $rootScope.a;
$scope.b = $rootScope.b;
$scope.c = $rootScope.c;
$rootScope.$watch('mapLoaded', function () {
$scope.a = $rootScope.a;
$scope.b = $rootScope.b;
$scope.c = $rootScope.c;
}, true);
Would the issue of destroying scopes and removing wathces that #charlietfl described in comments appear? I am definitelly not gonna let him code like this but I need the arguments against it. Thanks again.
$rootScope exists, but it can be used for evil
Scopes in Angular form a hierarchy, prototypally inheriting from a root scope at the top of the tree. Usually this can be ignored, since most views have a controller, and therefore a scope, of their own.
Occasionally there are pieces of data that you want to make global to the whole app. For these, you can inject $rootScope and set values on it like any other scope. Since the scopes inherit from the root scope, these values will be available to the expressions attached to directives like ng-show just like values on your local $scope.
Of course, global state sucks and you should use $rootScope sparingly, like you would (hopefully) use with global variables in any language. In particular, don't use it for code, only data. If you're tempted to put a function on $rootScope, it's almost always better to put it in a service that can be injected where it's needed, and more easily tested.
Conversely, don't create a service whose only purpose in life is to store and return bits of data.
-- AngularJS FAQ
I will response myself to Edit 2 citing this:
Using $watch means whenever you read this code in the future you’ll
have to consider whether it’s being triggered by something else, too.

Angular - Access Enums from view

I have some server side enums that I'm sending down to an angular application.
Ideally, I'd like to be able to access the enums for this sort of behavior:
<select ng-options="type.name as type.value for type in Enums.TYPES" />
I've tried several things to get this working:
angular.module('myModule').constant('Enums', {myEnumObject})
var Enums = {myEnumObject};
$window.Enums = {myEnumObject};
obviously, none of these ways make the object accessible from the view. I've also tried using services to return the object, but that doesn't make it accessible from the view.
My problem here is that I know it can be done from the scopes, using one of these:
$rootScope.Enums = {myEnumObject};
OR
$scope.Enums = {myEnumObject};
My concern with this is that this seems unsustainable. Using a scope seems like bad practice since every child scope created will be polluted with this data.
I could also do it by assigning the enums to a controller, but then that seems like it's kind of defeating the purpose of having these global objects. In reality, they ARE constants that never change.
It seems like I must be missing something here. Can somebody point me in the right direction for maintaining sustainability for this code, as well as handling it in an "angular" way. Thanks.
One way to avoid attaching the constant or service enum to every scope you need, is to take a directive centric design to your application. As directives can have isolate scopes, you can bundle your views/controller and scope bindings together in a nice reusable package. For example:
.directive('SomeEnumThing', function(Enums){
return {
scope: {}, // don't forget to set an isolate scope on the directive
templateUrl: 'sometpl.html',
link: function(scope, elem, attrs){
// bind the enums to your directive's scope
scope.enums = Enums;
}
};
});
The only downsides to this method are the extra verbosity in writing a directive (but ultimately more reusable), and the added requirement of setting up any other necessary bindings with outside objects (as you are now outside the general scope hierarchy).
You could do:
angular.module('myModule').constant('Enums', {myEnumObject})
Then in any scope that you want to use it, you can
$scope.Enums = Enums;
This is not very polluting, only a scope that requires it will have it set.
Global constants can be put on the $rootScope, and this can be seen as polluting. But if this is something that you do need all throughout your app in various directives, it's not such a big deal (IMO). There's no correct answer to this question.

angularjs same controller used twice, data updated but not in the view

I use a same controller twice on my page, with same data, but i display it not in the same way (not same parts of the array).
I can see with ng-inspector that the scope is correctly updated when i perform some change, and it's duplicate. But in the view, it's doesn't change ! A simple param to false by default and passed at true with a simple timeout, in the view, it's always to false.
If i display only one time the ng-controller, the view it's updated.
How to correct that ?
Ok, so you didn't actually give much in the way of code, but this sounds like you're falling victim to one of the classic scope blunders. The first thing to try is to use properties of objects, not primitives on the scope.
This means instead of using this code ---
$scope.myBool = false;
{{myBool}}
use this code ---
$scope.myBool = { value: false };
{{myBool.value}}
The reason being is that Angular likes to do funny stuff like use prototype for new scopes. This means you'll get the prototype of the parent scope, and then when you instantiate your scope, you can override the parents prototype with your new value, without changing the parents value. You get past this by using an object on the scope instead.
The second option that might be occurring is you're doing something that isn't causing an angular digest cycle to happen. You might need to manually kick one off.
Without seeing any code, there's no telling, though.

AngularJS, bind scope of a switch-case?

To get a grip on AngularJS I decided to play around with one of the examples, specifically, simply adding a "complete" screen to the Todo-example, when the user has entered 5 todos it uses a switch-case to switch to another div. Code is available here http://jsfiddle.net/FWCHU/1/ if it's of any use.
However, it appears that each switch-case gets its own scope ($scope.todoText is not available), however it can be accessed using "this" from within addTodo() in this case. So far so good, but say I want to access todoText (which is inside the switch-case) outside of the switch-case, how would I go about doing that? Can I bind the switch-case scope to the model perhaps, is it accessible in some other way or should this be solved in some other way?
PS. I'm not trying to find ANY solution to the code above, I'm pretty sure I know how to solve it without using switch-cases, I want to understand how scopes work in this case!
Mark has some great suggestions! Make sure you also check out the AngularJS Batarang Chrome Extension to see the various scopes and their values (among other things). Note it doesn't appear to work well with jsFiddle.
I'm not sure how to access inner scopes directly but here is one way to access the same text in the outer scope by binding to an object instead of a primitive.
1) Declare todoText as an object instead of a primitive in your controller:
$scope.todoText = {text: ''};
2) Bind to todoText.text instead of just todoText:
<form ng-submit="addTodo()">
<input type="text" ng-model="todoText.text" size="30" placeholder="add new todo here">
<input class="btn-primary" type="submit" value="add">
</form>
3) Modify the existing functions to use todoText.text:
$scope.addTodo = function() {
$scope.todos.push({text:$scope.todoText.text, done:false, width: Math.floor(Math.random() * 100) + 50});
$scope.todoText.text = '';
};
Take a look at this fiddle and note that the text displayed beneath the textbox when you type something in is accessing the todoText.text on the outside scope.
If you change the code back to use a primitive (like in this fiddle) the parent scope todoText won't reflect any changes you make to the textbox. This is likely more to do with how JavaScript copies reference values (see this post for more info) and less an AngularJS specific thing.
Update2: Now that I know a little more about AngularJS, here's a much better answer.
say I want to access todoText (which is inside the switch-case)
outside of the switch-case, how would I go about doing that?
There is no way for parent scopes to access child scopes. (One reason for this restriction, according to Angular developers, is for easier memory management of scopes.) (Well, you could use $$childHead and $$childTail to access child scope, but you shouldn't!)
Can I bind the switch-case scope to the model perhaps, is it
accessible in some other way or should this be solved in some other
way?
There are three common ways to access the parent model from the child scope:
Do what #Gloopy suggests: create an object in the parent scope, then refer to properties on that object in the child scope.
Use $parent in the child scope to access the parent scope and its properties, even primitive properties.
Call a method on the parent scope
To convert your fiddle to use $parent:
<input type="text" ng-model="$parent.todoText" ...
$scope.addTodo = function() {
$scope.todos.push({text: $scope.todoText, ...
$scope.todoText = '';
As I mentioned in the comments on Gloopy's answer, ng-repeat and ng-switch both have the new child scope prototypically inherit from the parent scope. ng-repeat also copies the loop variable/item to the new child scope (and the nuances that #Gloopy describes with primitives vs object applies). ng-switch does not copy anything from the parent scope.
To see what the inner/child scope looks like, add the following after the ng-switch-when:
<a ng-click="showScope($event)">show scope</a>
and add this to your controller:
$scope.showScope = function(e) {
console.log(angular.element(e.srcElement).scope());
}
Update1: (strikethroughs added to bad advice, []'s added for clarity)
For this scenario, where AngularJS is creating additional inner scopes (implicitly), and you don't really want/need another controller, I like Gloopy's solution. A service (what I originally suggested below) is [the wrong way to do this] probably overkill here. I also like that Gloopy's solution does not require the use of 'this' in the controller methods.
Original Answer: (strikethroughs added to bad advice, []'s added for clarity)
To see where scopes are being created (if you haven't tried this already, it is handy):
.ng-scope { margin: 4px; border: 1px dashed red }
To access todoText outside the switch-case (hence outside of its scope), you're essentially asking about inter-controller communication, since multiple scopes are involved. You have a few options, but a service is probably best. Store the data (that needs to be shared) inside the service, and inject that service into each controller that needs access to the data.
For your specific example, I think you'd need to attach a controller to each switch-case and inject the service into it, to get access to the shared data.
See also AngularJS: How can I pass variables between controllers?.
The other options:
Using $scope.$parent in the inner scope is [one way to do this -- see Update2 above] not recommended, since then a controller would be making assumptions about how the data is presented.
Using $rootScope is not recommended, except maybe for simple, one-off applications. That shared data may start to take on a life of its own, and $rootScope is not the place for that to happen. Services are easier to reuse, to add behavior to, etc.
Using $scope.$emit is another option, but it seems messy and a bit odd: emitting events to share data (instead of triggering behavior).
[Using an object in the parent scope is probably best -- see #Gloopy's answer.]

Resources