Is 'this' truly a reference to "$scope" in AngularJS's context? - javascript

I'm quite new to the superheroic framework AngularJS, so please excuse my noobness! So, while I was trying to develop a simple SPA that allowed admins to change permissions of other users in my website through a table filled with selection checkboxes, I came by an issue. I defined $scope.checked so I could know how many users the administrator chose and also $scope.mainCheckBox which would hold the correct CSS class for the major checkbox (like the one from Gmail, which you use to select all/none) in each of the three situations: all users selected, no users selected and partial selection. This simple logic goes as follows:
$scope.updateMainCheckBox = function(){
if(this.checked==this.users.length) this.mainCheckBox = "qm-icon-checked";
else if(!this.checked) this.mainCheckBox = "";
else this.mainCheckBox = "qm-icon-minus";
};
Running this at the end of the ng-click event callback of the other checkboxes, this code was capable of choosing the class correctly but it wouldn't assign the class to the mainCheckBox. When I changed every this.mainCheckBoxto $scope.mainCheckBox it all went well and the page would behave as expected.
So, I know in vanilla Js this is a reference to the window object but since AngularJS has its own event handling loop, I believe that's not the case now. I expected it to be a ref to $scope, since checked and users were found, but apparently it's not. So, what's happening here? And also, is there a more "angular" way to implement this?

You're mostly right--in the case of your function, this did reference a scope. But, it did not reference $scope; instead, it referenced one of $scope's children.
Try this: add some console.log statements to log out the ID of the $scope variable, and then the ID of the this scope in your callback:
$scope.updateMainCheckBox = function(){
console.log('$scope:', $scope.$id);
console.log('this:', this.$id);
// ...
};
You'll find the IDs are different. This is because the ngRepeat directive creates a child scope, which is a scope that inherits prototypally from its parent. This means that when you assign directly to a value on that scope, it's not reaching the parent scope, which is $scope.
I highly recommend a read through the excellent (and lengthy) "The Nuances of Scope Prototypal Inheritance" by Mark Rajcok; it esplains why you see this seemingly strange behavior on child scopes. (You can also find a copy on the AngularJS wiki.)

Related

AngularJS get the template or original expression

I'm wondering if there is any way I can get the original {{expression}} after angular has complied the directives and interpolated the expressions. For instance if there is a text e.g. Hi, Filip and the user clicks on it, I want to be able to show a pop-up with Hi, {{name}}.
Now, one way I thought of doing that is by analysing the DOM before angular (e.g. during run) and then saving the expressions as additional attributes to the parent element. However, I run into various problems with that (e.g. if parent has other child elements and they are removed, e.g. with ng-if, then I can't reliably know which expression belongs to which text node).
Since Angular keeps watchers for these expressions, it must have a reference to the text nodes they are applied on. Is there any way I could access those?
The second question is, can I somehow get the original element of ng-repeat (before it was compiled and transcluded), for the similar purpose (allowing the user to modify it on-the-fly).
I want to avoid introducing new directives as this is meant to work on existing angular applications.
I'm not concerned about performance or security (i.e. this is not for production applications but rather for prototyping/debugging).
Use factories to supply to your html expressions with reusable logic. Assumes you're using controller as syntax so you can the controller's scope as this in your view.
// factory
function() {
return 'bob';
}];
// in your controller
['somefactory', function(factory) {
this.factoryString = factory.toString(); // => "function() { return 'bob'; }"
this.factory = factory;
}];
// view
<div>hi {{this.factory()}} you were made with {{this.factoryString}}</div>
// interpolated
<div>hi bob, you were made with function() { return 'bob' }</div>
I didn't test any of that though.

AngularJS - Create a directive that uses ng-model and md-autocomplete

I am trying to create a directive for md-autocomplete. I tried using the answer provide by AngularJS - Create a directive that uses ng-model, but it does not work for me.
My CodePen is here: http://codepen.io/colbroth/pen/QyMaQX/?editors=101.
The code is based on the Angular material demo for autocomplete at https://material.angularjs.org/latest/demo/autocomplete. I have multiple pages, which need an autocomplete field to select a state. I do not want to repeat the code for each web page component.
state-directive takes an md-autocomplete input, I need demoCtrl.selected state to reflect the same value in both cases. But when I update the input element, the state-directive does not reflect this and vice versa.
<input ng-model="demoCtrl.selectedState">
<state-directive ng-model="demoCtrl.selectedState">...</state-directive>
You are on the right track. Your problem is that your model is a string - a primitive in javascript but a ngModel always needs to be an object if you want to avoid these kind of problems.
Scope inheritance is normally straightforward, and you often don't even need to know it is happening... until you try 2-way data binding (i.e., form elements, ng-model) to a primitive (e.g., number, string, boolean) defined on the parent scope from inside the child scope. It doesn't work the way most people expect it should work. What happens is that the child scope gets its own property that hides/shadows the parent property of the same name. This is not something AngularJS is doing – this is how JavaScript prototypal inheritance works. New AngularJS developers often do not realize that ng-repeat, ng-switch, ng-view and ng-include all create new child scopes, so the problem often shows up when these directives are involved. (See this example for a quick illustration of the problem.)
This issue with primitives can be easily avoided by following the "best practice" of always have a '.' in your ng-models.
Taken from Understanding-Scopes
It also links to this Youtube video - 3 minutes of well invested time
function DemoCtrl() {
var self = this;
self.state = {
selected: "Maine"
};
}
Fixed codepen

Data binding in directive isolated scope

I want to create a directive with isolated scope, but I'm not able to get it working.
jsFiddle
I want to isolate age model in a directive scope. I want to perform some business logic on that model and then set that model to parent binding. I hope the fiddle is explanatory.
I am also adding a button to the template which when clicked should invoke a submit function:
<button ng-click="submit()">click me</button>
It seems the button is working fine, but why is $scope.$watch() is not begin triggered? In a normal situation, if I change the view value it will automatically update the model value. But now it isn't.
$watch requires a dollar sign, and you pass either a function or a string that is evaluated on your scope, i.e.:
$scope.$watch('age', function(value) {
There are many more errors in your code, for instance you don't have a declared variable called 'age' so this line will reference window.age and give you an error because it is undefined, you need to say $scope.age I think:
age = age+10;
It just looks like your updated fiddle is a playground, hope these point you in the right direction. I'd recommend going through the egghead.io angular videos.

How do I get access to a specific parent scope when I have nested directives?

I'm attempting some Angular and I still have my JQuery head on.
I have a div which contains a dropdown nav which contains a link. When the link is clicked I want the div to go away and be replaced by some ajaxed content, and I want flagged to be set on the flaggable scope.
A have created 3 nested directives, each of which has it's own scope (scope:true):
flaggable, for the div, this AJAXes in content when flagged is set to true.
dropdown, for the nav, this folds and unfolds the nav when the folded state is set on it's scope.
flagButton, this sets flagged on the nearest containing flaggable element.
My question, and it's probably a simple one, is how do I get from the flagButton scope to the nearest containing flaggable scope, so that I can set the flagged parameter in the flaggable scope.
I don't want to assume the flagLink is always in a dropdown, or that there are always 3 nested scopes, there may be more.
In JQuery this would be simple, I would just use traversal to get the .closest('.flaggable') element, and set the value on that. This doesn't seem very Angular to me though. I very well may be attempting something ridiculous.
Thanks!
The scopes inherit from each other. There's no need to know which one is the nearest flaggable scope.
E.g. you can have this in your flaggable scope:
$scope.setFlag = function(val) { ... };
In your flaggable scope you can call $scope.setValue(val), because you can always be sure that this function exists.
Another approach would be setting up a controller for flaggable and specify require: '^flaggable' in the definition of your flagButton directive.
Hint: use the controller and the require attributes in your directive directive definition objects (and read the guide).

Angular multiple instances of same directive, scope is not isolated

I have a problem when creating multiple directives with isolated scope: when I change something in 1st directive it also makes changes in all other directives.
Here is a working example: http://plnkr.co/edit/drBghqHHx2qz20fT91mi?p=preview
(try to add more of Type1 'Available notifications' - a change in 1st will reflect in all other directives of Type1)
I found some solutions to similar problems here but they don't work in my case. Also found a working solution with mapping 'subscription' data to local scope variables in directive (app.js, line 76) but I think there should be a more general way to do this right?
In your directive 'notificationitem' you have the following code, keep it in mind as i explan:
// if all variables are mapped in this way than works
//$scope.enabled = $scope.subscription.enabled;
The reason why all of the 'isolated' scopes are updating is because of this code in your scope declaration in the same directive (notificationitem):
scope: {
subscription: '=',
index: '#'
},
The equal sign on subscription is angular's way of saying "Whenever the current scope updates, go to the parent and update that value as well." This means whenever you update your 'isolated' scope, it updates the parent scope as well. Since all of these isolated scopes are binding to the parent, they will change as well.
Since you want the subscription.value to be the default value of that text field, you will need to do exactly what your commented code is doing:
scope.value = scope.subscription.value;
This will create an isolated value inside of the isolated scope. When scope.value changes, scope.subscription.value will not. All of the text fields now have their own 'value' to keep track of.
Check out this article for information on directive bindings: http://www.ng-newsletter.com/posts/directives.html
Also, another way to get the default value would be to inject your service into the directive, if you don't like the above solution. Hope this all helps.

Categories