angular js: programmatically instantiate a scope? - angularjs

Simple case: Ask a controller $scope to create a child scope. This new scope would be applied to a $compile's linking function -- i.e. programmatic directive instantiation.
My need falls just outside the scope: true declaration in the directive definition -- I do need a private directive scope but I don't want one created by the framework every time I instantiate the directive. Rather, I'd like to reapply the existing scope to a fresh linkage -- a fresh directive.
IOW, I want to teach a new dog old tricks.
I'm talking about the scope obtained from the compiled directive (see: Retrieving Scopes from the DOM).
Consider the scenario where the HTML representation of the directive (let's call it "half") may move in and out of and around the document. I merely want to save its state (let's call it "the other half") off and reapply it to a fresh half-compiled directive instance.
Scope hierarchy would be respected, i.e. this new directive instance would nestle itself in the same Angular-DOM area as it did in a former life so I don't think any worm holes would be opened nor anti-matter created.
A super-contrived plunk for your viewing pleasure.

If you want create private scope inside directive you can use scope.$new method like that:
app.directive('colorblock', function ($rootScope) {
link: function (scope, iElement, iAttrs, controller) {
var privateScope = $rootScope.$new(true);
}
...
});
It will create isolate scope.

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.

Find other element with same directive within same scope?

Is there any way to find other element with same directive applied within same scope?
I want to use this info to create matching input values directive.
e.g. I might have a template with a few inputs which all using same directive:
<div ng-controller="MainCtrl">
<input type="text" same-value>
<input type="text" same-value>
</div>
Primarily I would use this for comparing password & password repeat, but I wanted to make this more general directive.
You can create a parent directive and communicate between children and parent via the controller or you can create a service that is shared across instances.
Another option is to provide shared data and functionality when the directive is declared, as in:
angular.module("whatever").directive("sameValue", function() {
var sameValueInstances = [];
return {
link: function(scope, elem, attr) {
sameValueInstances.push(scope);
// register listener to remove from array when destroyed..
// do something with the instance array
sameValueInstances.forEach(...);
}
};
});
There is more than one instance of the directive, but there is only one instance of the declaration of the directive (the function itself). The declaration is shared and therefore sameValueInstances are shared. When angular instantiates a directive, it will create a new scope and cal the link function. Each instance gets its own scope, which is why we put the scopes on the instances. We then use whatever data we add to the scope to identify instances and can use functions on scope to communicate.

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.

Angular.js how to build dynamic template parts using directives with isolated scope

I've got a directive... like so:
.directive('formMenuBuilderMenu', function (formMenuService) {
return {
templateUrl: '../../views/templates/formmenubuilder-menu-template.html',
restrict: 'A',
scope:{
menu:'='
},
link: function postLink(scope, element, attrs) {
// does stuff
} ...
It gets built dynamically using $compile whenever a new menu node is created.
scope.menu = {//new data for menu view directive part}
var $nodeTemplate = '<div form-menu-builder-menu menu="menu"></div>';
var html = $compile($nodeTemplate)(scope);
$content.append(html);
I had the impression that because I've defined a scope section in the formMenuBuilderMenu directive that this directive would have isolate scope and not be affected by new instances created
BUT this doesn't work at all!
What happens is that every time a new directive is created using $compile the scope.menu gets updated using the new value for all previous directives created, not keeping its isolated scope. Indeed logging out the scope in each directive created shows it's the same scope instance every time.
How would I do this so the directive scope remains independent and each instance has it's own scope? Is it even possible? Please let me know if further explanation is needed. I'm sure I'm going about this the wrong way so a pointer in the right direction would be appreciated.
To be clear my main objective is basically to be creating dynamic template parts using directives each with their own subset of data.
Instead of:
var html = $compile($nodeTemplate)(scope);
Try this:
var html = $compile($nodeTemplate)(scope.$new());

Modify scope within link function in AngularJS

Normally in all examples/source code of AngularJS modifications of scope is done in controllers. In my directive I need to get some information from another directive (or it's scope) and put it into scope (so visible in template of directive). As this information is common for all instances of this directive, using scope binding does not sound good for me.
So the only solution I found is to modify instance scope in linking function:
link: function(scope, element, attr, parentCtrl) {
scope.data = parentCtrl.someData;
}
This solution works. Plnkr example
The question: Is it ok according to AngularJS philosophy/style to modify scope in linking function or there is another solution?
Since you are creating isolate scopes in your directives (in your example plnkr), and you want to allow for parents to be 'somewhere' in the scope hierarchy (according to your comment to #MathewBerg), I believe your only option is to use the linking function to modify the scope.
(I suppose you could define methods on your MainCtrl that only the child directives should call, but enforcing that would be messy and break encapsulation).
So, to echo what #MathewBerg already said, yes, modify the scope in the directive/linking function.
Modifying scope in directives is fine. As for sharing information between directives there are a few methods. One is the way you described, where you access the parents controller and get it's data, another very similar method would be to have
scope.data = scope.$parent.data;
Instead of
scope.data = parentCtrl.someData;
The general way to share stuff between directives though is to use a service. This would allow you to inject the service into each directive and they can share the values. The problem with your initial method (and the one that I've described) is that if you ever move the element around so that the hierarchy of scopes change, your code will break. This is why I would recommend using a service over both. I suggest reading up on the service docs. There's also plenty of videos out there describing how to set them up: http://www.youtube.com/watch?v=1OALSkJGsRw

Resources