Angular directive new scope rule - angularjs

The Angular directive documentation says: "If set to true, then a new scope will be created for this directive. 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."
My question is the last sentence. I assume that "template" refers to the directive's template, but on testing a simple directive whether it has or doesn't have a template, no new scope is created without setting "scope: true". Am I missing something here?

Perhaps the "root of the template" there means the root element which matches ngController directive. In the example below, the first and second myCustomer directives belong to the same "root template", but the third one is different.
<div ng-controller="Controller">
<my-customer></my-customer>
<div>
<my-customer></my-customer>
</div>
</div>
<div ng-controller="Controller">
<my-customer></my-customer>
</div>

Related

Showing or hiding the parent element of a custom directive

I have a query concerning a custom directive example found within Angular's docs here.
When the select method is called (from the parent controller in order to show / hide a pane), the relevant <div class="tab-pane" ng-show="selected"> element is shown / hidden, which is how it should function as per the example, naturally.
However, I'd like to hide the parent element, which is <my-pane title="..."> so that <my-pane title="..."> isn't left visible (even though all it's child content is hidden.) In other words, move the ng-show directive from <div class="tab-pane" ng-show="selected"> to <my-pane title="..." ng-show="selected">
I assumed that each <my-pane title="..."> had its own isolated scope with a unique $scope.id, so it should be easy to accomplish by targetting the relevant scope and updating a selected value via the tab links, but for the life of me I cannot seem to make it work.
After inspecting both <my-pane title="...">'s in console it appears as they both have the same $scope.id, which shouldn't be the case as they're separate scopes, right?
Something is missing from my understanding of isolated scopes perhaps. Any pointers would be great.

Why must I set "trasnclude: true" in an Angular DDO?

I know what transclude: true does, but I've always wondered: "why must I put transclude: true in my DDO as well as a ng-transclude in my template?".
What's going on internally that forces angular to be redundant? Is it security/XSS protection? Performance?
The ngTransclude documentation explains the separation of the two (emphasis mine):
[ngTransclude is a] directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
This means:
transclude: true indicates that the directive makes its content available for transclusion.
ng-transclude indicates where the content should go.
A directive with transclusion enabled doesn't actually have to handle the transclusion of it's own content. It can let a child element choose where to put the transcluded content.
Here is a (trivial) example that shows how transclusion can be handled by a child directive inside a parent:
<!-- Application template -->
<parent-el>
<h1>Transcluded content</h1>
</parent-el>
<!-- <parent-el> template -->
<p>I am the parent element</p>
<child-el></child-el>
<!-- <child-el> template -->
<p>I am the child element</p>
<div ng-transclude></div>
This is how the content will then be rendered in the page:
<!-- Rendered content -->
<parent-el>
<p>I am the parent element</p>
<child-el>
<p>I am the child element</p>
<div>
<h1>Transcluded content</h1>
</div>
</child-el>
</parent-el>
There are three kinds of transclusion depending upon whether you want to transclude just the contents of the directive's element, the entire element or multiple parts of the element contents:
true - transclude the content (i.e. the child nodes) of the directive's element.
'element' - transclude the whole of the directive's element including any directives on this element that defined at a lower priority than this directive. When used, the template property is ignored.
{...} (an object hash): - map elements of the content onto transclusion "slots" in the template.
-- https://docs.angularjs.org/api/ng/service/$compile#transclusion

AngularJS : Correct usage of ng-model

I was reading Angular js docs when I came across the issues mentioned related to using ng-model with directives like ng-include , ng-switch , and ng-view.The reason mentioned was child scope and parent scope but I was not able to understand it completely.
Also it was mentioned that issue with primitives can be easily avoided by following the "best practice" of always have a '.' in your ng-model.
Here's the link
Can anyone please explain it in less-technical language?
ng-include, ng-switch and ng-if creates child scope.
It means that if you create
<div ng-controller="MyCtrl">
<div id="innerDiv" ng-if="!something">
<input ng-model="model">
</div>
</div>`
the model will be created in scope created by the #innerDiv (because of using ng-if). It might be problematic if you want to use model value in the controller, because it won't be possible (Parent scope has no access to properties of child scope!).
The solution is to use $parent.model in <input ng-model="model">. Then the property will be changed in parent's scope and that is what you usually want to achieve.
However using $parent might look not good for someone, so better solution is to create a named model in the controller. The you can use it and follow the rule "always have a '.' in your ng-model." Child scope created by ng-if has access to parrent scope, so he can use already defined $scope.model and change $scope.model.text property.
Controller:
$scope.model = {};
Html:
<div ng-controller="MyCtrl">
<div id="innerDiv" ng-if="!something">
<input ng-model="model.text">
</div>
</div>`
(But remember that if you not define $scope.model in the controller, it would behave like in first example).
If you are not sure that you are in the same scopes, you can check it by displaying scope's $id. Simply add in html {{$id}} above ng-if (or ng-include, ng-switch) and inside.
<div ng-controller="MyCtrl">
scope id: {{$id}}
<div id="innerDiv" ng-if="!something">
child scope id:{{$id}}
</div>
<div>scope id again {{$id}}</div>
</div>`
Some examples:
https://jsfiddle.net/La90btfh/3/

Variable value as directive/controller name inside template (with $compile/$interpolate)?

I am creating a directive in which template I need to use the a scope's variable value as the name of the directive (or alternatively controller) to load.
Say I have a directive widget that has a template called widget.html which looks like:
<div class="widget widget.type" {{widget.type}} ng-controller="widget.type">
<div class="navBar">
<div ng-include="widget.type + '-t.html'"></div>
<i class="fa fa-close"></i>
<hr>
</div>
<div ng-include="widget.type + '-f.html'"></div>
</div>
Now widget.type is not getting evaluated in the first line. It works fine for ng-include. Say widget.type's value is weather. The first line should then be interpolated first to look like (doesn't matter if class attribute, widget.type-attr or ng-controller is interpolated)
<div class="widget" weather>
and then compiled to include the weather directive.
How can I get widget.type interpolated in the template?
Not an option is to use ng-include to load the directive. I need to use one common template for the widget and want to add/override/extend the base directive with additonal functionality/Variables.
If this is not the way to achieve that, is there a way to extend a directive the OOP-way?
See the plunkr
You can only place interpolation expressions in text nodes and attribute values. AngularJS evaluates your template by first turning it into DOM and then invoking directive compilation, etc. If you try to place {{...}} instead of attribute name, you'll just end up with messed-up DOM.
If you really need to replace a whole directive based on $scope variable value, you'll need to create a directive for application of other directives and do some heavy lifting with $compile (you'll have to completely re-compile the template each time the value changes). I'd recommend trying to find other designs solving your situation before attempting this.
For adjusting your template based on element attributes, see this answer.

Can't put ng-controller on directive?

I have a very simple angular example at http://jsfiddle.net/7eL47/3/. The rendered output of the code shows "Foobar" on the page.
The template for this rendered output is:
<div ng-app="myApp" ng-controller="MenuController">
<unordered-list>
Foo{{foo}}
</unordered-list>
</div>
However, when I change the location of ng-controller to the unordered-list as shown below, "Foobar" no longer appears--it's just "Foo." The value of {{foo}} is never replaced with "bar".
<div ng-app="myApp">
<unordered-list ng-controller="MenuController">
Foo{{foo}}
</unordered-list>
</div>
Why don't I see "Foobar" still when I change the ng-controller directive to be on the unordered-list element?
In your first example, ngController is a parent to unordered-list. So it has visibility to foo.
Your second example:
<unordered-list ng-controller="MenuController">
Results in two sibling scopes each with a parent of ngApp.
Both your directive and the ngController directive use scope: true. scope: true causes a child scope to be created for that directive which inherits from the parent. Therefore you end up with sibling scopes.
Thus, in the second example, $scope.foo is not visible, since it's no longer on the scope unoderedList inherits from (but rather on a sibling scope).

Resources