My directive's controller is not getting updated with the scope that was set using the '=' two-way-binding.
Here is my directive:
.directive('navigation', function() {
return {
restrict: 'E',
scope: {
selection: '=?selectedItem',
goforward: '&onForward'
},
controller: function() {
var vm = this;
vm.hideForward = !vm.selection
},
controllerAs: 'vm',
bindToController: true,
template: '<button ng-hide="vm.hideForward" ng-click="vm.goforward()">Continue</button>'
};
});
Here is my html file where I use the directive:
<div class='product' ng-click='ctrl.selectedItem = true'>item</div>
<navigation on-forward="ctrl.goForward()" selected-item='ctrl.selectedItem'></navigation>
note: the ctrl.goForward() works just fine.
The vm.selectedItem in the html's controller is only set to true once the product div is clicked.
I expected the ctrl.selectedItem to get passed into my directive's controller and modify the vm.hideForward value, except this is not happening.
I want to be able to change whether the navigation directive is visible and/or active depending on variables that are passed into it from whatever controller's scope I used my directive in.
If I place a <div>{{vm.selectedItem}}</div> inside my directive's template, that does print out properly depending on how ctrl.selectedItem that value changes. My issue is getting the directive's controller to change as well.
How am I setting up this scope binding improperly? I am using angular 1.5.3
You dont need the double brackets for binding a function to ng-click, use ng-click="vm.goforward()"
Pass the function to the directive as on-forward="ctrl.goForward", if you use parenthesis you will be passing the result of the function call instead.
Also for, ng-click='ctrl.selectedItem === true' you should use ng-click='ctrl.selectedItem = true' to set the value, as === is a comparison operator.
ctrl.selectedItem seems to be a variable from the present controller. So while passing it as attribute, you need to pass it as '{{ctrl.selectedItem}}" .
Try using:
**<navigation on-forward="ctrl.goForward()" selected-item='{{ctrl.selectedItem}}'></navigation>**
Try this
.directive('navigation', function() {
return {
restrict: 'E',
scope: {
selection: '=selectedItem',
goforward: '&onForward'
},
controller: function(scope) {
var vm = this;
vm.hideForward = !scope.selection
},
controllerAs: 'vm',
bindToController: true,
template: '<button ng-hide="vm.hideForward" ng-click="vm.goforward()">Continue</button>'
};
});
Related
In my angular application, I defined a directive foo-directive, which is placed in a parent controller as below:
<div ng-app="app">
<div ng-controller="ParentCtrl as parent">
<foo-directive data="parent.city" tab-click="parent.tClick()" tab-click2="parent.tClick2(v)"></foo-directive>
</div>
</div>
pass two method: parent.tClick() and parent.tClick2(v) into the directive and bind to tab-click and tab-click2 attributes respectively. The difference is that the second one has a parameter
and the JS code goes as below:
function ParentCtrl($timeout){
this.city= "London";
this.tClick = function(){
console.log("debugging parent tclick...");
}
this.tClick2 = function(v){
console.log("debugging parent tclick2...");
console.log(v)
}
}
function FirstCtrl() {
this.$onInit = function(){
this.click = function(){
this.tabClick();
this.tabClick2("abc");
}
}
}
function fooDirective() {
return {
scope: {
data: '=',
tabClick : "&",
tabClick2: "&"
},
controller: 'FirstCtrl',
controllerAs: 'foo',
bindToController: true,
template: '<div ng-click="foo.click()">{{ foo.data }}</div>',
link: function ($scope, $element, $attrs, $ctrl) {
//console.log($ctrl.name);
}
};
}
Now the issue comes from the second method this.tabClick2("abc"). There is TypeError message. I have reproduced this issue with this live demo:
https://jsfiddle.net/baoqger/sv4d03hk/1/
any help?
When passing the functions into your directive, you should pass the "reference" to the function, rather than the "result" of the function. Adding the parenthesis, is actually executing the function and returning the result to the directive. As neither function returns a value, they will both be passing undefined.
Given that the value of the parameter you want to pass (v) to the function is inside the directive scope, not that of the parent, you dont need to even tell the directive that the function accepts a parameter. You just pass it to the function inside the directive. ie.
<foo-directive data="parent.city" tab-click="parent.tClick" tab-click2="parent.tClick2"></foo-directive>
According to the docs, using & is then you want to evaluate the result of the attribute:
The & binding allows a directive to trigger evaluation of an expression in the context of the original scope, at a specific time.
Instead, we want to be able to execute the passed attribute, and in particular give it a variable. As such either = (two-way binding) or # (one-way binding) are probably more appropriate for what we're after.
tabClick: "=",
tabClick2: "="
You can also do away with your controller completely by updating your template.
template: '<div ng-click="foo.tabClick();foo.tabClick2(data)">{{ foo.data }}</div>'
Updated JSFiddle
function ParentCtrl($timeout) {
this.city = "London";
this.tClick = function() {
console.log("debugging parent tclick...");
}
}
function FirstCtrl() {}
function fooDirective() {
return {
scope: {
data: '=',
tabClick: "="
},
controller: 'FirstCtrl',
controllerAs: 'foo',
bindToController: true,
template: '<div ng-click="foo.tabClick(data)">{{ foo.data }}</div>',
link: function($scope, $element, $attrs, $ctrl) {
//console.log($ctrl.name);
}
};
}
angular
.module('app', [])
.directive('fooDirective', fooDirective)
.controller('FirstCtrl', FirstCtrl)
.controller('ParentCtrl', ParentCtrl)
<script src="https://code.angularjs.org/1.6.2/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="ParentCtrl as parent">
<foo-directive data="parent.city" tab-click=":: parent.tClick"></foo-directive>
</div>
</div>
PS. if you're concerned about performance using # or =, consider one time bindings using ::. ie. <foo-directive data="parent.city" tab-click=":: parent.tClick" tab-click2=":: parent.tClick2"></foo-directive>
Try the following snippet of code for your "FirstCtrl":
function FirstCtrl() {
this.$onInit = function(){
this.click = function(){
this.tabClick({v: this.data});
}
}
}
As you are using an expression binding (&), you need to explicitly call it with a JSON containing "v" and it's value. like the following:
this.tabClick({v: this.data});
Trying to use the new bindtocontroller for a directive however, struggling to find any working examples of how to execute a function when item changes.
Directive:-
.directive('freetextNote', [function () {
return {
restrict: 'E',
scope: {
activityRef: '#'
},
controller: 'TestCtrl',
templateUrl: 'content/templates/test.html',
controllerAs: 'vm',
bindToController: true,
};
and in the controller :-
$scope.$watch("activityRef",function(newValue,OldValue){
debugger;
if (newValue){
//execute function
alert(JSON.stringify(newValue));
}
});
as i understand, there is no need to use "watch" when using bindtocontroller
I don't use controllerAs and i don't always need to watchIt too. This is because every expression in the template are already "watched" by angular.
If this is only a displayed value you don't need to watch it. However if you need to perform some logic if the value change, you need a $watch or attr.$observe.
I am very new to the concepts of angularJS. The problem I am facing is I have declared a variable $scope.myVariable = true in my controller. I need to toggle the the value of $scope.myVariable from the directive. Is it possible to do that.. if yes how??
Please help..
From a directive, you can access the scope through the link function:
app.directive('myDirective', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
controller: 'MyController',
templateUrl: 'my-template.html',
link: function (scope, element) {
scope.name = 'Jeff';
}
};
});
See https://docs.angularjs.org/guide/directive
If you want to use Angularjs Directive, you can use 3 methods of it's scope
1) true (inherit)
2) false (not inherit)
3) {} (Isolated)
if you use 1) method, you have to work with it's parent scope, because that variable is in parent scope
if you use 2) method you don't have to do anything else.You are working with current scope
if you want more with examples here is a cool link
http://www.w3docs.com/snippets/angularjs/bind-variable-inside-angularjs-directive-isolated-scope.html
You can pass the reference of "myVariable" from your main controller to the directive like this :
//index.html
<my-directive myvar="myVariable"></my-directive>
//myDirective.js
app.directive('myDirective', [function () {
return {
restrict: "E",
require: 'myvar',
scope: {
myvar:"=" //"=" mean, you can set the variable (your toggle function needs that)
},
templateUrl: "./directives/myDirective/myDirectiveView.html",
controller: "myDirectiveController as myDirectiveCtrl"
}
}]);
With this solution, myvar in the directive contains the reference to myVariable of the main controller and you can use it with $scope.myvar in the directive.
EDIT :
for example, if you have a list for object contains src and compression var like this :
{ src:"path/to/img", compressed:false}
<div ng-repeat="imgObj in imgObjList">
<my-directive myvar="imgObj"></my-directive>
</div>
In the my-directive view :
<img ng-src="myvar.src" ng-show="myvar.compressed == true"/>
In your my-directive Controller :
you can set myvar.compressed to true when encoding is finished
I'm opening a custom modal (using angular-bootstrap):
$modal.open({
// templateUrl, etc
controller: 'DemoModalController'
});
It has form handling methods and data loaded to its controller/scope:
modals.controller('DemoModalController', ['$scope', function($scope) {
$scope.formData = {};
});
The template of this modal has a <div location-field> element, it's a custom field I've defined as a directive.
locationField.directive('locationField', function() {
return {
restrict: 'A',
templateUrl: 'location-field.html',
controller: 'LocationFieldController'
};
});
I'm trying to find the best solution for accessing data in the form fields inside the locationField directive.
If there's an element inside <input type="text" ng-model="location.x">, I want the location to be accessible to the DemoModalController
I've tried setting the directive to scope: false, scope: { location: '=' } (and setting a location object in the controller), and also scope: { location: '#' } but none seem to work.
The only way that works so far is calling $scope.$$childTrail.location from the modal controller scope.
Can you try passing the field like:
<div location = "{{location}}">
In angular.js, can a directive controller access data in a page controller that loaded it?
/**
* Profile directive
*/
.directive('profile', function () {
return {
restrict: 'E',
replace: true,
templateUrl: '/partials/users/_profile.html',
scope: {
user: '=',
show: '=?'
},
controller: function($scope, $rootScope){
$scope.show = angular.isDefined($scope.show) ? $scope.show : { follow: true, link: true };
$scope.currentUser = $rootScope.currentUser;
//do stuff here and then set data in UserShowCtrl
}
};
});
The <profile user="user"></profile> method is called from ./users/show.html which uses the UserShowCtrl controller.
Is there anyway I can use scope on the profile directive with its own controller and still be able to pass data to the UserShowCtrl?
Even though the profile can be isolated to its own functionality, it still needs to set some data on the page level in the UserShowCtrl controller.
Here is where _user.html is loading the <profile> directive. The data for the page is served by the UserShowCtrl and has some collections that get updated when things happen, like following a user.
<ol class="following" ng-show="showConnections == 'following'">
<li ng-repeat="following in user.following">
<profile user="connections[following]"></profile>
</li>
</ol>
Right now there is an ng-click="follow(user)"> that is happening in the _profile.html. I would like to be able to have the directive handle this but also update the collections in the UserShowCtrl.
Edit: here is a plunker demonstrating what I'm trying to do:
http://plnkr.co/edit/9a5dxMVg9cKLptxnNfX3
You need to use a service in order to share any information between controllers, directives, services
something like
angular.module('myapp',[]).
service('myservice',function(){
return {a:'A',b:'B'}
}).
controller('mycontroller',['myservice',function(myservice){
//do someting with myservice
}]).
directive('mydirective',['myservice',function(myservice){
//do someting with myservice
}]);
there controller and directive access the same data through the service
You can access the parent scope from your directive with $scope.$parent.myvar.
myvar will be resolved in parent scope, which means prototypical scope inheritance is used to resolve the variable.
However, this does not guarantee that myvar is coming from the same scope as UserShowCtrl since its possible that any scope in between the 'profile' directive and UserShowCtrl's scope may override 'myvar'.
A better solution would be to use directive-to-directive communication. There are generally two ways for directives to communicate:
Through attributes passed into your directive. You've already used this method to import 'user' and 'show' from parent scope into your directive's isolated scope.
Requiring another directive. When you use 'require: ^UserShow', you are specifying that your 'profile' directive requires another directive as a dependency. The '^' means that it will search for the directive on the current element, or any parent element further up the DOM tree. UserShow's controller is then passed to your link function:
.directive('UserShow', function () {
return {
restrict: 'E',
controller: function($scope){
$scope.myvar = 'test';
this.setMyVar = function(var) {
$scope.myvar = var;
}
}
};
});
.directive('profile', function () {
return {
restrict: 'E',
replace: true,
templateUrl: '/partials/users/_profile.html',
require: '^UserShow',
scope: {
user: '=',
show: '=?'
},
controller: function($scope, $rootScope){
},
link: function(scope, element, attr, UserShowCtrl) {
UserShowCtrl.setMyVar('hello world!);
}
};
});
HTML:
<user-show>
<profile>...</profile>
</user-show>
I am not quite sure what your after.
You are already having 2 two-way data bindings, which means that if you change user in your directive, that will also flow to the outside scope.
So you already have a solution in front of you...
So if that is not "good enough", there is something missing in your question.
Here is an illustration: http://plnkr.co/edit/qEH2Pr1Pv7MTdXjHd4bD?p=preview
However, if you use something in your outside template that creates a child scope, binding it as "value" there is NOT enough, you need to have a . in there.
But that is where there is missing something to the question, if you share your show.html I may be able to find where the scope breaks apart and explain why...
Relevant Source from demo.js:
app.directive('profile', function () {
return {
restrict: 'E',
replace: true,
template: '<div><input type="text" ng-model="user"></input></div>',
scope: { //defines an isolate scope.
user: '=',
show: '=?'
},
controller: function($scope, $rootScope){
$scope.show = angular.isDefined($scope.show) ? $scope.show : { follow: true, link: true };
$scope.currentUser = $rootScope.currentUser;
$scope.user = "Changed by scope!";
//do stuff here and then set data in UserShowCtrl
}
};
});
app.controller('UserShowCtrl', function($scope) {
$scope.value = "Value set outside!";
$scope.alertValue = function() {
alert($scope.value);
}
});
Relevant Source from home.html:
<div ng-controller="UserShowCtrl">
{{ value }}
<profile user="value"></profile>
<button ng-click="alertValue()">ALERT!</button>
</div>