Angularjs, watching scope variable in directive - angularjs

From controller, I need to watch variable which is 2-way bound to directive scope.
As I tested in JsFiddle,
angular 1.1 works well, but angular1.2 does not.
How can I fix it?
http://jsfiddle.net/4091qg9r/3/
var simulationAppModule = angular.module('simulationApp', [])
simulationAppModule.controller('tst', function ($scope) {
$scope.acts = [];
$scope.$watch('acts', function (neww, old) {
console.log('controller', neww)
}, true)
})
simulationAppModule.directive('bn', function () {
return {
restrict: "A",
scope: {
acts: '='
},
link: function ($scope, iElement, iAttrs) {
$scope.addaction = function () {
$scope.acts.push('aaa')
}
}
}
})

The problem is that you are declaring isolated scope in your directive, so $scope.addaction is never reached when you use ng-click="addaction()" on the directive element. ngClick and the addaction are in to different scopes.
One solution is not to use isolated scope, so you can remove
scope: {
acts: '='
},
If you however do need the scope to be isolated child scope, then you can try (not recommended)
$scope.$parent.addaction = function () {
$scope.acts.push('aaa')
}
But the best approach is to revise your set up, because addaction function should be declared in controller, not in directive.

Related

Call method in controller from directive

HTML :
<div id="idOfDiv" ng-show="ngShowName">
Hello
</div>
I would like to call the function which is declared in my controller from my directive.
How can I do this? I don't receive an error when I call the function but nothing appears.
This is my directive and controller :
var d3DemoApp = angular.module('d3DemoApp', []);
d3DemoApp.controller('mainController', function AppCtrl ($scope,$http, dataService,userService,meanService,multipartForm) {
$scope.testFunc = function(){
$scope.ngShowName = true;
}
});
d3DemoApp.directive('directiveName', [function($scope) {
return {
restrict: 'EA',
transclude: true,
scope: {
testFunc : '&'
},
link: function(scope) {
node.on("click", click);
function click(d) {
scope.$apply(function () {
scope.testFunc();
});
}
};
}]);
You shouldn't really be using controllers and directives. Angularjs is meant to be used as more of a component(directive) based structure and controllers are more page centric. However if you are going to be doing it this way, there are two ways you can go about it.
First Accessing $parent:
If your directive is inside the controllers scope you can access it using scope.$parent.mainController.testFunc();
Second (Preferred Way):
Create a service factory and store your function in there.
d3DemoApp.factory('clickFactory', [..., function(...) {
var service = {}
service.testFunc = function(...) {
//do something
}
return service;
}]);
d3DemoApp.directive('directiveName', ['clickFactory', function(clickFactory) {
return {
restrict: 'EA',
transclude: true,
link: function(scope, elem) {
elem.on("click", click);
function click(d) {
scope.$apply(function () {
clickFactory.testFunc();
});
}
};
}]);
Just a tip, any time you are using a directive you don't need to add $scope to the top of it. scope and scope.$parent is all you really need, you will always have the scope context. Also if you declare scope :{} in your directive you isolate the scope from the rest of the scope, which is fine but if your just starting out could make things quite a bit more difficult for you.
In your link function you are using node, which doesn't exist. Instead you must use element which is the second parameter to link.
link: function(scope, element) {
element.on("click", click);
function click(d) {
scope.$apply(function() {
scope.testFunc();
});
}

Passing ngModel to directive in controllerAs component

From a directive using the controllerAs declaration, I want to pass a scope variable to a nested directive, that is to be used within. I want any changes from "inside" to be reflected on the outside, and vice versa.
Grabbing the variable through an isolated scope is not an option, as the controllerAs directive is already asking for an isolated scope. See example here
I have come up with this, in the inner directive:
link: function($scope, el, attrs, ngModel) {
$scope.ngModel = {};
$scope.modelValue = ngModel.$modelValue;
$scope.$watch(function () {
return ngModel.$modelValue;
}, function(newValue) {
$scope.ngModel = newValue;
});
$scope.$watch(function () {
return $scope.ngModel;
}, function(newValue) {
ngModel.$setViewValue(newValue);
}, true);
}
- as seen in the following example, but it seems very hacky. How is this done best?

How to use isolated scope action with parameter from one directive to another

I have a directive I want to pass isolated scoped action with parameter from one directive to another. please see the Plunker.
plnkr
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.singleClick = function (test) {
alert('singleClick'+test);
}
});
app.directive('myButton', [function () {
return {
restrict: 'E',
template: '<input type="button" value="Click" ng-click="click("test")" />',
scope: { onSingleclick: '&singleclickFn' },
link: function (scope, iElm, iAttrs, controller) {
scope.click = function (test) {
alert('singleClick'+test);
scope.onSingleclick(test);
}
}
};
}]);
app.directive('myNewButton', [function () {
return {
restrict: 'E',
scope: { singleclick: '&singleclickFn' },
template: '<my-button singleclick-fn="singleclick(test)" />',
};
}]);
In controller method I have got parameter is undefined.
To pass data from directive with isolated scope to the parent scope pass an object as argument instead of the primitive. So in your myButton directive use something like
ng-click="click({inp:'test'})
and access the argument as object in the singleClick function of your controller.
In your plunkr i can see you pass the result of the function execution to you directive instead of the function itself. Therefore it is undefined as the function doesn't return anything.
use singleclick-fn="singleClick" to pass the funciton to your directive's scope.
Please make your plunkr work (I get errors in the console if i open it), so i can verify the error

AngularJS : Directive not able to access isolate scope objects

I am trying to put some default values in my directive with Isolate scope. Basically, I need to do some DOM manipulations using the scope object when my directive is bound. Below is my code:
Controller:
angular.module('ctrl').controller('TempCtrl', function($scope, $location, $window, $timeout, RestService, CommonSerivce) {
$scope.showAppEditWindow = function() {
//Binding the directive isolate scope objects with parent scope objects
$scope.asAppObj = $scope.appObj;
$scope.asAppSubs = $scope.appSubscriptions;
//Making Initial Settings
CommonSerivce.broadcastFunction('doDirectiveBroadcast', "");
};
Service:
angular.module('Services').factory('CommonSerivce', function ($rootScope) {
return {
broadcastFunction: function(listener, args) {
$rootScope.$broadcast(listener, args);
}
};
Directive:
angular.module('directives').directive('tempDirective', function() {
return {
restrict : 'E',
scope:{
appObj:'=asAppObj',
appSubs: '=asAppSubs'
},
link : function(scope, element, attrs) {},
controller : function ($scope,Services,CommonSerivce) {
//Broadcast Listener
$scope.$on('doDirectiveBroadcast', function (event, args) {
$scope.setDefaults();
});
$scope.setDefaults = function() {
//Setting Default Value
alert(JSON.stringify($scope.appSubs)); //Coming as undefined
};
},
templateUrl:"../template.html"
};
});
Custom Directive element:
<temp-directive as-app-obj="asAppObj" as-app-subs="asAppSubs" />
Now, the issue is that while trying to access the isolate scope in the default method inside directive, I aam getting an undefined value whereas the data is coming and is getting bound to the DOM. How can I access the isolate scope in the broadcast listener and modify the directive template HTML? Is there another wasy for handling this?
The problem is: at that time angular does not update its bindings yet.
You should not access your variables like this, try to use angular js binding mechanism to bind it to view (by using $watch for example). Binding to parent scope variables means you're passive, just listen for changes and update other variables or your view. That's how we should work with angular.
If you still need to access it. You could try a workaround using $timeout
$scope.setDefaults = function() {
$timeout(function () {
alert(JSON.stringify($scope.appSubs)); //Coming as undefined
},0);
};
DEMO
It's better to use $watch
angular.module('ctrl', []).controller('TempCtrl', function ($scope, $location, $rootScope) {
$scope.appSubscriptions = "Subscriptions";
$scope.appObj = "Objs";
$scope.showAppEditWindow = function () {
//Binding the directive isolate scope objects with parent scope objects
$scope.asAppObj = $scope.appObj;
$scope.asAppSubs = $scope.appSubscriptions;
};
});
angular.module('ctrl').directive('tempDirective', function () {
return {
restrict: 'E',
replace: true,
scope: {
appObj: '=asAppObj',
appSubs: '=asAppSubs'
},
link: function (scope, element, attrs) {
},
controller: function ($scope, $timeout) {
$scope.$watch("appSubs",function(newValue,OldValue,scope){
if (newValue){
alert(JSON.stringify(newValue));
}
});
},
template: "<div>{{appSubs}}</div>"
};
});
DEMO
By using $watch, you don't need to broadcast your event in this case.
Most likely the isolated scope variable is not available when the directive's controller first instantiates but probably its available when you need it for a following event such as: within a function bound to an ng-click
its just a race condition and the object doesn't arrive exactly when directive's controller loads

Two way binding, shallow $watch, isolate scope not working together

Please refer to this fiddle for the questions. http://jsfiddle.net/AQR55/
1) Why a watch that is attached to an isolate scope property - which is bidirectionally bound to a parent property, is not triggering on changing the parent scope property.
In the fiddle, the below metioned watch is not getting triggered, on changing the parent scope property to which it is bound.
$scope.$watch('acts', function(neww ,old){
console.log(neww)
})
2) ng-click="addaction()" addaction="addaction()". Can this code be put in more elegant way? Because, to perform an action in isolated scope, it seems we need to set bidirectional binding and the attach to ng-click.
3)Can i declare methods inside the isolated scope like shown below? If i do like this, I'm getting .js error.
<isolate-scope-creating-cmp ng-click="isolateCmpClickHandler()"></isolate-scope-creating-cmp>
scope:{
isolateCmpClickHandler:function(){
//If i do like this, I'm getting .js error
}
}
Question 1.
Since you are adding a item to the acts array, you need to set the third parameter in $watch() to true
$scope.$watch('acts', function (neww, old) {
console.log(neww)
}, true);
Demo: Fiddle
Question 2.
Since there is an isolated scope, you need to call the $parent scope's function
<input type="button" bn="" acts="acts" ng-click="$parent.addaction()" value="Add Action" />
Demo: Fiddle
Question 3.
Yes you can, but you need to use a controller
animateAppModule.directive('bn', function () {
return {
restrict: "A",
scope: {
acts: '='
},
link: function ($scope, iElement, iAttrs) {
$scope.$watch('acts', function (neww, old) {
console.log(neww)
}, true)
},
controller: function($scope){
$scope.dosomething = function(){
console.log('do something')
}
}
}
})
Demo: Fiddle
An overall solution could look like
<input type="button" bn="" acts="acts" addaction="addaction()" value="Add Action" />
JS
animateAppModule.controller('tst', function ($scope) {
$scope.acts = [];
$scope.addaction = function () {
$scope.acts.push({
a: "a,b"
})
}
})
animateAppModule.directive('bn', function () {
return {
restrict: "A",
scope: {
acts: '=',
addaction: '&'
},
link: function ($scope, iElement, iAttrs) {
$scope.$watch('acts', function (neww, old) {
console.log(neww)
}, true);
iElement.click(function(){
$scope.$apply('addaction()')
})
}
}
})
Demo: Fiddle

Resources