AngularJS ─ can't get scope by link from the directive controller - angularjs

I have an example app with very simple structure (you may see it here: http://plnkr.co/edit/5VAqUQsqKFGoteahacR2?p=preview):
The file index.html includes template (app/templates/home.html), which, in turn, includes the directive's template:
<div class="included" ng-include="'app/templates/outer-directive-2.html'"></div>
It includes the next directive:
<p>This is the included file <b>app/templates/outer-directive-2.html</b></p>
<div inner2="context"></div>
The value of the param inner2 is the key for the $scope.contents object which is defined in the contentsCtrl controller:
app.controller('contentsCtrl', function($scope,$rootScope){
$scope.contents = {
context: "Context for investigations here."
}
});
This key is needed to extract the object field in the directive's script (app/scripts/directives/defaultDirective.js):
app.directive('inner2', function(){
return{
restrict: 'A',
replace: true,
scope: {
inner2: '#',
contents: '&'
},
templateUrl: 'app/templates/inner-directive-2.html',
controller: function($scope){
$scope.getStuff = function(){
console.log('$scope.inner2, $scope.contents', {
// returns the key "context"
'$scope.inner2':$scope.inner2,
// returns function (???)
'$scope.contents':$scope.contents,
// returns "undefined"
'$scope.context':$scope.contents[$scope.inner2]
});
}
}
};
});
The content of that last folded directive (app/templates/inner-directive-2.html) is very simple:
<div class="included" title="{{inner2}}">
<p>Hello, I am the inner directive 2</p>
<span class="pseudolink" ng-click="getStuff()">Click me</span> and check console message!
</div>
So the idea is to get the $scope.contents[object_key] by calling getStuff().
But it can't see $scope.contents. I thought that it may be done by binding the isolated scope param (see above) contents to the outer scope:
scope: {
....
contents: '&'
},
...but it doesn't return the scope object, it returns function instead. Probably something is wrong here.
The questions are:
1. Why function and where it comes from?
2. May I get $scope.contents by some way and how?
http://plnkr.co/edit/5VAqUQsqKFGoteahacR2?p=preview

According to the angular`s docs:
& or &attr - provides a way to execute an expression in the context of the parent scope. If no attr name is specified then the attribute name is assumed to be the same as the local name. Given and the isolate scope definition scope: { localFn:'&myAttr' }, the isolate scope property localFn will point to a function wrapper for the count = count + value expression. Often it's desirable to pass data from the isolated scope via an expression 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})
so scope.contents in your directive will be a wrapper function.
You should either pass your contents object into directive scope or modify doStuff to execute it in the context of the parent scope.
Like in the doc (plese look at the example with increment(amount)) your doStuff should be in the parent scope. And from the directive you could pass locale variables

You are already getting scope contents. It's just that you didn't defined the default route to home rather it was set 404. Please check the updated plunker - http://plnkr.co/edit/BlXBqK?p=preview
Let me know if you are looking for something else.
app.config(function($stateProvider, $urlRouterProvider){
$urlRouterProvider.otherwise("/home");
$stateProvider
.state('home',{
url: "/home",
templateUrl: 'app/templates/home.html'
})
.state('error',{
url: "/404",
templateUrl: "app/templates/404.html"
});
});

Related

Along with Transclude element, can i pass its scope too to a directive?

Moment i felt i have understood enough about Transclude i came across this statement :
Transclude allows us to pass in an entire template, including its scope, to a directive.
Doing so gives us the opportunity to pass in arbitrary content and arbitrary scope to a directive.
Does this mean, if there is a scope attached to Transclude element and it can be passed on to the directive ? If that's true then am not able to access that scope property inside directive template.
Let me take couple of steps back and explain with code about what am trying to do :
JSFiddle Link
My directive is directive-box and transclude: true is defined in Directive Definition Object(DDO).
Now there is a Child Div, which is the element to be Transcluded
<div ng-controller='TransCtrl'>Inside Transclude Scope : {{name}}</div>
and it has controller TransCtrl attached to it.
Now am trying to access $scope.name property which is part of TransCtrl from directive level after defining this in DDO :
scope: {
title: '#directiveTitle',
name: '='
}
Is this possible ?
This is more like a Parent scope trying to access Child scope property, is this permitted in JavaScript Protoypical inheritance ? Or is there something else i need to know ??
If this is not possible what does first statement mean ?
Transclude allows us to pass in an entire template, including its scope, to a directive.
UPDATE 1 :
My primary concern is Controller should remain with Transclude element, still we should be able to pass its (Transclude element) scope to Directive and then Directive should be able to consume that scope i.e., name from TransCtrl controller .
<div ng-controller='TransCtrl'>Inside Transclude Scope : {{name}}</div>
Above line of code should remain as is.
I may be completely wrong with my question but please let me if this can be accomplished.
The problem seems to be with the way the controller is defined within the ng-transcluded html.
I have made it clearer by using
the bindToController construct
using a controller at the directive level
Refer this fiddle for a working example.
controllerAs: "TransCtrl",
bindToController: true
And your statement, 'Parent scope trying to access Child scope property' is incorrect right? Since we are trying to use the parent scope property, i.e. name from within the child (ng-transcluded content), which is possible with protypical inheritance, and not the other way around.
Does this answer your question: https://jsfiddle.net/marssfa4/4/?
In it I have created a new controller on the outside (effectively replacing the functionality of your rootScope for inside the directive) and I made the directive's controller be set inside your controller template.
The long and short of it is though that you can see that it is possible to transclude html along with its scope even into a directive with its own scope.
The html:
<div ng-app='myApp' ng-controller="OutsideScope">
<h1>{{externalWorld}}</h1>
<div directive-box directive-title='{{directiveWorld}}' name='name'>
<div>Inside Transclude Scope : {{name}}</div>
</div>
</div>
JS (includes Update 1):
angular.module('myApp', [])
.directive('directiveBox', function() {
return {
restrict: 'EA',
scope: {
title: '#directiveTitle',
name: '='
},
transclude: true,
template: '<div ng-controller="TransCtrl">\
<h2 class="header">{{ title }}</h2>\
<div class="dirContent">Directive Element</div>\
<div>Outside Transclude Scope : {{name}}</div>\
<div class="content" ng-transclude></div>\
</div>'
}
})
.controller('TransCtrl', function($scope) {
$scope.name = 'Transclude World'
})
.controller('OutsideScope', function($scope) {
$scope.name = 'External World'
})
.run(function($rootScope) {
$rootScope.externalWorld = 'External World',
$rootScope.directiveWorld = 'Here comes directive'
});
UPDATE 1: JSFIDDLE
I restored the original scope declarations as the scope: false was a mistake.
If I understand your comment correctly you want to leave the controller on the element to be transcluded but still have the {{name}} within that element ignore its immediate controller and use as controller its parent (i.e. the directive's) scope.
The reason I placed the controller within the template directive is because that is the only way to limit the directive's scope on the directive and not its transcluded elements. If you are explicitly placing a controller on an element, then regardless of whether it is contained within a directive with another scope, its closest scope will override whatever scope has been declared on the directive. In other words, regardless of what the directive's scope is, the {{name}} in
<div ng-controller='TransCtrl'>Inside Transclude Scope : {{name}}</div>
will always be whatever $scope.name is in TransCtrl.

Nested - transcluded items - scope clarification?

I already know how transclusion works ( within first level only I guess) , bUt I have a question about nested transcluded item's scope.
Ok so I have this code :
<body ng-app="docsTabsExample" ng-controller="ctrl">
<my-tabs>
<my-pane title="Hello">
<h4>Hello , The value of "i" is => {{i}}</h4>
</my-pane>
</my-tabs>
</body>
Basically I have a controller , <my-tabs> and <my-pane >.
Looking at myTabs directive :
.directive('myTabs', function()
{
return {
restrict: 'E',
transclude: true,
scope:
{},
controller: ['$scope', function($scope)
{
$scope.i = 2;
}],
template: '<div ng-transclude></div>'
};
})
I know that the content of the directive will have access to the outer directive's scope
So the yellow part will have access to the outer scope ( which is the main controller scope) :
Here is the code for myPane directive :
.directive('myPane', function()
{
return {
require: '^myTabs',
restrict: 'E',
transclude: true,
scope:
{
},
controller: function($scope)
{
$scope.i = 4; //different value
},
template: '<div ng-transclude></div>'
};
})
The program starts with :
.controller('ctrl', function($scope)
{
$scope.i = 1000;
})
The output of the program is :
Hello , The value of "i" is => 1000
But
According to the documentation : myPane's transcluded data should have access to the outer scope of the directive which is myTabs directive which has the value i=2.
But myPane has an isolated scope so it does NOT inherit the scope from myTabs.
Question
So does it goes one level more higher to the controller's scope in order to get i=1000 ?? (Clarification , I'm not asking how can I make i get another value - I'm asking why/how it has the value of 1000).
I mean how does the hierarchy of scope looks here?
Is it like this?
controller's scope
|
+--------+---------+
| |
myTabs's mypanes's
transcluded transcluded
data's scope data's scope
the docs says :
The transclude option changes the way scopes are nested. It makes it
so that the contents of a transcluded directive have whatever scope is
outside the directive, rather than whatever scope is on the inside. In
doing so, it gives the contents access to the outside scope.
But what scope does the outside of myPAne directive has ?
In other words , why/how does i=1000?
FULL PLUNKER
EDIT FROM OP AFTER ANSWER
After installing and configuring PeriScope ( from #MarkRajcok) I can now see it visually :
From the docs on $compile
When you call a transclude function it returns a DOM fragment that is
pre-bound to a transclusion scope. This scope is special, in that it
is a child of the directive's scope (and so gets destroyed when the
directive's scope gets destroyed) but it inherits the properties of
the scope from which it was taken.
Parent Hierarchy (from $$childTail) is like:
-1 (root)
--2 (ctrl)
---3 mytab
----4 ($$transcluded = true)
------5 mypane
--------6 ($$transcluded = true)
Prototypical Hierarchy is like (screenshot from AngularJS Batarang)-
Updated plunker with scope id's printed in console should give you a better idea.
Why these are different, I am not very sure. Someone can throw light on this.
Why the value is 1000. Its because i needs to be provided as a bidirectional attribute = so the child scopes can modify it. I have updated the above plunker, you can see now the value responds to change in pane controller.
More on transcluded scopes -
Confused about Angularjs transcluded and isolate scopes & bindings
https://github.com/angular/angular.js/wiki/Understanding-Scopes

Accessing ng-repeat scope on a custom directive

I'm having a go at a directive which will dynamically load a template based on a scope value passed into it.
I am using ng-repeat on my directive, and am using the iterated item as an attribute property:
<my-form-field ng-repeat="field in customFields" field="field">
In my directive I have the following code to set the template being used.
(function() {
'use strict';
angular
.module('app')
.directive('myFormField', myFormField);
function myFormField() {
var directive = {
restrict: 'E',
scope: {
field: '='
},
link: function(scope){
scope.getContentUrl = function() {
return 'app/modules/form_components/form_field/' + scope.field.type + '-template.html';
}
},
template: '<div ng-include="getContentUrl()"></div>'
};
return directive;
}
})();
Whilst the above works (which I found from other posts), I wonder if there is a better way.
For example I have seen examples of calling a function on the templateUrl config option instead, and then in that function access the scope attributes being passed in. When I tried this way, my field attribute was a literal 'field' string value (they are objects in my customFields array), so I think at that point the scope variables had not yet been evaluated.
With this current solution I am using, all of my templates get wrapped in an extra div since I am using ng-include, so I am just trying to make the rendered markup more succinct.
Any suggestions\advice is appreciated.
Thanks

Call method on Directive to pass data to Controller

So basically I have a controller, which lists a bunch of items.
Each item is rendering a directive.
Each directive has the ability to make a selection.
What I want to achieve is once the selection has been made, I want to call a method on the controller to pass in the selection.
What I have so far is along the lines of...
app.directive('searchFilterLookup', ['SearchFilterService', function (SearchFilterService) {
return {
restrict: 'A',
templateUrl: '/Areas/Library/Content/js/views/search-filter-lookup.html',
replace: true,
scope: {
model: '=',
setCriteria: '&'
},
controller: function($scope) {
$scope.showOptions = false;
$scope.selection = [];
$scope.options = [];
$scope.selectOption = function(option) {
$scope.selection.push(option);
$scope.setCriteria(option);
};
}
};
}]);
The directive is used like this:
<div search-filter-lookup model="customField" criteria="updateCriteria(criteria)"></div>
Then the controller has a function defined:
$scope.updateCriteria = function(criteria) {
console.log("Weeeee");
console.log(criteria);
};
The function gets called fine. But I'm unable to pass data to it :(
Try this:
$scope.setCriteria({criteria: option});
When you declare an isolated scope "&" property, angular parses the expression to a function that would be evaluated against the parent scope.
when invoking this function you can pass a locals object which extends the parent scope.
It's a common mistake to think that $scope.setCriteria is the same as the function inside the attribute. If you log it you'll see it's just an angular parsed expression function which have the parent scope saved at it's closure.
So when you run $scope.setCriteria() you actually evaluate an expression against the parent scope.
In your case this expression happens to be a function but it could be any expression.
But you don't have a criteria property on the parent scope, that's why angular let you pass a locals object to extend the parent scope. e.g. {criteria: option}
Extends the parent scope
you wrote in a comment that it requires the directive to have knowledge of the parameter name defined in the controller. No it doesn't, it just extends the parent scope with a criteria option, you can still use any expression you want though you are provided with an extra property you may use.
A good example would be ngEvents, take ng-click="doSomething($event)":
ngClick provides you with a local property $event, you don't have to use but you may if you need.
the directive doesn't know anything about the controller, it's up to you to decide which expression you write, cheers.
You can pass the function in using =...
scope: {
model: '=',
setCriteria: '='
},
controller: function($scope) {
// ...
$scope.selectOption = function(option) {
$scope.selection.push(option);
$scope.setCriteria(option);
};
}
<div search-filter-lookup model="customField" criteria="updateCriteria"></div>

Directive scope is not refreshing when provided value will be assign to directive scope

I created my own directive in angularjs and I noticed that directive scope is not refreshing when I change scope in main controller.
I made simple example which after 3s changing scope value, but the value is not changed in the directive. The issue exists only if I assign directive provided value to the directive scope.
Entire example is available here: jsfiddle
My directive:
myApp.directive('textPresenter', function() {
return {
transclude : false,
restrict: 'E',
scope: {
adfName : '='
},
template: '{{xxx}}' ,
controller: function($scope){
$scope.xxx = $scope.adfName; //this is the issue
}
};
});
$scope.xxx should refresh, after scope change in main controller.
The scope value adfName in the template is bound to the one in your main controller by Angular, and will change when that one changes. If you change the template to:
template: '{{adfName}}'
as in http://jsfiddle.net/6bp7c/1/ , then you can see this.
However, from your comment, you say you can't do this. Then one possibility would be setup a watcher in your directive, to watch adfName, and set xxx to be equal to it when it changes:
$scope.$watch('adfName', function(newAdfName) {
$scope.xxx = newAdfName;
});
as can be seen at http://jsfiddle.net/7V9FV/ . This is necessary because
$scope.xxx = $scope.adfName
just copies the contents of adfName and put it into xxx. If adfName later changes, then the contents of xxx are unaffected.
I am curious as to why you have to copy the variable though, and not just change the template to use the one passed into the directive.

Resources