Why I can't access the right scope? - angularjs

html:
<!doctype html>
<html>
<head>
</head>
<body>
<div ng-app="project">
<div ng-controller="mainCtrl">
{{ property1 }}
<br />
{{ property2 }}
<div class="ts" d-child property1="{{ property1 }}cloud" property2="property2">
property1: {{ property1 }}
<br />
property2: {{ property2 }}
</div>
</div>
</div>
</body>
</html>
js:
angular.module('project', [])
.controller('mainCtrl', function($scope) {
$scope.property1 = 'ss';
$scope.property2 = 'dd';
});
angular.module('project')
.directive('dChild', function() {
return {
restrict: 'A',
scope: {
property1: '#',
property2: '='
},
link: function(scope, element, attrs) {
}
// template: '<input type="text" ng-model="property1" />'
}
})
I thought the property1: {{ property1 }} would be "property1: sscloud",but it turns out to be "ss",as if it still refers to the scope of the mainCtrl controller, shouldn't it be refer the scope of the d-child directive?
if I use template in the directive,it does refer to the right scope and shows 'sscloud',anyone can tell me why?

When angular compiles an element with isolated scope it has some rules:
If directives has no template property (or templateUrl), the inner content is attached to the parent scope. Actually before this commit, inner contents were getting the isolated scope. check your example to confirm it works on versions less than 1.2
If directives do have a template property then it would override the inner content (unless trancluded).
Only when you use a transclusion, then the inner content is attached to a sibling scope (non isolated).
The reason why angular works this way is to let reusable components be loosely coupled, and not have any side effects on your application.
Directives without isolate scope do not get the isolate scope from an isolate directive on the same element (see important commit).
Directive's template gets the isolated scope anyways.
If you want to alter this behavior you can pass the isolated scope to the tranclusion function like so:
angular.module('project')
.directive('dChild', function() {
return {
restrict: 'A',
transclude: true,
scope: {
property1: '#',
property2: '='
},
link: function(scope, element, attrs, ctrl, linker) {
linker(scope, function(clone, scope){
element.append(clone)
})
}
}
})
I highly recommend you to see these tutorials:
Angular.js - Transclusion basics
Angular.js - Components and containers
And to read more:
Access directive's isolate scope from within transcluded content
https://github.com/angular/angular.js/wiki/Understanding-Scopes

I'm not quite sure about this, I'm pretty sure it all has to do with when each {{}} is evaluated, and when the scope of the directive becomes isolated. My suggestion is to place the content in a template, as it seems to be working when doing so.
If you want to read more about the difference of of "#" and "=" in directive scopes, here's the best text I've found about it.
What is the difference between '#' and '=' in directive scope in AngularJS?

I think you have to use the transclude option.
In fact, as AngularJS docs says :
What does this transclude option do, exactly? transclude makes the contents of
a directive with this option have access to the scope outside of the directive
rather than inside.
Because of the Directives isolated scope that you created
More docs at:
http://docs.angularjs.org/guide/directive

Related

How to retrieve custom property in isolate scope?

I've read quite a lot examples on how to do binding in an isolate scope but not how to retrieve scope custom properties in template.
I think the fix must be very simple but I just don't figure out what's wrong here
<!doctype html>
<html ng-app="myApp">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular.js"></script>
</head>
<body>
<div my-directive>
</div>
<script>
angular.module('myApp', [])
.directive('myDirective', function() {
return {
restrict: 'A',
scope: {
data.myProperty:"1234"
},
template:'Inside myDirective, isolate scope: {{ data.myProperty }}'
};
})
</script>
</body>
</html>
Somehow, data.myProperty couldn't be reached.
You can't directly use and access bounded properties in bindings like you were doing data.myProperty:"1234". It will eventually result in error.
You have to pass custom property via attribute of your directive. Over here you can consider adding custom-data attribute, add mention the scope property name over it. So it would be passed to isolated scope directive.
Controller
$scope.data = {
myProperty: 'My Property Value'
}
Html
<div my-directive custom-data="data">
</div>
directive
angular.module('myApp', [])
.directive('myDirective', function() {
return {
restrict: 'A',
scope: {
customData: '=data'
//without alias it will look like below.
//customData: '='
},
template:'Inside myDirective, isolate scope: {{ data.myProperty }}'
//without alias template would look like below
//template:'Inside myDirective, isolate scope: {{ customData.myProperty }}'
};
})
Note: It seems like you are using older unstable version. If possible update angular to latest angularjs 1.7.x version, to find more features and performant angularjs. After 1.5.x version, you could also use < inside binding (customData: '<data') to keep one way data binding flow. Just to not confuse you I used =(two way data binding) for demo.

Being wrapped by a directive, how can I access its scope?

How can I access the directive's isolate scope in the directive's body? My DOM looks like this:
<div ng-app="app">
<directive>
<p>boolProperty: {{boolProperty|json}}</p>
</directive>
</div>
The boolProperty is assigned inside the directive's link function:
angular.module("app", []).directive("directive", function() {
return {
scope: {},
link: function($scope) {
$scope.boolProperty = true;
}
};
});
The problem is, the child <p> inside the directive binds to the directive's parent scope, not the directive's isolated scope. How can I overcome this?
Click here for jsFiddle.
There are couple of problems in your code.
The default restrict option is A for attribute so anyways your directive will not be compiled because you are using it as an element. Use restrict: 'E' to make it work.
As per the documentation, the scope of the transcluded element is not a child scope of the directive but a sibling one. So boolProperty will always be undefined or empty. So you have to go up the scope level and find the proper sibling.
<div ng-app="app">
<directive>
<p>boolProperty: {{$parent.$$childHead.boolProperty}}</p>
</directive>
</div>
and need to use transclusion in the directive as:
angular.module("app", []).directive("directive", function() {
return {
restrict: 'E',
scope: {},
transclude: true,
template: '<div ng-transclude></div>',
link: function(scope) {
scope.boolProperty = true;
}
};
});
However, this approach is not advisable and break later If you add a new controller before the directive because transcluded scope becomes 2nd sibling unlike 1st as before.
<div ng-app="app">
<div ng-controller="OneCtrl"></div>
<directive>
<p>boolProperty: {{$parent.$$childHead.boolProperty || $parent.$$childHead.$$nextSibling.boolProperty}}</p>
</directive>
</div>
Here is the Working Demo. The approach I mentioned is not ideal so use at your own risk. The #CodeHater' s answer is the one you should go with. I just wanted to explain why it did not work for you.
You forgot about two things:
By default AngularJS uses attrubute restriction, so in your case in directive definition you should specify restrict: "E"
You should use child scope, but not isolated. So set scope: true to inherit from parent view scope.
See updated fiddle http://jsfiddle.net/Y9g4q/1/.
Good luck.
From the docs:
As the name suggests, the isolate scope of the directive isolates everything except models that you've explicitly added to the scope: {} hash object. This is helpful when building reusable components because it prevents a component from changing your model state except for the models that you explicitly pass in.
It seems you would need to explicitly add boolProperty to scope.
<div ng-app="app" ng-controller="ctrl">
<directive bool="boolProperty">
<p>boolProperty: {{boolProperty|json}}</p>
</directive>
</div>
JS
angular.module("app", []).controller("ctrl",function($scope){
$scope.boolProperty = false;
}).directive("directive", function() {
return {
restrict:"E",
scope: {boolProperty:'=bool'},
link: function($scope) {
$scope.boolProperty = "i'm a boolean property";
}
};
});
Here's updated fiddle.

angularjs custom directive isolated scope one way data binding doesn't work

i am a new to angularjs, I read some literature and followed a lot of tutorials, but i am still have the feeling that i completely confused.
My current issue is with custom directive and isolated scopes. All i trying to do is pass "strings" with # binding to my directives that use isolated scopes and I can't understand what am i doing wrong. Specifically WHY when i use template everything just works fine and when the template already in the DOM the one way data binding doesn't work.
JSBin fiddle link
major parts from my code:
HTML
<div my-directive my-title="TITLE ONE WAY Data Binding">
<div>
<div>This directive is <span style="color:red;">NOT using template</span></div>
<div>
$scope.title = <small><pre>{{title}}</pre></small>
</div>
</div>
</div>
<div my-directive-with-template my-title="TITLE ONE WAY Data Binding"
>
<!-- this directive use a template -->
</div>
JS
var app = angular.module('app', []);
app.directive('myDirective', function() {
return {
restrict: 'AE',
scope:{
title: "#myTitle"
},
link: function(scope, ele, attrs, c) {
console.log('non template directive link:',scope.title,attrs.myTitle);
},
controller:['$scope', function($scope){
console.log('non template directive controller:',$scope.title);
}]
};
});
app.directive('myDirectiveWithTemplate', function() {
return {
restrict: 'AE',
scope:{
title: "#myTitle"
},
link: function(scope, ele, attrs, c) {
console.log('using template directive link:',scope.title,attrs.myTitle);
},
controller:['$scope', function($scope){
console.log('using template directive link:',$scope.title);
}],
template:'<div><div>This directive is using template</div><div>$scope.title = <small><pre>"{{title}}"</pre></small></div></div>',
replace:true
};
});
JSBin fiddle link
In your non-template scenario the title is not being bound to any scope and therefore not showing anything.
What you call the DOM template is really HTML outside the directive that has no access to it's isolated scope. You could embed this div inside a controller and then title could be bound to the controller's $scope.title
For what I understand it only makes sense to create an isolated scope to make it available to the directive's template.
Clarification
Isolated scopes allow the directive to have state independent of the parent scope (avoiding it's pollution) and also avoiding sharing this state with sibling directives.
Supposing you're creating this directive to reuse that piece of UI somewhere else in your code, you start by creating its template with the shared HTML.
Ok, but you need to go a bit further and parameterize it passing some data to it.
You can then use attributes on the directive to communicate with the outside (parent scope, or just to pass static data).
The directive's template can now bind to this data without needing to have any knowledge of it's "outside world", and it's done through it's isolated scope.
Conclusion, why create an isolated scope, if not to provide the template with this data?
Hope I've made this a bit clear :)
Now after thinking a bit about my affirmation... well you could also create a directive without any template, by using the compile or link function and do it manually through DOM manipulation. And in this case it might make sense to have an isolated scope for the reasons presented above :)

AngularJS directive transclude part binding

I'd like to use a directive, transclude content, and call directive's controller method within the transcluded part:
<mydirective>
<div ng-click='foo()'>
click me
</div>
</mydirective>
app.directive "mydirective", ->
return {
restrict: 'EACM',
transclude: true
template: "<div ng-transclude></div>"
scope: { } #required: I use two way binding on some variable, but it's not the question here
controller: [ '$scope', ($scope)->
$scope.foo = -> console.log('foo')
]
}
plunkr here.
How can I do that please?
I have a different answer, which is not a hack and I hope it will be accepted..
see my plunkr for a live demo
Here is my usage of the directive
<div custom-directive custom-name="{{name}}">
if transclude works fine you should see my name right here.. [{{customName}}]
</div>
Note I am using customName within the directive and I assign it a value as part of the directive's scope.
Here is my directive definition
angular.module('guy').directive('customDirective', function($compile, $timeout){
return {
template : '<div class="custom-template">This is custom template with [{{customName}}]. below should be appended content with binding to isolated scope using the transclude function.. wait 2 seconds to see that binding works</div>',
restrict: 'AC',
transclude: true,
scope : {
customName : '#'
},
link : function postLink( scope, element, attrs, dummy, transcludeFn ){
transcludeFn( scope, function(clone, innerScope ){
var compiled = $compile(clone)(scope);
element.append(compiled);
});
$timeout( function(){
scope.customName = 'this stuff works!!!';
}, 2000);
}
}
});
Note that I am changing the value on the scope after 2 seconds so it shows the binding works.
After reading a lot online, I understood the following:
the ng-transclude directive is the default implementation to transclusion which can be redefined per use-case by the user
redefining a transclusion means angular will use your definition on each $digest
by default - the transclusion creates a new scope which is not a child of the isolated scope, but rather a sibling (and so the hack works). If you redefine the transclusion process you can choose which scope is used while compiling the transcluded content.. -- even though a new scope is STILL created it seems
There is not enough documentation to the transclude function. I didn't even find it in the documentation. I found it in another SO answer
This is a bit tricky. The transcluded scope is not the child of the directive scope, instead they are siblings. So in order to access foo from the ng-click of the transcluded element, you have to assign foo to the correct scope, i.e. the sibling of the directive scope. Be sure to access the transcluded scope from the link function because it hasn't been created in controller function.
Demo link
var app = angular.module('plunker', []);
app.directive("mydirective", function(){
return {
transclude: true,
restrict: 'EACM',
template: "<div> {{ name }} <br/><br/> <div ng-transclude> </div></div>",
scope: { },
link: function($scope){
$scope.name = 'Should change if click below works';
$scope.$$nextSibling.foo = function(){
console.log('foo');
$scope.name = 'it works!';
}
}
}
})
Another way is assigning foo to the parent scope because both prototypally inherits from the parent scope, i.e.
$scope.$parent.foo = ...
Technically, if you remove scope: { }, then it should work since the directive will not create an isolated scope. (Btw, you need to add restrict: "E", since you use the directive as element)
I think it makes more sense to call actions defined in parent scope from directive rather than call the actions in the directive from parent scope. Directive should be something self-contained and reusable. The actions in the directive should not be accessible from outside.
If you really want to do it, you can try to emit an event by calling $scope.$broadcast(), and add a listener in the directive. Hope it helps.

How can I inherit complex properties from the parent scope into my directive's isolated scope

After reviewing AngularJS (and related) documentation and other stackoverflow questions regarding isolated scopes within directives, I'm still a little confused. Why can't I do a bi-directional binding between the parent scope and directive isolated scope, where the parent scope property is an object and not an attribute? Should I just use the desired property off scope.$parent? That seems wrong. Thanks in advance for your help.
The related fiddle is here.
HTML:
<div ng-app="myApp">
<div ng-controller="myCtrl">
<div my-directive>{{test.name}}</div>
</div>
</div>
JavaScript:
var myApp = angular.module('myApp', []);
myApp.controller('myCtrl', function ($scope) {
$scope.test = {name:"name", value:"value"};
});
myApp.directive("myDirective", function () {
return {
replace: true,
restrict: 'A',
scope: {test: '='},
template: '<div class="parent"><div>This is the parent Div.</div><div>Value={{test}}</div></div>',
link: function (scope, element, attrs) {
console.log("scope.test=["+scope.test +"]");
console.log("scope.$parent.test=["+scope.$parent.test.name+"]");
}
};
});
For directives using an isolate scope, attributes are used to specify which parent scope properties the directive isolate child scope will need access to. '=' provides two-way binding. '#' provides "one-way strings". '&' provides one-way expressions.
To give your directive (two-way binding) access to parent scope object property test, use this HTML:
<div my-directive test="test"></div>
It might be more instructive to use different names:
<div my-directive some-obj-prop="test"></div>
Then in your directive:
scope: { localDirProp: '=someObjProp'},
template: '<div ...>Value={{localDirProp}}...',
Isolate scopes do not prototypically inherit from the parent scope, so it does not have access to any of the parent scope's properties (unless '#' or '=' or '&' are used). Using $parent is a way to still access the parent scope, but not via prototypical inheritance. Angular creates this special $parent property on scopes. Normally (i.e. best practice), it should not be used.

Resources