Bind to attributes in prototypically inherited scope - angularjs

I'd like to create a directive, with a prototypically inherited scope (i.e. scope=true), but also set up scope binding to attributes, similar to following when setting up an isolated scope:
scope = {
'varname':'=attrname'
}
My current solution is to set scope=true and set up the binding to the attributes along the following lines in the link function:
scope.$watch(element.attr('attrname'), function(val) { scope.varname =
val; }); // watch changes scope.varname =
scope.$eval(element.attr('attrname')); // initialize
Although it does the trick it doesn't seem to be very elegant. What approach do you suggest?
I find it surprising angularjs seems to expect that in a directive you wouldn't need attribute binding when setting up a new inherited scope.

I know what you mean and I agree with you. It would be nice if Angular provided a convenient framework for a directive to set-up two-way model binding between a parent scope variable and a child scope variable and still support prototypical scope inheritance.
To achieve the same effect as the following with isolated scope:
scope = {'varname':'=attrname'}
you can set up the two-way model binding within your directive's link function:
scope: true,
link: function(scope, element, attr) {
// when attrname changes in parent scope, update varname in current scope
scope.$parent.$watch(attr.attrname, function(newVal) {
scope.varname = newVal;
});
// when varname changes in current scope, update attrname in parent scope
scope.$watch('varname', function(newVal) {
scope.$parent[attr.attrname] = newVal;
});
}

Given what you want to achieve I consider your solution totaly valid. You could still improve your code if you use $scope.$observe() instead of $scope.$watch():
scope.$observe('attrname', function(val) {
scope.varname = $parse(val)(scope);
});

I think what you're looking for is this code in the directive link function:
var yourVar = $parse(attrs.attrName)(scope); // Parse the attribute with a specific scope context
You'll then pass attributes like you did with isolated scope directive, while still being able to use a prototypically inherited scope.
If you find yourself wanting to observe the changes of the variable, then I believe that you should use isolated scope and not inherited. It's still doable by observing attribute changes like suggested below, however I don't think it's the best practice. (I don't have much to support that claim, only intuition, so pick whatever you think is best)
If you want to know how $parse works, you can find more info here:
https://docs.angularjs.org/api/ng/service/$parse
Please let me know in the comments if you need a plunker also, or if you got the hang of it.

Related

Priority between custom and built-in directve

I am reading ng-book-r27.
There are some something i can't understand well.
About 'Scope Option' of The chapter 'Directives Explained'.
First confusion:
If multiple directives on an element provide an isolate scope, only
one new scope is applied. Root elements within the template of a
directive always get a new scope; thus, for those objects, scope is
set to true by default.
I think that mean other directives will use the isolate scope as theirs.
is that right?
Second confusion:
example of inherited scope
ng-init has higher priority than custom directive.
Why the expression of ng-init will use the scope of custom dirctive.
I find a recommendation from offical doc about ng-init:
This directive can be abused to add unnecessary amounts of logic into
your templates. There are only a few appropriate uses of ngInit, such
as for aliasing special properties of ngRepeat, as seen in the demo
below; and for injecting data via server side scripting. Besides these
few cases, you should use controllers rather than ngInit to initialize
values on a scope.
OK, I can ignore the strange behavior of second confusion.
I have not read the book ng-book, but as far as I know, the statement of your first confusion does not conform with the AngularJS documentation regarding inherited and isolated scopes within a directive.
The statement above is simply not possible, having multiple directives that each have their own isolated scope would produce a $compile:multidir error. Here is a DEMO.
.directive('elem1', function($rootScope) {
return {
restrict: 'A',
scope: {}
};
})
.directive('elem2', function() {
return {
restrict: 'A',
scope: {}
}
});
After scanning the AngularJS documentation, there is no supporting statement that validates the statement:
If multiple directives on an element provide an isolate scope, only
one new scope is applied.
The closest statement I see that is similar to the statement above is the scope: true option definition when creating directives:
true: A new child scope that prototypically inherits from its parent
will be created for the directive's element. If multiple directives on
the same element request a new scope, only one new scope is created.
The new scope rule does not apply for the root of the template since
the root of the template always gets a new scope.
The statement above suggests that when multiple directives with scope: true option (not an isolated scope) resides in a single element, it would create one scope and everything else is a shared scope. DEMO
.directive('elem1', function($rootScope) {
return {
restrict: 'A',
scope: true,
link: function(scope) {
console.log(scope.hello);
}
};
})
.directive('elem2', function() {
return {
restrict: 'A',
scope: true,
link: function(scope) {
scope.hello = 'world';
console.log(scope.hello);
}
}
});
You would notice that both directives logs 'world', this obviously supports the statement above.
If you read more in the $compile scope AngularJS documentation, you would see that:
In general it's possible to apply more than one directive to one
element, but there might be limitations depending on the type of scope
required by the directives. The following points will help explain
these limitations. For simplicity only two directives are taken into
account, but it is also applicable for several directives:
no scope + no scope => Two directives which don't require their own scope will use their parent's scope
child scope + no scope => Both directives will share one single child scope
child scope + child scope => Both directives will share one single child scope
isolated scope + no scope => The isolated directive will use it's own created isolated scope. The other directive will use its parent's scope
isolated scope + child scope => Won't work! Only one scope can be related to one element. Therefore these directives cannot be applied to the same element.
isolated scope + isolated scope => Won't work! Only one scope can be related to one element. Therefore these directives cannot be applied to the same element.
Perhaps you're having problems identifying the difference between prototypically inherted scopes and isolated scopes. You might want to read the $rootScope.Scope $new() method, the isolate parameter definition.
First question:
I think that mean other directives will use the isolate scope as
theirs. is that right?
The answer is an absolute no, in reference towards multiple directives with isolated scope, it would produce the $copile:multidir error.
For your second question:
ng-init has higher priority than custom directive. Why the expression
of ng-init will use the scope of custom directive?
As for directives that do not have isolated scopes or directives that don't prototypically inherit from their parent scope, you can directly associate those directives as having a scope property definition with a falsey scope value:
falsy: No scope will be created for the directive. The directive will
use its parent's scope
If a directive is bound to an element with a scope of its own then it simply uses the scope of that element, otherwise it seeks all the scope instances within the scope chain until it reaches the $rootScope.

Is there a way to get a scope of a DOM element when debug info is disabled?

I'm writing an directive which need to retrieve a scope of current DOM element. using the non public api angular.element().scope();
It works well until angular 1.3 introduces a new feature $compileProvider.debugInfoEnabled(false); which mainly aims to improve performance to avoid bind data in DOM element. But when debugInfoEnabled() is set to false, angular.element().scope() will return undefined. So I must find another way to get the scope of an DOM element or I have to redesign my code logic.
Is there a way to make this possible?
I just faced a similar problem in our application after compiling our app with $compileProvider.debugInfoEnabled(false);. I needed to later access some of our directive's isolate scope but couldn't use the isolateScope() method. To get around the problem, I created a helper function in a Utils service that looks like this:
this.setElementIsolateScope = function(element, scope) {
element[0].isolateScope = function() {
return scope;
};
};
Then inside any directive where I needed to be able to later access the isolate scope I called this function inside the link() function: Since element is a jqLite object, you need to set the isolateScope() function on element[0]. You should already have the jqLite wrapped element and scope already passed into your link function, which you then just pass to your service method.
Utils.setElementIsolateScope(element, scope);
To then access the isolate scope later, you would get a reference to your element and then do this (assuming child_element is the reference to your element/directive):
var child_iso_scope = _.isFunction(child_element.isolateScope) && child_element.isolateScope();
Depending on how you are getting the reference to your element, you may need to wrap it a jqLite wrapper like this:
child_element = angular.element(child_element);
And then just use the same way as above to get the isolate scope. Hope this helps!

Why use scope in directive and how to use it?

I see so many examples https://docs.angularjs.org/guide/directive and What is the difference between & vs # and = in angularJS but even I don't understand the scope principe in a directive. It's very confusing to use this. Some examples make use of scope:true; and
scope: {
sourceObj: '=',
lookupSource: '#',
searchRes: '&',
disableSearch: ''
}
What is using a boolean value (scope:true;) doing exactly?
scope:true
If you set scope:true (instead of scope: { ... }) then prototypical inheritance will be used for that directive.
This is not something AngularJS is doing – this is how JavaScript prototypal inheritance works.
example
scope:false (default)
the directive does not create a new scope, so there is no inheritance here. This is easy, but also dangerous because, e.g., a directive might think it is creating a new property on the scope, when in fact it is clobbering an existing property. This is not a good choice for writing directives that are intended as reusable components.
example
scope: {......}
the directive creates a new isolate/isolated scope. It does not prototypically inherit. This is usually your best choice when creating reusable components, since the directive cannot accidentally read or modify the parent scope.
Notice, even the parent scope has a name “Harry”, the textbox inside directive is blank. This is because of the new Isolated scope doesn’t know anything about its parent scope.
example
Notice, even the parent scope has a name “Harry”, the textbox inside directive is blank. This is because of the new Isolated scope doesn’t know anything about its parent scope.
NOTE: I dont want now post diferences between scope properties (#,&,=) , it will be another lesson
from this source
It's all about whether or not you want to create an isolated (new) scope for your directive or you want to inherit the from the parent scope. Specifying scope: true is essentially the same as scope: {}. It just says, 'hey, I want my own private scope here'. The only difference between those two is that you can use the object notation to specify your own scope properties to use in your directive.

Can I replace scope for isolated scope?

I have directive in which I have scope mentioned as :
return {
restrict: 'AE',
transclude: true,
scope: {
model: '=',
menu: '='
},}
so my question is apart from model and menu variables what all variables are accessible to me? Does Isolated scope inherits parent scope variables ?
If I don't want to use isolated scope then what could be done where I can set these two variables in directives and inherits all variables from parent scope ?
Thank you.
If I understood correctly, what you want to achieve is a new scope inheriting all the parent stuff. That is easy to do.
By default a directive uses the parent scope, but you can do two different things:
scope: {} // Isolated scope
or:
scope: true // new scope inheriting from parent
So with the later, you will have all the parent scope has but you can set new stuff there and the parent won't know. Example:
http://plnkr.co/edit/oL5ALPvkEzkiXSuOSNnE?p=preview
I hope this is what you asked for.
EDIT: I edited the plunker. The idea with new scopes that inherits from others is:
If parent has a primitive like name the child will inherit it, but if you do something like:
childScope.name = ".." you are not modifying the parent name, you are shadowing it, AKA creating a new name that will hide the parent name. this means that the parent will never know if the child modified the name.
foo is something created in the new child scope, that means that the parent will never know.
user is not a primitive, it is an object and when the child modifies its name, it is not shadowing the entire user, it is just modifying its value:
childScope.user.name = "Fox"
This is getting the user reference and modifying its value, is not modifying the entire user, so you are not shadowing it. If you do:
childScope.user = { name: 'Foo' };
Then you're creating an entire user, AKA you're creating a new reference so that will shadow the parent user and the parent-child relationship of that object will end.
This is what we call the dot rule.
If the scope property of the directive definition is set to an { /* object hash */ } an isolated scope is created for your directive. If you set the scope property to true a new scope is generated that inherits from the parent scope.
See this plunker for a demo on directive scope behaviour.
If you don't want to isolate the scope but do want to pass argument to the directive you can use the $observe method of the attributes service.
function linkingFn(scope, elm, attrs, ctrl) {
// get the attribute value
console.log(attrs.ngModel);
// change the attribute
attrs.$set('ngModel', 'new value');
// observe changes to interpolated attribute
attrs.$observe('ngModel', function(value) {
console.log('ngModel has changed value to ' + value);
});
}
Creating a two way binding without an isolated scope can be done with the $parse service.
when you set scope like this, it's an isolated scope, and as its name says, it doesn't inherit any property from parent scope.
However, you can directly define variable on the scope, in the directive link method, like this:
.directive('someDirective', function(){
return {
restrict: 'C',
link: function(scope, element, attrs){
scope.newVariable = "newVariable"
}
}
}
it adds newVariable to your scope.Hope it helps~
More info on directive can be seen here, http://docs.angularjs.org/guide/directive. There is a section with the name Creating a Directive that Manipulates the DOM

Need some examples of binding attributes in custom AngularJS tags

I am attempting to create a custom tag similar to the following:
<mytag type="Big" />
where type is an attribute that gets bound to the component. in such a way that it sets the text in a label, as shown below:
<label>{{type}}</label>
... (other components)...
As the documentation says, I have a controller that sets a default type:
$scope.type = "Small";
so that if I use my tag without the attribute type still gets set.
I am attempting to do binding using a directive:
angular.module('TestPage',[])
.directive('mytag',function() {
return {
restrict: 'E',
templateUrl: 'component.html',
scope: {
type: '='
}
}
});
Note that I do have the appropriate ng-app settings in my component template (ng-app="TestPage").
My problem is that the binding to type does not appear to be actually binding anything.
I have read the documentation about how to bind a variable to components using directive. According to the documentation, you can do such bindings inside a scope. Scopes apparently can contain an "object-hash" (whatever that is!) which creates something called an "isolate scope" (???). Such scopes can represent "local properties" in the following ways:
# or #attr - bind a local scope property to the DOM attribute. The result is always a string
since DOM attributes are strings. If no attr name is specified then the local name and
attribute name are same. Given and widget definition of scope: { localName:'#myAttr' }, then widget scope property localName will reflect the interpolated value of hello {{name}}. As the name attribute changes so will the localName property on the widget scope. The name is read from the parent scope (not component scope).
Huh??? What has all this to do with the proper syntax for binding?
= or =expression - set up bi-directional binding between a local scope property and the parent
scope property. If no attr name is specified then the local name and attribute name are same.
Given and widget definition of scope: { localModel:'=myAttr' }, then widget scope property localName will reflect the value of parentModel on the parent scope. Any changes to parentModel will be reflected in localModel and any changes in localModel will reflect in parentModel.
Excuse me? What is being said here???
& or &attr - provides a way to execute an expression in the context of the parent scope. If no
attr name is specified then the local name and attribute name are same. Given
and widget definition of scope: { localFn:'increment()' },
then isolate scope property localFn will point to a function wrapper for the increment() expression. Often it's desirable to pass data from the isolate scope via an expression and to the parent scope, this can be done by passing a map of local variable names and values into the expression wrapper fn. For example, if the expression is increment(amount) then we can specify the amount value by calling the localFn as localFn({amount: 22}).
Now I'm totally confused! You have widget tags and some kind of related function that I have to write iin order to do the bind??? All I want is to bind a value to a label tag!
I have copied the above text from the documentation (http://docs.angularjs.org/guide/directive) to make a point: that this doco reads like the old UNIX documentation: really useful to those who already know the system, but not so helpful to beginners who are trying to develop real expertise. With all the tutorials that show how to do simple tasks in AngularJS (great for toy apps but not so good for the kinds of client- side applications I want to build), why aren't there any for the more advanced stuff???
Okay, time for me to be more constructive.
Can someone please provide some nice, simple examples of how to do the various bindings that this documentation is trying so hard to describe??? Examples that show the proper syntax for these scope statements and descriptions (in plain English) of exactly how they go back to the attribute being added to the custom tag???
Thank you for your patience and thanks in advance for any assistance.
I struggled a bit with this documentation too when first getting into angular, but I will make an attempt try to clarify things for you. First, when using this scope property, it creates an "isolated scope." All this means is that it won't inherit any properties from parent scopes, and so you don't have to worry about any collisions within the scope.
Now, the '#' notation means that the evaluated value in the attribute will automatically get bound into your scope for the directive. So, <my-directive foo="bar" /> would end up with the scope having a property called foo that holds the string "bar". You could also do something like <my-directive foo="{{bar}}" And then the evaluated value of {{bar}} will be bound to the scope. Since attributes are always strings, you will always end up with a string for this property in the scope when using this notation.
The '=' notation basically provides a mechanism for passing an object into your directive. It always pulls this from the parent scope of the directive, so this attribute will never have the {{}}. So, if you have <my-directive foo="bar" /> it will bind whatever is in $scope.bar into your directive in the foo property of your directive's scope. Any change's you make to foo within your scope will be refelected in bar in the parent scope, and vice versa.
I haven't used the '&' notation nearly as much as the other too, so I don't know it as well as those two. From what I understand, it allows you to evaluate expressions from the context of the parent scope. So if you have something like <my-directive foo="doStuff()" />, whenever you call scope.foo() within your directive, it will call the doStuff function in the directive's parent scope. I'm sure there's a lot more you can do with this, but I'm not as familiar with it all. Maybe someone else can explain this one in more detail.
If just the symbol is set in the scope, it will use the same name as the attribute to bind to the directives scope. For example:
scope: {
foo1: '#',
foo2: '=',
foo3: '&'
}
When including the directive, there would need to be the attributes foo1, foo2, and foo3. If you want a property in your scope different than the attribute name, you can specify that after the symbol. So, the example above would be
scope: {
foo1: '#bar1',
foo2: '=bar2',
foo3: '&bar3'
}
When including the directive, there would need to be the attributes bar1, bar2, and bar3, and these would get bound in the scope under properties foo1, foo2, and foo3 respectively.
I hope this helps. Feel free to ask questions with which I can clarify my answer.
Youre pretty close..
app.directive('mytag',function() {
return {
restrict: 'E',
template: '<div>' +
'<input ng-model="controltype"/>' +
'<button ng-click="controlfunc()">Parent Func</button>' +
'<p>{{controlval}}</p>' +
'</div>',
scope: {
/* make typeattribute="whatever" bind two-ways (=)
$scope.whatever from the parent to $scope.controltype
on this directive's scope */
controltype: '=typeattribute',
/* reference a function from the parent through
funcattribute="somefunc()" and stick it our
directive's scope in $scope.controlfunc */
controlfunc: '&funcattribute',
/* pass a string value into the directive */
controlval: '#valattribute'
},
controller: function($scope) {
}
};
});
<div ng-controller="ParentCtrl">
<!-- your directive -->
<mytag typeattribute="parenttype" funcattribute="parentFn()" valattribute="Wee, I'm a value"></mytag>
<!-- write out your scope value -->
{{parenttype}}
</div>
app.controller('ParentCtrl', function($scope){
$scope.parenttype = 'FOO';
$scope.parentFn = function() {
$scope.parenttype += '!!!!';
}
});
The magic is mostly in the scope: declaration in your directive definition. having any scope: {} in there will "isolate" the scope from the parent, meaning it gets it's own scope... without that, it would use the parent's scope. The rest of the magic is in the scope's properties: scope: { 'internalScopeProperty' : '=externalAttributeName' }... where the = represents a two way binding scenario. If you change that = to a # you'll see it just allows you to pass a string as an attribute to the directive. The & is for executing functions from the parent scope's context.
I hope that helps.
EDIT: Here is a working PLNKR

Resources