Angular directive scope sharing between two directive - javascript

I am working on a project where I have one controller and two directives and I need to share the scope between them all, I have created plnkr here.
The code structure is as following:
Main Controller
--Drawable Directive
----Draw-rectangle Directive
In the Main ctrl there is one object rois on scope which I am to Drawable and Draw-rectangle directive. and on click of drawable it updates to scope of Main controller but when I click on the draw-rectangle directive it's not updating the scope.
I want to all (3) scopes to be synced using two way data binding.
It seems conceptually correct but why its not updating the scope from Draw-rectangle Directive?
Thanks in advance!

When you click "draw-rectangle" your are also clicking "drawable" because "draw-rectangle" is inside "drawable". You must stop propagation from "draw-rectangle" to "drawable" using event.preventDefault(); as folow:
var app = angular.module('myApp', []);
app.controller('MainCtrl', function($scope) {
$scope.rois = [{
name: 'Jack',
city: 'pune'
}, {
name: 'Tony',
city: 'Mumbai'
}];
$scope.test = "Test";
});
app.directive('drawable', [
function() {
return {
restrict: "EA",
link: function(scope, element, attrs) {
element.on('click', function(event) {
event.preventDefault();
scope.rois = [{
name: 'Stark',
city: 'pune'
}, {
name: 'Inc',
city: 'Mumbai'
}];
scope.$apply();
console.log(scope.rois);
});
}
};
}
]);
app.directive('drawRectangle', [
function() {
return {
restrict: "EA",
link: function(scope, element, attrs) {
element.on('click', function(event) {
event.stopPropagation(); // STOP PROPAGATION
event.preventDefault();
scope.rois = [{
name: 'Meuk',
city: 'pune'
}, {
name: 'Tony',
city: 'Mumbai'
}];
scope.$apply();
console.log(scope.rois);
});
}
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller='MainCtrl' style='width: 400px;height: 400px;border: 1px solid red;'>
<div drawable rois="rois" style='width: 300px;height: 300px;border: 1px solid red;'>
<div draw-rectangle rois="rois" style='width: 200px;height: 200px;border: 1px solid red;'>
<button type="button" style='margin: 20px; border: 1px solid red;'>Click me!</button>
</div>
</div>
<br>
<br>{{rois | json}}
</div>

u need to stop bubble up the event, because when rect directive clicked, drawable also trigger click! use event.stopPropagation()
var app = angular.module('myApp');
app.directive('drawable', ['$document',
function($document) {
return {
restrict: "EA",
scope: {
rois: '='
},
link: function(scope, element, attrs) {
console.log(scope.rois);
element.on('click', function(event) {
event.stopPropagation();
scope.rois = [{
name: 'Stark',
city: 'pune'
}, {
name: 'Inc',
city: 'Mumbai'
}];
scope.$apply();
console.log(scope.rois);
});
}
};
}
]);
app.directive('drawRectangle', ['$document',
function($document) {
return {
restrict: "EA",
scope: {
rois: '='
},
link: function(scope, element, attrs) {
element.on('click', function(event) {
event.stopPropagation();
scope.rois = [{
name: 'Meuk',
city: 'pune'
}, {
name: 'Tony',
city: 'Mumbai'
}];
scope.$apply();
console.log(scope.rois);
});
}
};
}
]);

You are using isolated scope for two directives. Isolated scope will create a child scope. So, you cannot access "rois" out of the directive's link function.
Try after you remove the isolated scope,
scope: {
rois: '='
},

Related

How to bind html code with ng-... attributes

I want to create <select ng-model='selected' ng-options='stage as stage.name for stage in stages'></select> element in variable as a string. As you see here, there are ng-... attributes inside the select element. If I use select element without ng-... attribute it is displaying without problem. If I use ng-... inside it, it's not showing anything. So, how do I make it display with ng-...? Please help.
My html code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="Scripts/angular.min.js"></script>
<script src="Scripts/angular-sanitize.js"></script>
<script src="Scripts/myScripts/index.js"></script>
</head>
<body>
<div ng-app="myapp" ng-controller="myCtrl">
<div ng-bind-html="htmlCode">
</div>
</div>
</body>
</html>
My js code:
var app = angular.module('myapp', ['ngSanitize']);
app.controller('myCtrl', ["$scope", function ($scope) {
$scope.stages =
[{ name: "Identification of Discontinuing Factors", value: 1 },
{ name: "Project Level Assessment", value: 2 },
{ name: "Organizational Readiness Assessment", value: 3 }];
$scope.htmlCode = "<select ng-model='selectedStage' ng-options='stage as stage.name for stage in stages'></select>";
}]);
you need to recompile the dom in order to work the ng- attributes. use this directive as an attribute to the ng-bind-html element.
.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function(html) {
ele.html(html);
$compile(ele.contents())(scope);
});
}
};
});
Demo
var app = angular.module('myapp', []);
app.controller('myCtrl', ["$scope","$sce", function ($scope,$sce) {
$scope.stages =
[{ name: "Identification of Discontinuing Factors", value: 1 },
{ name: "Project Level Assessment", value: 2 },
{ name: "Organizational Readiness Assessment", value: 3 }];
$scope.htmlCode = "<select ng-model='selectedStage' ng-options='stage as stage.name for stage in stages'></select>";
$scope.trust = function(html){
return $sce.trustAsHtml(html)
}
}]);
app.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function(html) {
ele.html(html);
$compile(ele.contents())(scope);
});
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myapp" ng-controller="myCtrl">
<div ng-bind-html="trust(htmlCode)" dynamic>
</div>
{{selectedStage}}
</div>

How to handle click event in angular1 directive test cases

I am trying to write test case for my directive in anguarjs1.x
here is my directive
.directive("weeklyDirective", function($timeout) {
return {
scope: {
data: '=',
},
link: function(scope, element) {
scope.weekDays = [
{ text: 'Sun', id: 1 },
{ text: 'Mon', id: 2 },
{ text: 'Tue', id: 3 },
{ text: 'Wed', id: 4 },
{ text: 'Thu', id: 5 },
{ text: 'Fri', id: 6 },
{ text: 'Sat', id: 7 }
];
},
restrict: 'A',
templateUrl: "/flat-ui/tpls/weekly-scheduler.html",
};
})
where is my directive template
<ul style="padding: 0px;display: inline;margin: 0;list-style: none;">
<li ng-repeat="weekDay in weekDays" style="padding: 10px;display: inline;">
<input type="checkbox" value="{{weekDay.id}}" check-list="data.weeklyDetails" id="{{'chk_'+$index}}" class="ee-check"> <label class="ee-check" for="{{'chk_'+$index}}"><span></span> {{weekDay.text}}</label>
</li>
</ul>
In my weekly directive, I have used another directive which handles my checkbox list
.directive('checkList', function() {
return {
scope: {
list: '=checkList',
value: '#'
},
link: function(scope, elem, attrs) {
var handler = function(setup) {
var checked = elem.prop('checked');
var index = scope.list.indexOf(scope.value);
if (checked && index == -1) {
if (setup) elem.prop('checked', false);
else scope.list.push(scope.value);
} else if (!checked && index != -1) {
if (setup) elem.prop('checked', true);
else scope.list.splice(index, 1);
}
};
var setupHandler = handler.bind(null, true);
var changeHandler = handler.bind(null, false);
elem.bind('change', function() {
scope.$apply(changeHandler);
});
scope.$watch('list', setupHandler, true);
}
};
});
now I am trying to write test cases to test my directive
describe("weeklyDirective directive", function() {
var elm, scope, httpBackend, controller;
beforeEach(module('guideApp.directives'));
beforeEach(module('/flat-ui/tpls/weekly-scheduler.html'));
beforeEach(angular.mock.inject(function($rootScope, $compile) {
compile = $compile;
scope = $rootScope;
elm = angular.element('<div weekly-directive data="data"></div>');
compile(elm)(scope);
scope.data = {
interval: 1,
weeklyDetails: ['1'],
}
scope.$digest();
}));
it("click on check box it should get added in weeklyDetails", function() {
var e = elm.find('input[id="chk_3"]');
console.log(e);
e.trigger('click');
scope.$apply();
var isolateScope = elm.isolateScope();
expect(isolateScope.data.weeklyDetails.indexOf('4') > -1).toBeTruthy();
});
});
where I am trying to test that when user click on check box its value should get added to my array weeklyDetails which in data object (passed to the weeklydirective).
its not working as exptected for me please help me to get this working.
Thanks in Adv.

Directive within a directive within a template not working

I'm an Angular noob. I'm structuring my panes as sets of tables and using directives to keep the template HTML abstract from the table implementation. The panes are getting transcluded correctly, but interior only contains the text not the table structure. Here's my code:
<!DOCTYPE html>
<html>
<head>
<script src= "angular.js"></script>
<script src= "angular-sanitize.js"></script>
</head>
<body ng-app="pp">
<div ng-controller="ppMain">
<ul class="menu">
<li ng-repeat="pageID in homePageList">{{pages[pageID].str}}</li>
</ul>
<div>
<div ng-repeat="pageID in pageList" ng-bind-html="pages[pageID].template">
</div>
</div>
</div>
<script>
var pp = {
ctl: {}
};
pp.mod = angular.module('pp', ['ngSanitize']);
pp.mod.directive({
'ppHeader': function () {
return ({
});
},
'ppGroup': function () {
return ({
template: '<table ng-transclude></table>',
transclude: true,
restrict: 'EA'
});
},
'ppRow': function () {
return ({
template: '<tr ng-transclude></tr>',
transclude: true,
restrict: 'EA'
});
},
'ppLabel': function () {
return ({
template: '<td ng-transclude></td>',
transclude: true,
restrict: 'EA'
});
},
'ppValue': function () {
return ({
template: '<td ng-transclude></td>',
transclude: true,
restrict: 'EA'
});
},
});
pp.ctl.main = pp.mod.controller('ppMain', ['$scope', function ($scope) {
$scope.curPage = 'page1';
$scope.pages = {
"page1": {
str: "page 1",
template:'\
<pp-group>\
<pp-row>\
<pp-label>Page:</pp-label><pp-value>1</pp-value>\
</pp-row>\
</pp-group>\
'},
"page2": {
str: "page 2",
template:'\
<pp-group>\
<pp-row>\
<pp-label>Page:</pp-label><pp-value>2</pp-value>\
</pp-row>\
</pp-group>\
'},
"page3": {
str: "page 3",
template:'\
<pp-group>\
<pp-row>\
<pp-label>Page:</pp-label><pp-value>3</pp-value>\
</pp-row>\
</pp-group>\
'}
};
$scope.pageList = ["page1","page2","page3"];
$scope.homePageList = ["page2", "page3"];
}]);
</script>
</body>
</html>
Looking at the generated DOM with the debugger, I find no tables, only text.
That is not possible with ng-bind-html because ng-sanitize does not compile the html. It can only be used for static content. You would need to create a directive yourself for this.
Something like this:
.directive('compileHtml', function($compile) {
return {
restrict: 'A',
link: function(scope, elm, attrs) {
var template = scope.$eval(attrs.compileHtml); //get the template by evaluating
elm.html(template); //set the html
$compile(elm.contents())(scope); //compile the contents
}
}
});
Also remember that you have child directives with no replace, which means you will be creating invalid html, table created by ppGroup will have ppRow as child which results in invalid html and it will push the directive content out of the table and compilation will not happen properly. So you would instead need to use the option replace:true on these directives.
However it will be really unsafe to compile the html dynamically though unless you know it is from a trusted source which is what $sce service used by ng-bind-html does. It sanitizes the boundhtml.
Demo
var pp = {
ctl: {}
};
pp.mod = angular.module('pp', ['ngSanitize']);
pp.mod.directive({
'ppHeader': function() {
return ({});
},
'ppGroup': function() {
return ({
template: '<table ng-transclude></table>',
transclude: true,
restrict: 'EA'
});
},
'ppRow': function() {
return ({
replace: true,
template: '<tr ng-transclude></tr>',
transclude: true,
restrict: 'EA'
});
},
'ppLabel': function() {
return ({
replace: true,
template: '<td ng-transclude></td>',
transclude: true,
restrict: 'EA'
});
},
'ppValue': function() {
return ({
replace: true,
template: '<td ng-transclude></td>',
transclude: true,
restrict: 'EA'
});
},
}).directive('compileHtml', function($compile) {
return {
restrict: 'A',
link: function(scope, elm, attrs) {
var template = scope.$eval(attrs.compileHtml);
elm.html(template);
$compile(elm.contents())(scope);
}
}
});
pp.ctl.main = pp.mod.controller('ppMain', ['$scope',
function($scope) {
$scope.curPage = 'page1';
$scope.pages = {
"page1": {
str: "page 1",
template: '\
<pp-group>\
<pp-row>\
<pp-label>Page:</pp-label><pp-value>1</pp-value>\
</pp-row>\
</pp-group>\
'
},
"page2": {
str: "page 2",
template: '\
<pp-group>\
<pp-row>\
<pp-label>Page:</pp-label><pp-value>2</pp-value>\
</pp-row>\
</pp-group>\
'
},
"page3": {
str: "page 3",
template: '\
<pp-group>\
<pp-row>\
<pp-label>Page:</pp-label><pp-value>3</pp-value>\
</pp-row>\
</pp-group>\
'
}
};
$scope.pageList = ["page1", "page2", "page3"];
$scope.homePageList = ["page2", "page3"];
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.3/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.3/angular-sanitize.min.js"></script>
<div ng-app="pp">
<div ng-controller="ppMain">
<ul class="menu">
<li ng-repeat="pageID in homePageList">{{pages[pageID].str}}</li>
</ul>
<div>
<div ng-repeat="pageID in pageList" compile-html="pages[pageID].template">
</div>
</div>
</div>
</div>

angular js directive attribute and controller scope

I would like to have a directive which creates a div as title and a ul list under it.
I want the title to be set from attribute and a the list by a controller.
Here is a fiddle of my code
HTML:
<div ng-app="myModule">
<my-list caption="My List" ng-controller="ListController"></my-list>
</div>
JavaScript:
angular.module('myModule', []).
controller('ListController', ['$scope', function ($scope) {
$scope.items = [{
caption: 'Item 1'
}, {
caption: 'Item 2'
}, {
caption: 'Item 3'
}];
}]).directive('myList', [function () {
return {
restrict: 'E',
template: '<div>' +
'<div style="font-weight:bold;">{{caption}}</div>' +
'<ul>' +
'<li ng-repeat="item in items">{{item.caption}}</li>' +
'</ul>' +
'</div>',
scope: {
caption: '#caption'
},
link: function (scope, element) {
element.find('li').on('click', function (evt) {
alert($(this).html());
});
}
}
}])
How can I solve this issue?
fixed few things in your controller.
moved controller to div
<div ng-app="myModule" ng-controller="ListController">
<my-list caption="My List" list="items"></my-list>
</div>
fixed directive to receive list as a parameter
directive('myList', [function () {
return {
restrict: 'E',
template: '<div>' +
'<div style="font-weight:bold;">{{caption}}</div>' +
'<ul>' +
'<li ng-repeat="item in items" ng-click="onClick(item)">{{item.caption}}</li>' +
'</ul>' +
'</div>',
scope: {
caption: '#caption', items: '=list'
},
link: function (scope, element) {
scope.onClick= function(item){console.log(item);}
}
}
}])
there is one doubt though that i have.
controller myList to is tried to view or to directive?? in case it is tied to directive then
angular.module('myModule', []).
controller('ListController', ['$scope', function ($scope) {
$scope.items = [{
caption: 'Item 1'
}, {
caption: 'Item 2'
}, {
caption: 'Item 3'
}];
}]).directive('myList', [function () {
return {
restrict: 'E',
template: '<div>' +
'<div style="font-weight:bold;">{{caption}}</div>' +
'<ul>' +
'<li ng-repeat="item in items" ng-click="onClick(item)">{{item.caption}}</li>' +
'</ul>' +
'</div>',
scope: {
caption: '#caption'
},
link: function (scope, element) {
scope.onClick= function(item){console.log(item);}
},
controller: 'ListController'
}
}])
The problem (as the error message indicates) is that you specify multiple directives which request for an isolated scope.
Actually, if you want to specify a controller for your directive, you use the controller property of the Directive Definition Object:
<my-list caption="My List"></my-list>
.directive('myList', [function () {
return {
...
controller: 'ListController',
...
Updated fiddle

Passing an object to a directive as an argument

I have a view and a controller for it:
<app-head options="options"></app-head>
app.controller('homeCtrl',function($scope){
$scope.options = {
first: {
description: "A link",
link: "../"
},
second:{
description: "Another link",
link: "../../"
}
};
});
I want to use the object options within my directive:
app.directive('appHead',function(){
return{
restrict: 'E',
templateUrl: 'partials/modules/appHead.html',
replace: true,
controller: function($scope,$element,$attrs){
$scope.options = $attrs.options;
}
}
});
When i call console.log($scope.options) in my directive's controller however, it will return options
No Properties
Why is that and how do i accomplish this?
Try this
app.directive('appHead',function(){
return{
scope: {
options: '=',
},
restrict: 'E',
templateUrl: 'partials/modules/appHead.html',
replace: true,
link: function($scope,$element,$attrs){
// $scope.options
}
}
});
Working Demo

Categories

Resources