How to get modified values from controller to custom directive? - angularjs

I have defined one variable in controller and i have assigned this value to one attribute of custom directive. So on the basis of this value i am showing the modal box template. Now if i click on the cancel button from modal box template then it calls one function from controller which is modifying the variable value to false but it is not hiding the popup box. Please help me to fix it.
(function () {
'use strict';
angular.module('module1').directive('myDirective', function () {
function linkFunction(scope, elem, attrs) {
//scope.openvalue = attrs.openvalue;
scope.closevalue = false;
scope.close = function () {
console.log("Inside Close");
scope.openvalue = false;
scope.closevalue = false;
};
};
return {
templateUrl: 'confirmTemplate.html',
restrict: 'E',
link: linkFunction,
scope: {
confirmtext: '#',
openvalue: '=',
closeconfirm: '&',
submitconfirm: '&'
},
controller: ['$scope', function ($scope) {
$scope.$watch('openvalue', function () {
console.log("OpenValue : " + $scope.openvalue);
});
}]
};
});
})();
Following is the html for opening this modal.
<div class="col-xs-12 options" ng-click="cntrl.flag1 = true">
<div class="row">
<myDirective openvalue="cntrl.flag1" confirmtext="This is the text from directive"
closeconfirm="cntrl.closeconfirm()" submitconfirm="cntrl.submitconfirm()"></myDirective>
<div class="col-xs-9 no-left-right-padding">My text</div>
</div>
</div>
And i want the updated value of openvalue inside html template but it is not working.

It would be more clear to have you codes here, but I think the problem is when you call the function from controller, it doesn't actually modify the variable of controller scope but modal's scope.
In AngularJS scope, any change of inherited variable in child scope will create a local version.
Based on your words, when you open a modal window it will create a new child scope and when you call the function from controller to modify that scope variable, you actually modifying that child scope variable not controller's.
You can simply add console.log($scope.$id); in controller and the function then you should be able to see the scope id is different.
This Fiddle will give you the idea, press Esc key to close the modal window. However, as I said it would be better to have your code to address exact issue.
Based on your code, a quick fix is assign the cntrl object into directive which will make sure your directive refer to the same object.
Change your modal to
<myDirective cntrl="cntrl" confirmtext="This is the text from directive"></myDirective>
in your directive
scope: {
confirmtext : '#',
cntrl : '='
},
in your linkFunction
function linkFunction(scope, elem, attrs){
scope.close = function(){
scope.cntrl.flag1 = false;
}
you still can access closeconfirm and submitconfirm by $scope.cntrl.closeconfirm and $scope.cntrl.submitconfirm respectively.

Related

$scope.$apply() is not updating the parent scope. Is this a binding issue?

Perhaps this is a poor way of doing this, so I'm open to changing this.
I am implementing a drag and drop function through a <drag-item> directive and a <drop-target> directive. The <drop-target> directive successfully calls this function in the parent scope:
$scope.testAddSet = function(){
console.log($scope.workoutItem); // gives me the last value
$scope.thisMonth[0].sets.push($scope.workoutItem);
$scope.$apply(); // needed to update the view, but uses the old value for workoutItem "test"
};
$scope.workoutItem = "test"; // value declared here
but my <drag-item> directive doesn't seem to update the value of $scope.workoutItem,
View:
<div ng-repeat="exercise in exercises">
<drag-item workout-item="workoutItem" exercise="exercise"></drag-item>
</div>
<drag-item> directive:
angular.module('app.directives.dragItem', [])
.directive('dragItem', function(){
return {
restrict: 'E',
scope: {
exercise: '=',
workoutItem: '='
},
templateUrl: "templates/dragTile.html",
link: function(scope, element, attrs){
element.on('dragstart', function (event) {
scope.workoutItem = scope.exercise.name; //correctly updates the local scope, but not the parent scope
console.log(scope.workoutItem);
});
}
};
});
How do I fix this?
Looks like the problem is that you are defining $scope.workoutItem in your parent scope and then directive is creating another workoutItem property at directive's scope level. To avoid this, it is recomended that you use dots(.) to define your models. Please try something like:
$scope.workout = {item: undefined}
and then in your view:
<div ng-repeat="exercise in exercises">
<drag-item workout-item="workout.item" exercise="exercise"></drag-item>
</div>
I think this should solve your problem without modifying the directive itself.

pass data from controller to directive's link?

In my controller :
myApp.controller('homeCtrl', function($scope, $rootScope, $state, 'red';
$rootScope.$on('new_story', function(event, data) {
$scope.cardObj = {key:'value'};
});
});
In my HTML :
<div clickmeee ></div>
<div id="feedContainer" card='{{cardObj}}'> </div>
In my directive :
myApp.directive('clickmeee', function($compile, $rootScope) {
return {
restrict: 'A',
scope: {
card: '#'
},
link: function(scope, element, attrs) {
element.bind('click', function() {   
scope.$watch('card', function(newVal, oldVal) {
alert(scope.card);
});       
});
}
};
});
How do I pass data from controller to this directive. I compile some html and prepend it to the div. All of that is sorted out but I need some data from object I am trying to pass.
Any help??
There are several problems in your code:
you define a scope attribute named 'card', but you use cardObj instead
you use a watch that is completely unnecessary. And worse: you create a new watch every time the element is clicked
you don't define any card attribute on your clickmeee element. Instead, you're placing it on another element, on which the directive is not applied
you're passing the attribute with '#'. That works, but the directive will receive a string, containing the JSONified object, rather than the object itself
you're not showming us where you emit an event that will initialize cardObj in the controller scope
Here is a plunkr showing a working version of your code.
Also, note that using bind('click') is a bad idea. You'd better have a template in your directive and use ng-click in the template, or simply not use a directive at all and just use ng-click directly on the div element.
Bad news. You are doing it wrong all the ways.
Firstly
card='{{cardObj}}' >
this one should be put in the
<div clickmeee ></div>
So you can take it as binded scope variable in your directive registration
Secondly
If you managed to use '#' syntax
card: '#'
it will turn your input to string, not a binded scope. Use '=' instead.
In the end
You dont need to use watch here:
scope.$watch('card', function(newVal, oldVal) {
alert(newVal);
});
since scope.card is binded via '=' connector. Just simple use alert(scope.card). (Need to warn you that alert an object is not a good idea)
I have tried your code here: plunker. Changed a litte bit by using cardObj as string for easier presentation. Does it match your work?
You should watch the card object:
myApp.directive('clickmeee', function() {
return {
restrict: 'A',
scope: {
card: '#'
},
link: function(scope, element, attrs) {
scope.$watch('card', function(value) {
console.log(value);
});
}
};
});
And:
<div clickmeee id="feedContainer" card='{{cardObj}}'> </div>
Whenever the controller changes the cardObj, the directive's watch on card is triggered:
$scope.$apply(function() {
$scope.cardObj = "test";
}

Using a function created in parent scope and also local scope varibles in an AngularJS directive with a template

So I am trying to create a directive which will replace an element with HTML which includes an ng-click which calls a function created in $scope.
An example I made can be found at http://jsfiddle.net/adinas/wbfu9ox3/5/
My HTML is
<div ng-app="myapp" ng-controller="mycontroller">
{{greeting}}
<!--This works-->
<div xyz></div>
<!--This works-->
<div ng-click="dosomething('Click me 2')">Click me 2</div>
<!--The click added in the directive here does NOT work-->
<div abc mytext="Click me 3"></div>
My JS is
var myapp = angular.module('myapp',[]);
myapp.controller('mycontroller', ['$scope', function($scope) {
$scope.greeting = 'Hola!';
$scope.dosomething = function (what) {
alert(what);
};
}]);
//Works
myapp.directive('xyz', function () {
return {
template: '<div ng-click="dosomething(\'Click me 1\')">Click me 1</div>'
};
});
//Does not work
myapp.directive('abc', function () {
return {
template: '<div ng-click="dosomething(\''+attr.mytext+'\')">Click me 3</div>'
};
});
I made 3 elements. The first two show that A. without the directive the click work. B. a directive without using the 'dosomething' function also works.
The 3rd element which tries to pass a parameter to the 'abc' directive and also call the function fails.
How can I also pass a parameter and also use the function? Thanks.
Well, if you look in the console, you will see an error that shows why this is not working:
Error: attr is not defined
You're trying to access an attr variable that doesn't exist.
The way to include attribute values in your directive template is to include them in the directive's scope:
scope: {
mytext: '#'
}
This means that there will now be a mytext variable in the scope of the directive.
This creates a problem, however, since the use of the scope: {} option creates an isolate scope for the directive, and this means that the enclosing controller's scope is not part of the directive's scope.
If feasible, the clean approach here is to also pass in the dosomething function as a parameter of the directive:
<div abc mytext="Click me 3" action='dosomething'></div>
return {
template: '<div ng-click="action(mytext)">Click me 3</div>',
scope: {
mytext: '#',
action: '='
}
};
http://jsfiddle.net/jx1hgjnr/1/
But if that's not what you want to do and you really want to access dosomething from the parent scope, one thing you can do is use $parent.dosomething:
return {
template: '<div ng-click="$parent.dosomething(mytext)">Click me 3</div>',
scope: {
mytext: '#'
}
};
http://jsfiddle.net/c815sqpn/1/
Such directive should have isolated scope, so it can be used everywhere.
That is simple directive that introduce two parameters: func and param.
app.directive('pit', function () {
return {
scope : {
func: '=',
param: '='
},
template: '<button ng-click="func(param)">Click me 3</button>'
};
});
And this is how u use it in html:
<div pit func="test1" param="test2"></div>
My plunker:
http://plnkr.co/edit/YiEWchRPwX6W7bohV3Zo?p=preview

Why does my nested directive disconnect from its ngModel as soon as $setViewValue() is called?

Plunker here.
I have a directive ("child") nested inside another directive ("parent"). It requires ngModel, and ngModelCtrl.$modelValue is shown and kept up-to-date just fine in its template. That is, until I call ngModelCtrl.$setViewValue().
So here is the HTML initialising the directives:
<div parent>
<div child ng-model="content">Some</div>
</div>
And here are the directives:
angular.module('form-example2', [])
.controller('MainCtrl', function($scope){
$scope.content = 'Hi';
})
.directive('parent', function() {
return {
transclude: true,
template: '<div ng-transclude></div>',
controller: function(){
},
scope: {}
};
})
.directive('child', function() {
return {
require: ['ngModel', '^parent'],
transclude: true,
template: '<div>Model: {{model.$modelValue}} (<a style="text-decoration: underline; cursor: pointer;" ng-click="alter()">Alter</a>)<br />Contents: <div style="background: grey" ng-transclude></div></div>',
scope: {},
link: function(scope, elm, attrs, ctrl) {
var ngModelCtrl = ctrl[0];
var parentCtrl = ctrl[1];
scope.model = ngModelCtrl;
// view -> model
scope.alter = function(){
ngModelCtrl.$setViewValue('Hi2');
}
// model -> view
// load init value from DOM
}
};
});
When the model (i.e. content) changes, this change can be seen inside the child directive. When you click the "Alter" link (which triggers a call of $setViewValue()), the model's value should become "Hi2". This is correctly displayed inside the child directive, but not in the model outside the directive. Furthermore, when I now update the model outside the directive, it is no longer updated inside the directive.
How come?
The directives ended up being just fine; the only problem was that the passed model should be an object property. Hence, the directives work if the following modifications are made to the calling code (Plunker):
In the controller, instead of $scope.content = 'Hi';:
$scope.content = {
value: 'Hi'
};
In the template, replace all references to content with content.value:
<input ng-model="content.value" type="text" />
<div parent>
<div child ng-model="content.value">Some</div>
</div>
<pre>model = {{content.value}}</pre>
The reason this works, roughly, is that when Angular passes the reference to the model to the transcluded scope of the parent directive (i.e. the one the child is in), this is only a reference when it refers to an object property - otherwise it is a copy, which Angular cannot watch for changes.
#Josep's answer helped greatly so, even though it did not provide the actual solution, if you're reading this and it's useful, give it a vote :)

Directive does not have access to controller function (AngularJS)

When I declare a directive to use that accesses a controller function, it cannot find the controller.
Here is my directive declared in app.js:
app.directive("delete", function() {
return {
restrict: 'A',
link: function(scope, elem, attr, ctrl) {
elem.bind('click', function(e) {
alertCtrl.alert();
});
}
}
});
Here is my controller:
app.controller('AlertController', function() {
this.alert = function() {
alert('Ahah!');
}
});
And here is my HTML:
<div ng-controller="AlertController as alertCtrl">
<div ng-repeat="i in [1,2,3]">
<img src='image.png' delete />
</div>
</div>
If I click on the image, I get no alert and the console says that alertCtrl is not defined. How come alertCtrl is not defined when you click on the image with the delete directive on it?
If I change the controller to have $scope.alert = function()... it works fine. But I do not want this.
Also, is this the proper way to handle such a situation? If not, what is the best practice?
You need to call, it on the scope. scope has the property alertCtrl which is your controller instance. Your controller alias (alertCtrl) is available when you bind it on the html since scope is implicit there. but when you do it in the javascript i.e in the directive, or another controller you would need to get the controller instance (defined as alias) from the scope as a property.
scope.alertCtrl.alert();
Plnkr

Resources