angularjs nested directive scope isolation hiding parent directive object - javascript

I want to create nested directive that ordered top, parent and sub.
<div ng-app="app">
<top>
<parent>
<sub global-name="global"></sub>
</parent>
</top>
</div>
And my jsvascript is:
angular.module("app",[]);
angular.module("app").directive("top",function(){
return {
restrict: "E",
transclude: true,
template: "<div ng-transclude></div>"
}
});
angular.module("app").directive("parent", function(){
return {
restrict: "E",
controller: function($scope){
$scope.global = {
name: "parent directive"
};
},
link: function(scope){
},
transclude: true,
template: "<div ng-transclude></div>"
}
});
angular.module("app").directive("sub", function(){
return {
restrict: "E",
require:"^parent",
scope: {
global: "=globalName"
},
controller: function(){
},
link: function(scope){
scope.title = scope.global;
console.log(scope.global);
},
template: "{{global.name}}"
}
});
this is working. JSfiddle code is here. But;
if I isolate parent directive scope, I can not access the global object of parent from sub directive.
angular.module("app").directive("parent", function(){
return {
restrict: "E",
controller: function($scope){
$scope.global = {
name: "parent directive"
};
},
link: function(scope){
},
transclude: true,
template: "<div ng-transclude></div>",
scope: {}
}
});
This is not working. Jsfiddle is here.

Of course you can't, that's all the point of isolted scopes, since both your parent and sub directive have isolate scopes this doesn't work.
When you want to have 2 directive having a parent/child relationship, you use the parent's controller API to communicate with from the sub directive to the parent.
Check this Fiddle : https://jsfiddle.net/tp1pc31z/
angular.module("app").directive("parent", function(){
return {
restrict: "E",
controller: function($scope){
this.global = {name:"parent directive"};
},
link: function(scope){
},
transclude: true,
template: "<div ng-transclude></div>",
scope: {}
}
});
angular.module("app").directive("sub", function(){
return {
restrict: "E",
require:"^parent",
scope:{},
controller: function(){
},
link: function(scope, element, attr, parentCtrl){
console.log("parent : "+parentCtrl);
scope.title = parentCtrl.global;
console.log(scope.title.name);
},
template: "Global : {{title.name}}"
}
})

Here is a solution with a different approach - Fiddle
JS
angular.module("app",[]);
angular.module("app").directive("top",function(){
return {
restrict: "E",
template: "<parent></parent>"
}
});
angular.module("app").directive("parent", function(){
return {
restrict: "E",
controller: function($scope){
$scope.global = {
name: "parent directive"
};
},
link: function(scope){
},
template: "<sub global='global'></sub>",
scope: {}
}
});
angular.module("app").directive("sub", function(){
return {
restrict: "E",
scope: {
global: "="
},
controller: function(){
},
link: function(scope){
scope.title = scope.global;
console.log(scope.global);
},
template: "{{global.name}}"
}
});
Markup
<div ng-app="app">
<top>
</top>
</div>

Related

Nested directives not inheriting parent scope properly

Having a problem with nested directives and inheriting the scope from a parent. It happens that the scope I want to inherit from is an isolated scope. I've the code below
test.html
<div ng-controller="testCtrl as test">
<special-select data-selected-item="test.selectedThing">
<special-select-selected-item></special-select-selected-item>
</special-select>
</div>
app.js
angular
.module('testApp', ['special-inputs'])
.controller('testCtrl', [
function() {
this.items = [
{ id: 1, name: 'alex 1', displayName:"alex 1 dn", imageUrl: 'http://placehold.it/505x100' }
];
this.selectedThing = this.items[0];
this.test = function() {
console.log('test fn');
}
}
]);
special-inputs.js
angular
.module('special-inputs', [])
.directive('specialSelect', [
function() {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div class="container" ng-transclude></div>',
scope: {
selectedItem: '='
},
link: {
pre: function(scope) {
console.log('parent link pre - ', scope.selectedItem);
},
post: function(scope) {
console.log('parent link post - ', scope.selectedItem);
}
}
}
}
])
.directive('specialSelectSelectedItem', [
function() {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div class="selected" ng-transclude></div>',
scope: true,
link: {
pre: function(scope) {
console.log('child link pre - ', scope.selectedItem);
},
post: function(scope) {
console.log('child link post - ', scope.selectedItem);
}
}
}
}
])
As you can see the nested directive is trying to create it's own isolate scope but grab the "selectedItem" from the isolate scope of the parent.
The docs say it should inherit and transclusion is semi prototypical, so I'm wondering why it's not working. Thanks. Any help would be appreciated
UPDATE
I've updated the code to show the problem I'm having now. It seems that the scope item is set in the pre + post link functions on the parent, but never get put down to the child. The console outputs
2015-12-29 15:13:44.258 special-select.js:35 parent link pre - Object {id: 1, name: "alex 1", displayName: "alex 1 dn", imageUrl: "http://placehold.it/505x100"}
2015-12-29 15:13:44.258 special-select.js:64 child link pre - undefined
2015-12-29 15:13:44.260 special-select.js:67 child link post - undefined
2015-12-29 15:13:44.260 special-select.js:38 parent link post - Object {id: 1, name: "alex 1", displayName: "alex 1 dn", imageUrl: "http://placehold.it/505x100"}
I've tried using scope.$watch and attrs.$observe to populate the valu

Property in custom Angular directive link function always null

Plunkr here
I have a custom directive I'm build to display basic user data. The HTML template is able to render the user data using {{xxx}} syntax, but I also need to use one of the properties from the user data object to retrieve a photo of the user.
My problem is that within the link function the personData value is always null. I added a $observe on person, but it is always null also.
Is there a way observe changes to the personData object and perform action on change?
Directive Code:
app.directive('graphPerson', ['$http', function() {
return {
restrict: 'E',
scope: {
personData: '=person'
},
link: function($scope, element, attrs, ctrl) {
console.log("Directive was linked");
console.log($scope.personData);
attrs.$observe('person', function(newValue, oldValue) {
console.log($scope.personData);
if ($scope.personData) {
//hard-coded photo for demo purposes
$scope.myPhoto = "http://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Blank_woman_placeholder.svg/315px-Blank_woman_placeholder.svg.png";
}
});
},
templateUrl: 'graph-person.html',
};
}]);
I am assuming that your intent is to monitor changes made to the personData object and not to the actual value of the DOM attribute person in your directive. Is that correct?
I would use $scope.$watch() here.
See: http://plnkr.co/edit/SU5x1F5tZKQWk4VK5OGO
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.personData = null;
$scope.people = [
{
"displayName": "Katie Jordan",
"givenName": "Katie",
"jobTitle": "Auditor",
"mail": "KatieJ#demo.com",
"mobilePhone": null,
"officeLocation": "12/1110",
"preferredLanguage": "en-US",
"surname": "Jordan",
"userPrincipalName": "KatieJ#demo.com",
"id": "a0da13e4-1866-492e-96e6-8a2a4b60f650"
},
{
"displayName": "James Jordan",
"givenName": "James",
"jobTitle": "Auditor",
"mail": "JamesJ#demo.com",
"mobilePhone": null,
"officeLocation": "12/1110",
"preferredLanguage": "en-US",
"surname": "Jordan",
"userPrincipalName": "JamesJ#demo.com",
"id": "b0da13e4-1866-492e-96e6-8a2a4b60f651"
}
];
$scope.clickMe = function(index) {
$scope.personData = $scope.people[index];
}
});
app.directive('graphPerson', ['$http', function() {
function getImageUrl(id)
{
console.log("I should get the URL For: " + id);
return id;
}
return {
restrict: 'E',
scope: {
personData: '=person'
},
link: function($scope, element, attrs, ctrl) {
console.log("Directive was linked");
$scope.$watch('personData', function(newValue) {
console.log($scope.personData);
if ($scope.personData) {
$scope.myPhoto = getImageUrl($scope.personData.id);
}
});
},
templateUrl: 'graph-person.html',
};
}]);

Issue with scope of a custom directive

Hi I have a directive like,
mainApp.directive('myMenu',function(){
return {
restrict : 'E',
scope :{menuItems : "=menuItems"},
compile: function(element, attributes) {
var linkFunction = function($scope, element, attributes){
for (i = 0;i<$scope.menuItems.length;i++){
element.append('<li>'+$scope.menuItems[i].name+'</li>');
}
}
return linkFunction;
}
}
});
I am using it like below in my HTML
<my-menu menuItems="menuItems"></my-menu>
But in the console I am getting an error like
TypeError: Cannot read property 'length' of undefined
The problem could be that, then the linking phase is executed the menuitems may not be loaded so $scope.menuItems may be undefined.
A better solution could be
var mainApp = angular.module('my-app', [], function() {})
mainApp.controller('AppController', function($scope) {
$scope.menuItems = [{
name: 'one'
}, {
name: 'two'
}, {
name: 'three'
}, {
name: 'four'
}];
})
mainApp.directive('myMenu', function() {
return {
restrict: 'E',
scope: {
menuItems: "="
},
template: '<ul><li ng-repeat="item in menuItems">{{item.name}}</li></ul>'
}
});
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.js"></script>
<div ng-app="my-app" ng-controller="AppController">
<my-menu menu-items="menuItems"></my-menu>
</div>
If you can't use template then
var mainApp = angular.module('my-app', [], function() {})
mainApp.controller('AppController', function($scope) {
$scope.menuItems = [{
name: 'one'
}, {
name: 'two'
}, {
name: 'three'
}, {
name: 'four'
}];
})
mainApp.directive('myMenu', function() {
return {
restrict: 'E',
scope: {
menuItems: "=menuItems"
},
link: function($scope, element, attributes) {
$scope.$watch('menuItems', function(value) {
element.empty();
angular.forEach(value, function(item) {
element.append('<li>' + item.name + '</li>');
});
});
}
}
});
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.js"></script>
<div ng-app="my-app" ng-controller="AppController">
<my-menu menu-items="menuItems"></my-menu>
</div>
The Issue was with the name I used, like menuItems in directive should be equal to menu-items, solved the issue by repalcing the menuItems to menu.
use $scope.$eval(attributes.menuItems) inside compile function to get menuItems

Why doesn't directive work if I put name as an attribute rather than a tag?

In the example from the Angular documentation, a directive can be used by putting its name as an attribute in a <div>. The example given is
<div ng-controller="Controller">
<div my-customer></div>
</div>
with the js looking like
angular.module('docsSimpleDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.customer = {
name: 'Naomi',
address: '1600 Amphitheatre'
};
}])
.directive('myCustomer', function() {
return {
template: 'Name: {{customer.name}} Address: {{customer.address}}'
};
});
However, in a similar example, over here, if I change the html code from <tree> to <div tree>, the code no longer works.
Why not?
The code from JS Fiddle:
<div ng-app="myapp">
<div ng-controller="TreeCtrl">
<tree family="treeFamily">
<p>{{ family.name }}</p>
</tree>
</div>
</div>
var module = angular.module('myapp', []);
module.controller("TreeCtrl", function($scope) {
$scope.treeFamily = {
name : "Parent",
children: [{
name : "Child1",
children: [{
name : "Grandchild1",
children: []
},{
name : "Grandchild2",
children: []
},{
name : "Grandchild3",
children: []
}]
}, {
name: "Child2",
children: []
}]
};
});
module.directive("tree", function($compile) {
return {
restrict: "E",
transclude: true,
scope: {family: '='},
template:
'<ul>' +
'<li ng-transclude></li>' +
'<li ng-repeat="child in family.children">' +
'<tree family="child">{{family.name}}</tree>' +
'</li>' +
'</ul>',
compile: function(tElement, tAttr, transclude) {
var contents = tElement.contents().remove();
var compiledContents;
return function(scope, iElement, iAttr) {
if(!compiledContents) {
compiledContents = $compile(contents, transclude);
}
compiledContents(scope, function(clone, scope) {
iElement.append(clone);
});
};
}
};
});
tree {
margin-left: 20px;
display: block;
}
The restrict option is used to specify how a directive can be invoked on the page. There are four different ways to invoke a directive, so there are four valid options for restrict:
'A' - attribute - <span ng-sparkline></span>
'E' - element - <ng-sparkline></ng-sparkline>
'C' - class - <span class="ng-sparkline"></span>
'M' - comment - <!-- directive: ng-sparkline -->
In your case its not working because it is defined as restrict 'E' - element.
It's because of the restrict option in the directive.
Here it is set to e which means to match only the element name.
More https://docs.angularjs.org/guide/directive

passing different array variables to directive

In a controller I have defined two different arrays and I need to pass different arrays to directives, but the same item array is getting passed only.
Actually this is just snippets. In my actual code this directive I am using inside another.
Is that causing problem or any other way?
<div my-sticky tags="items" template_name="test2.html"></div>
<div my-sticky tags="kititems" template_name="test2.html"></div>
JS:
app.controller('MyCtrl', function($scope, $http) {
$scope.items = [
{ id: 18, text: '1' },
{ id: 28, text: '2' },
{ id: 38, text: '3' }
];
$scope.kititems = [
{ id: 0, text: '001' },
{ id: 028, text: '002' },
{ id: 038, text: '003' }
];
});
app.directive("mySticky", function($http, $compile) {
return {
restrict : "A",
scope : {
templateName: "#",
tags: "=",
},
templateUrl : function(el, attrs) {
return attrs.templateName;
},
controller : function($http, $scope, $element, $sce, $templateCache, $compile, $attrs) {
$scope.drawerStyle = {left: '140px'};
//$scope.CommonArray=$attrs.tags;
},
replace : false,
transclude : false,
link : function(scope, element, attrs) {
// change element just to show we can
}
};
});
Try this one
Working Demo
Inside your templateURL(test2.html) should like this i.e,
<div ng-repeat="tag in tags"> {{tag.id}} {{tag.text}} </div>
You might try to access items or kititems both are not in current scope because you have used isolated scope here.For clarity template used here instead of templateURL.
HTML Markup:
<div ng-controller="MyCtrl">
MyCtrl
<div my-sticky tags="items"></div>
<hr/>
<div my-sticky tags="kititems"></div>
</div>
angular modules
var app = angular.module("myApp",[]);
app.controller('MyCtrl', function($scope) {
$scope.items = [
{ id: 18, text: '1' },
{ id: 28, text: '2' },
{ id: 38, text: '3' }
];
$scope.kititems = [
{ id: 0, text: '001' },
{ id: 028, text: '002' },
{ id: 038, text: '003' }
];
});
app.directive("mySticky", function($http, $compile) {
return {
restrict : "A",
scope : {
templateName: "#",
tags: "=",
},
template :'<div ng-repeat="tag in tags"> {{tag.id}} {{tag.text}} </div>',
controller : function($http, $scope, $element, $sce, $templateCache, $compile, $attrs) {
$scope.drawerStyle = {left: '140px'};
// console.log($scope);
//$scope.CommonArray=$attrs.tags;
},
replace : false,
transclude : false,
link : function(scope, element, attrs) {
// change element just to show we can
}
};
});

Categories