angularjs how to access controller $scope variable in directive isolated scope - angularjs

In angularjs I have been trying to access main controller $scope variable in my directive isolated scope.
My html code,
<body ng-controller="MainCtrl">
<div id="TestContainer" class="TestContainer" ng-init=Intialfunc()>
<collection collection='testdata'>{{testdata}}</collection>
</div>
</body>
My directive code,
var app = angular.module('plunker', []);
app.directive('collection', function () {
return {
restrict: "E",
replace: true,
scope: {collection: '='},
//controller: 'TreeController',
//bindToController: true,
template: "<ul><member ng-repeat='member in collection' member='member'></member></ul>"
}
})
app.directive('member', function ($compile) {
var linkerfunc = function(scope, element, attrs) {
var collectionSt = '<collection collection="member.children"></collection>';
$compile(collectionSt)(scope, function(cloned, scope) {
element.append(cloned);
});
}
return {
restrict: "E",
replace: true,
scope: {member: '=', ShowDetailsCtrlFunc : '&'},
template: "<li><span ng-click=ShowDetailsCtrlFunc()>{{member.NodeName}}</span></li>",
controller: 'MainCtrl',
//controllerAs: 'MainCtrl',
//bindToController: true,
link: linkerfunc
}
})
My controller code,
app.controller('MainCtrl', function ($scope) {
$scope.Intialfunc = function() {
$scope.testdata = []
var myjsondata = JSON.parse('{ "NodeName": "Parent", "children": [ { "NodeName": "mychild", "children": [ { "NodeName": "chld1", "children": [] } ] } ] }');
$scope.testdata.push(myjsondata);
console.log($scope.testdata) //This one is showing
}
$scope.ShowDetailsCtrlFunc = function(element,event) {
console.log("in function ShowDetailsCtrlFunc"); // coming to this fucntion on click.
console.log($scope.testdata) // but this one is not showing . shows undefined.
//event.stopImmediatePropagation();
};
});
it is coming to the function but not showing the controller $scope. I have created a plunker ,
plunker
Please help me. I have been struggling for many days.

You need to add a function expression to both of your directives' isolate scopes in order to properly call a function in your parent scope. Taking your original code, it should look something like this:
var app = angular.module('plunker', []);
app.directive('collection', function () {
return {
restrict: "E",
//replace: true, <- this is deprecated and should no longer be used
scope: {
collection: '=',
onMemberClick: '&'
},
template: "<ul><member ng-repeat='member in collection' member='member' on-click='onMemberClick()'></member></ul>"
}
})
app.directive('member', function ($compile) {
return {
restrict: "E",
//replace: true, <- this is deprecated and should no longer be used
scope: {
member: '=',
onClick : '&'
},
template: "<li><span ng-click='onClick()'>{{member.NodeName}}</span></li>"
}
});
And you original html should look something like this:
<body ng-controller="MainCtrl">
<div id="TestContainer" class="TestContainer" ng-init=Intialfunc()>
<collection collection='testdata' on-member-click='ShowDetailsCtrlFunc ()'>{{testdata}}</collection>
</div>
</body>
Argument binding
If you would like to actually know which member was clicked, you'll need to bind arguments to your function calls.
var app = angular.module('plunker', []);
app.directive('collection', function () {
return {
restrict: "E",
scope: {
collection: '=',
onMemberClick: '&'
},
template: "<ul><member ng-repeat='member in collection' member='member' on-click='onMemberClick({member: member})'></member></ul>"
}
})
app.directive('member', function ($compile) {
return {
restrict: "E",
scope: {
member: '=',
onClick : '&'
},
template: "<li><span ng-click='onClick({member: member})'>{{member.NodeName}}</span></li>"
}
});
Html:
<body ng-controller="MainCtrl">
<div id="TestContainer" class="TestContainer" ng-init=Intialfunc()>
<collection collection='testdata' on-member-click='ShowDetailsCtrlFunc (member)'>{{testdata}}</collection>
</div>
</body>
MainCtrl:
app.controller('MainCtrl', function ($scope) {
$scope.Intialfunc = function() {
$scope.testdata = []
var myjsondata = JSON.parse('{ "NodeName": "Parent", "children": [ { "NodeName": "mychild", "children": [ { "NodeName": "chld1", "children": [] } ] } ] }');
$scope.testdata.push(myjsondata);
console.log($scope.testdata) //This one is showing
}
$scope.ShowDetailsCtrlFunc = function(member) {
console.log("In show details function");
console.log(member);
};
});
plunker

Lets Begin with the query you have. You want to call a function from link inside the directive even when the scope is isolated. It's simple you want to access parent scope.
Here's the code you can use to access parent scope.
scope.$parent.yourFun();
//or you can do this by the code give below.
//Inside Directive Use this.
scope:{
fun:"&"
},
//now you can call this function inside link
link:function(scope, element,attr){
scope.fun();
}

In your app.directive, just put scope : false.
Your directive will use the same scope as his parent scope.

Related

AngularJS Edit JSON data using forms

In AngularJS , I am trying to make and editable TreeView from JSON data. I want to get a nodevalue from JSON and edit it using forms. While editing, the original JSON should change immediately. I created below plunkr for this but not getting an idea, how to achieve this.
Plunkr
var app = angular.module('plunker', []);
app.directive('collection', function () {
return {
restrict: "E",
//replace: true,
scope: {collection: '='},
//controller: 'TreeController',
//bindToController: true,
template: "<ul><member ng-repeat='member in collection' member='member'></member></ul>"
}
})
app.directive('member', function ($compile) {
var linkerfunc = function(scope, element, attrs) {
var collectionSt = '<collection collection="member.children"></collection>';
$compile(collectionSt)(scope, function(cloned, scope) {
element.append(cloned);
});
scope.ShowDetailsFunc = function() {
console.log('in function scope showdetails')
scope.ShowDetailsCtrlFunc(element,event);
}
}
return {
restrict: "E",
//replace: true,
scope: {member: '=', ShowDetailsCtrlFunc : '&'},
template: "<li><span ng-click=ShowDetailsCtrlFunc($event)>{{member.NodeName}}</span></li>",
controller: 'MainCtrl',
//controllerAs: 'MainCtrl',
//bindToController: true,
link: linkerfunc
}
})
app.controller('MainCtrl', function ($scope,$timeout) {
$scope.Intialfunc = function() {
$scope.testdata = []
var myjsondata = JSON.parse('{ "NodeName": "Parent", "NodePath": "1", "children": [ { "NodeName": "mychild", "NodePath": "1.1", "children": [ { "NodeName": "chld1", "NodePath": "1.1.1", "children": [] } ] } ] }');
$scope.testdata.push(myjsondata);
//console.log($scope.testdata) //This one is showing
$scope.changename = $scope.testdata[0].children[0].children[0].NodeName;
}
$timeout(function(){ $scope.Intialfunc(); }, 1000)
$scope.ShowDetailsCtrlFunc = function(event) {
console.log("in function ShowDetailsCtrlFunc");
//event.stopImmediatePropagation();
};
});
I tried angular.copy but that needs the portion of JSON data in a $scope variable and updates that variable as expected.
But my JSON data going to be a huge and i dont know how to update it without using variable. Please help.
You can use ng-change to perform an action when an input changes:
<input type="text" ng-model="changename" ng-change="inputChange()">
Then, just create a function in your controller that updates what you need it to:
$scope.inputChange = function() {
// do stuff here
$scope.testdata[0].children[0].children[0].NodeName = $scope.changename;
// write object to JSON
var JSONstring = $scope.testdata.toJSON();
}
I would also recommend looking at ng-model-options and changing the debounce settings so that you aren't constantly toJSONing with every keystroke.
http://plnkr.co/edit/dPWsowm4Puwajgk6CfvA?p=preview

AngularJS: data not passed into directive via attribute

In my AngularJs (v1.5.9) app, I have a kind of list view directive that itself depends on a directive to render the individual, complex items. What exactly should be rendered inside the list items is decided by the consumer and passed in via transclusion.
So the basic structure looks something like this:
<list-directive>
<list-item>
<some more stuff />
<transcluded content />
</list-item>
</list-directive>
I am now trying to add another directive to this structure, that takes data that can be passed in via an attribute from the outside into the top-level element and then does stuff depending on that input for each of the list-items.
The structure is somewhat complex and I tried to reduce the code snippet below to the bare minimum.
// controller
(function () {
'use strict';
function FcDataListCtrl($scope, $q, $element) {
var that = this;
initVars();
init();
function initVars() {
that.actionButtons = that.actionButtons || [];
}
function init() {
}
}
angular
.module('controls.fcDataList.controller', [])
.controller('fcDataListCtrl', FcDataListCtrl);
})();
(function () {
'use strict';
function FcDataList() {
return {
restrict: 'E',
transclude: true,
template: `<fc-data-list-item ng-repeat="item in ::fcDataList.items" item="::item">
<div ng-transclude></div>
</fc-data-list-item>`,
scope: {
items: '=?',
actionButtons: '=?'
},
controller: 'fcDataListCtrl',
controllerAs: 'fcDataList',
bindToController: true
};
}
angular
.module('controls.fcDataList', [
'controls.fcDataList.controller',
'controls.fcDataList.item'
])
.directive('fcDataList', FcDataList);
})();
(function () {
'use strict';
function FcDataListItem() {
return {
restrict: 'E',
replace: true,
require: '^fcDataList',
transclude: 'element',
template: `<div>
<div ng-transclude></div>
<fc-item-menu items="fcDataList.actionButtons"></fc-item-menu>
</div>`,
scope: {
item: '=?'
},
link: {
pre: FcDataListItemLink
}
};
function FcDataListItemLink(scope, elem, attrs, fcDataListCtrl) {
initVars();
init();
function initVars() {
}
function init() {
console.log('FcDataListItem')
console.dir(fcDataListCtrl.actionButtons);
}
}
}
angular
.module('controls.fcDataList.item', [
'components.fioControlsExtensions.fcDataList.menu'
])
.directive('fcDataListItem', FcDataListItem);
})();
(function () {
'use strict';
function FcItemMenu() {
return {
restrict: 'E',
template: `<div ng-repeat="item in items">
<div>{{ item.icon }}</div>
</div>`,
scope: {
items: '=?'
},
link: {
pre: FcItemMenuLink
}
};
function FcItemMenuLink(scope, elem, attrs) {
scope.open = open;
initVars();
init();
function initVars() {
console.log('MenuItem');
console.dir(scope.items);
}
function init() {
}
function open(event) {
}
}
}
angular
.module('components.fioControlsExtensions.fcDataList.menu', [])
.directive('fcItemMenu', FcItemMenu);
})();
(function () {
'use strict';
function AppCtrl() {
var that = this;
init();
function init() {
that.fcDataList = {
buttons: [
{ icon: 'ff-show' }
],
items: [
{ firstName: 'Ivan', lastName: 'Petrov', jobPosition: 'Zookeeper' },
{ firstName: 'Andrei', lastName: 'Müller', jobPosition: 'Pilot' },
{ firstName: 'Christian', lastName: 'Klein', jobPosition: 'Cook' },
{ firstName: 'Peter', lastName: 'Stoyanov', jobPosition: 'Fuller' },
{ firstName: 'Nadine', lastName: 'Wolf', jobPosition: 'Driving Instructor' },
{ firstName: 'Amya', lastName: 'Krüger', jobPosition: 'Military' }
],
}
}
}
angular
.module('controls.example', [
'controls.fcDataList'
])
.controller('AppCtrl', AppCtrl)
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
<html lang="en" ng-app="controls.example">
<body ng-controller="AppCtrl as app">
<b>Test for data list n stuff</b>
<fc-data-list items="app.fcDataList.items"
action-buttons="app.fcDataList.buttons">
<div class="row">
<span> This is a list item </span>
</div>
</fc-data-list>
</body>
</html>
here is alsow a Codepen with the same example: https://codepen.io/lyioth/pen/LbqWLz/
Please note that the transclusion of the actual item content is not shown here (but that is working without a problem, so I skipped it).
The actual problem is that, the items in fc-item-menu stay undefined. I added some log statements to show that at the levels above this component the array in question is not in fact empty.
If I change the directive to also require the controller and access the actionButtons property directly, it seems to work. But I'd rather not do that.
So the question is, why doesn't this work as expected? What am I missing?
The items at fc-item-menu are bound to fcDataList.actionButtons. That means that the fc-item-menu directive will look for them at scope.fcDataList.actionButtons.
Let's go one step back - in your fc-data-list directive you have the same type of binding - fcDataList.item. That works, because you have used bindToController and controllerAs, which puts the isolated scope in a scope field, with the name defined in controllerAs (scope.fcDataList).
Back to fc-item-menu: although you have require: ^fcDataList in the directive, the above expression will be undefined, because no scope.fcDataList exist. In addition that is true, because with the transclusion, an isolated scope has been created, which does not carry the scope.fcDataList. The way to make it work is define a scope property yourself with the values from the controller, like so:
function FcDataListItemLink(scope, elem, attrs, fcDataListCtrl) {
scope.buttons = fcDataListCtrl.actionButtons;
}
Here's a working plnkr: https://codepen.io/anon/pen/GNzELy

$scope.$watch doesn't update after service call in directive

I have a directive which loads a image data template.
The problem is that It doesn't update the image date after the service which retrieve the img information is called.
This is my code:
Controller method:
$scope.watchImage = function(file_id){
FileService.getFile(file_id)
.then(
function(data){
if(data.file){
$scope.img = data.file;
console.log('Service called');
}
}
);
}
Directive:
app.directive('imageDetails', function() {
return {
scope: {
img: '='
},
restrict: 'E',
link: function($scope, element, attrs){
$scope.$watch(function() {
return $scope.img;
}, function() {
console.log($scope.img);
});
},
template: 'IMG: {img}'
};
});
HTML:
<div class="ui container">
<h2 class="ui dividing header">Images</h2>
</div>
<div ng-view></div>
<image-details img="img"></image-details>
</div>
Log result:
undefined
Service called
Any idea how to solve it ?
Thanks!
First of all, thank you to everyone for your replies. All of them help me in the solution.
Finally this is my working code.
Directive:
app.directive('imageDetails', function() {
return {
scope: {
img: '='
},
restrict: 'E',
template: 'IMG: {{img}}'
};
});
And I added the directive to my template (I was adding it outside ngview).
you have some mistake in template and in link function.
var app = angular.module('myApp', []);
app.controller('mainCtrl', function ($scope) {
$scope.img = {id: 1, title: "avatar.jpeg", slug: "avatar.jpeg", filesize: 24875, created_at: "2016-03-10 11:44:59"};
})
app.directive('imageDetails', function() {
return {
scope: {
img: '='
},
restrict: 'E',
link: function(scope, element, attrs){
scope.$evalAsync(function() {
return scope.img;
});
},
template: 'IMG: {{img}}'
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="mainCtrl">
<image-details img="img"></image-details>
</div>
I think your directive should be Like :
app.directive('imageDetails', function() {
return {
scope: {
img: '='
},
restrict: 'E',
link: function(scope, element, attrs){
scope.$watch('img',function(image) {
return image;
}, function() {
console.log(image);
});
},
template: 'IMG: {img}'
};
});
First of all use a controller instead of link function because you don't need that. Link function is deprecated for simple components like this in angular 1.5.
Then, for using $watch, you need to specify what variable you want to watch, and only after what to do when it's change.
$watch('varToWatch', function(newValue) {...});
That said, if you use a controller instead of the link function, you probably use also a "Controller as" syntax. When you use it, you need to specify the "view name" of the variable you want to watch. For example:
app.directive('imageDetails', function() {
return {
scope: {
img: '='
},
restrict: 'E',
controllerAs: '$ctrl',
controller: function($scope){
$scope.$watch('$ctrl.img', function(newVal) {
console.log(newVal);
// if you want you can assign new value to your variable
// $scope.img = newVal;
});
},
template: 'IMG: {img}'
};
});
Try that and tell me if it's works for you ;)
This is a clear case of when the scope is affected outside the module. For those cases the lifecycle will not do the digest of the scope as you will expect.
You have to manually $digest or $apply when you want to notify your app that the scope have changed inside your directive

two-way data binding inside attribute directive that requires element directive

I have an element directive (e-dir) and an attribute directive (a-dir) on the same element:
<e-dir a-dir attr="msg"></e-dir>
I pass msg into e-dir's isolate scope via the attr attribute:
app.directive('eDir', function eDir($timeout) {
return {
restrict: 'E',
scope: {
attr: '='
}
};
});
In this way, msg is bound (two-ways) with $scope.attr (in EDirCtrl) or scope.attr (in e-dir's link function).
Is there a simple way I can achieve the same two-way data-binding inside a-dir's directive? Or would you recommend another, simpler approach?
The closest thing I've been able to come up with is to set eDirCtrl.attr = $scope.attr; inside e-dir's controller (EDirCtrl):
app.directive('eDir', function eDir($timeout) {
return {
restrict: 'E',
scope: {
attr: '='
},
controller: function EDirCtrl($scope) {
var eDirCtrl = this;
eDirCtrl.attr = $scope.attr;
},
controllerAs: 'eDirCtrl'
};
});
Then, have a-dir require e-dir, and access attr via e-dir's controller (eDirCtrl.attr):
app.directive('aDir', function aDir($timeout) {
return {
restrict: 'A',
require: 'eDir',
link: linkFn
};
function linkFn(scope, element, attrs, eDirCtrl) {
eDirCtrl.attr = 'eDirCtrl.attr';
}
});
But, it's not bound two-ways. As you can see this code snippet:
var app = angular.module('app', []);
app.controller('Ctrl', function Ctrl($scope) {
$scope.msg = 'initial message';
})
app.directive('eDir', function eDir($timeout) {
return {
restrict: 'E',
scope: {
attr: '='
},
template: '<div>$scope.attr: {{attr}}</div>'+
'<div>eDirCtrl.attr: {{eDirCtrl.attr}}</div>',
controller: function EDirCtrl($scope) {
var eDirCtrl = this;
eDirCtrl.attr = $scope.attr;
$timeout(function() {
$scope.attr = 'changing $scope.attr also changes msg';
}, 2000);
},
controllerAs: 'eDirCtrl'
};
});
app.directive('aDir', function aDir($timeout) {
return {
restrict: 'A',
require: 'eDir',
link: linkFn
};
function linkFn(scope, element, attrs, eDirCtrl) {
$timeout(function() {
eDirCtrl.attr = 'changing eDirCtrl.attr does not effect $scope.attr or msg';
}, 4000);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="Ctrl">
msg: <input type="text" ng-model="msg"><br>
<e-dir a-dir attr="msg"></e-dir>
</div>
The reason the two-way binding isn't working is that attr is being bound to a string rather than an object. In JavaScript, primitives (booleans, numbers, strings) are immutable, so when you change one, the previous instance is discarded and a new one is used. This breaks Angular's two-way binding and any changes to scope.msg are not propagated through attr into the directive.
You can get this to work as expected by setting msg on an object e.g. scope.test.msg and binding attr to test (the object) rather than msg (a string).
I've updated your code snippet to do this:
var app = angular.module('app', []);
app.controller('Ctrl', function Ctrl($scope) {
$scope.test = {msg : 'initial message'};
})
app.directive('eDir', function eDir($timeout) {
return {
restrict: 'E',
scope: {
attr: '='
},
template: '<div>$scope.attr: {{attr.msg}}</div>'+
'<div>eDirCtrl.attr: {{eDirCtrl.attr.msg}}</div>',
controller: function EDirCtrl($scope) {
var eDirCtrl = this;
eDirCtrl.attr = $scope.attr;
$timeout(function() {
$scope.attr.msg = 'changing $scope.attr also changes msg';
}, 2000);
},
controllerAs: 'eDirCtrl'
};
});
app.directive('aDir', function aDir($timeout) {
return {
restrict: 'A',
require: 'eDir',
link: linkFn
};
function linkFn(scope, element, attrs, eDirCtrl) {
$timeout(function() {
eDirCtrl.attr.msg = 'changing eDirCtrl.attr does not effect $scope.attr or msg';
}, 4000);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="Ctrl">
msg: <input type="text" ng-model="test.msg"><br>
<e-dir a-dir attr="test"></e-dir>
</div>

pass $scope value of a constructor function to directive

Here i want to pass $scope.myData of app.js to the directive mcsaForm and use the object attributes in myTemplate.html. My code is working properly except the approach which i mentioned above.
app.js
myApp.controller("myCont", function($scope){
.
some code
.
.
$scope.getTypeOfEvent = function(typeOfEvent, idOfEvent)
{
if(typeOfEvent == "MCQ")
{
if(idOfEvent)
{
var indexOfEvent =$scope.namesTwo.Events.indexOf("idOfEvent")+1;
var valueAtIndex = $scope.namesTwo.Events[indexOfEvent].id;
$scope.myData = $scope.namesTwo[valueAtIndex];
console.log($scope.myData); // output: Object {eventId: "001", TimeOfEvent: "2", EventType: "MCQ"}
}
//fetchJsonDir.gettingContData;
$scope.mCSSQ(typeOfEvent,idOfEvent);
}
}
});
myDirective.js
videoCourseApp.directive("mcsaForm",['fetchJsonDir', function(fetchJsonDir){
return{
restrict: "C",
templateUrl: "assets/template_blocks/Preview_forms/myTemplate.html",
scope: {
myData: "="
},
compile: function(scope, element, attrs)
{
$("#mcss_option_list").append("Hello");
},
controller: function($scope){
$scope.onSubmitHidePanel = function()
{
$(".mcsa_form").fadeOut("slow", function(){
$(this).remove();
});-
}
}
}
}]);
myTemplate.html
<div id=mcss_option_list>
</div>
<div>
001, 2, MCQ//print myData.eventId, myData.TimeOfEvent or whatever..
</div>
Use your directive like
<div class="mcsa-form" my-data="myData"></div>
You will get the value in your directive's myTemplate.html
<div id=mcss_option_list>
{{myData.TimeOfEvent}}
</div>

Resources