Why ngClick doesn't work anymore after $compile? - javascript

http://plnkr.co/edit/kL2uLPQu2vHHKIvRuLPp?p=preview
After a click on the button, from the controller i call the service who compiles the html and inject it in the body.
The html is compiled (see "Hello World" from $scope.name) against the $scope of the controller, but ngClick doesn't work.
Why?
How i can compile against the scope of the single phone (inside the ng-repeat?)?
I know that it's better to have a directive in this case, but i need to understand how it works because i need it for a dialog (see How can I get AngularJS binds working with a fancybox dialog?)

The two problems I see here both appear to stem from a misunderstanding of expressions and scope in angular.
When you use expressions in directives or bindings, such as in these two examples
<p>{{ phone.name }}</p>
<a ng-click="alert('angular')">Link</a>
they cannot access the JavaScript scope. Any identifiers used in these are coming from the angular scope, i.e. $scope. So
{{ phone.name }}
will display
$scope.phone.name
and
ng-click="alert('angular')"
will be trying to call
$scope.alert('angular')
Looking at your plunker, you are trying to use both phone and alert without assigning them to the scope. You can create a function to perform your alert by assigning it to your controller scope, but phone will be more difficult as it needs to be different every time.
You can either create a brand new scope for this and assign phone as I have done here, or you can pass the scope from inside your ng-repeat and compile your new element in this scope as I have done here.

The name alert is not defined on the $scope with which the template is compiled. If you do include an alert function, then ng-click does work:
$scope.alert = function (msg) { window.alert(msg); };
Demo
However, as you have yourself pointed out, this work is better suited for a directive than a service.

Related

Assigning to a isolate scope object outside of $watch within a directive yields unpredictable behaviour

At work, we use AngularJS as our front end. Ours is a massively forms driven app and requires a lot of reusable components.
We model these reusable components as directives with isolate scope. This makes sense for us because there are multiple cases where we end up using the same directive 2 times on a page.
When doing this, I seem to have encountered a problem relating to isolate scope object's being set from outside a $watch function.
Scenario:
Each directive has its own isolate scope
Each directive receives its inputs via a load-id attribute
The load-id attribute has a $watch on it and reacts to change.
Each directive performs an operation (a simple increment in this case) on the value received via load-id and binds the result to the result attribute so that it is accessible to the parent scope.
This increment operation is performed inside a increment method called from the $watch.
Usage:
<div foo-bar load-id="{{loadId1}}" result="res1"></div>
<pre>{{ res1 }}</pre>
<div foo-bar load-id="{{loadId2}}" result="res2"></div>
<pre>{{ res2 }}</pre>
<div foo-bar load-id="{{loadId3}}" result="res3"></div>
<pre>{{ res3 }}</pre>
Expectation (relative to above mentioned usage):
res1, res2 and res3 will be loadId1, loadId2 and loadId3 incremented by 1 respectively.
Reality
The last directive result (res3 in our case) is the only object bound. res1 and res2 stay undefined.
Here is the simplest way to reproduce the problem: http://jsbin.com/henixicubo/1/edit?html,js,output.
Additional research
After some digging around, I've come to notice that if we don't use the increment function and bind to scope.result directly within the $watch, things work fine.
Here is the same example as above, but not using the increment function: http://jsbin.com/figizecadi/1/edit?html,js,output which works completely as expected.
This is really puzzling to me. Am I doing something wrong here? Is my understanding of scopes totally whack?
the problem is that your increment function is a global function (it's assigned to window object), JavaScript uses function scope (http://www.w3schools.com/js/js_scope.asp), if you define your function (with var) it will work. I have modified your code, try out this one: http://jsbin.com/lohizibure/edit?html,js,output

AngularJS : Isolated Scope in directive with Compile in directive

I am struggling to get access to an isolated scope from some html I am building in the compile function of a directive. I have a SIMPLE example, the one I am working with is much more complex. Essentially I can't access properties on the isolated scope. Here is the code.
http://plnkr.co/edit/ETIRs4j3EwZ4DFN5XkPc?p=preview
It's odd to me because the in the console the scope looks great, from the post function.
Notice how the html is set up in the compile function:
tElement.append(angular.element('<div>{{name}}</div><span>{{value}}</span><span>Hello</span>'))
Also notice the second directive, Here with an inherited scope everything works fine.
Edit: I updated the first directive(The directive that isn't working) to set a property in the html so I could look for it in the console when I log the scope. It turns out it is on the $parent scope (The property is named "wat"). But why? It makes sense why it wouldn't work, but I don't understand why that html doesn't have access to the same isolated scope? Is there a way to force the html I am inserting to use the isolated scope instead?
Edit 2: Ok so a lot of questions about why I am trying to do this, the best description could be found here. https://github.com/angular/angular.js/issues/7874. Essentially we wanted to ng-repeat some transcluded content, and expose the item from ng-repeat to the transcluded content, but you can't do that in angular, because the transcluded content only has access to the parent scope.
The goal is to get the first directive to work like the second, in that the value of the two properties (value, and name) work from appending html in the compile function, with an isolated scope.
This is how I would go about what ( I think ) you are trying to achieve
http://plnkr.co/edit/mL7h7Hf8TkkpfsPNoL6g?p=preview
By using a template with transclude: true and a link function you can achieve the desired result as you replace the element with your template while interpreting the angular expressions
You could use replace: true to further customise your directive
app.directive('widget',function(){
return {
restrict:'E',
transclude: true,
template: '<span ng-bind="wat=\'YES\'"></span><div>{{name}}</div><span>{{value}}</span> <span>There</span>',
scope:{
name:'#',
value:'='
},
link:function($scope, tElement){
}
}
});

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.

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.

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

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.)

Categories