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.
Related
I am using Angular 1.5.7 version.
I have a directive that takes in the controller name and view name as strings to then invoke the controller respectively.
I am not able to bind username to the invoking controller from the previous controller, I see the value available in my previous controller.
Please can you advise what could be the issue?
myApp.directive("pendingRequests", function() {
return {
restrict: 'E',
controller: "#",
name: "controllerName",
controllerAs: 'pendingReqCtrl',
scope: {},
bindToController: {
username: '=username'
},
templateUrl: function(tElement, tAttrs) {
return tAttrs.templateUrl;
}
};
});
Thank you #westor you saved my day. I just updated my angular and started getting this issue, spent a lot of time fixing it, then I found this. So thought of giving some examples.
Using $onInit in your controller function for binding scope
controller: function () {
this.$onInit = function () {
// your business logic
};
};
In my app, I was binding through scope
myApp.directive('pendingRequests', function () {
return {
scope: {
item:'='
},
bindToController: true,
controller: controller,
controllerAs: 'vm',
controller: function ($scope) {
console.log(this.item); // doesn't logs
this.$onInit = function () {
console.log(this.item); // logs your other directives object
};
}
}
});
using require
myApp.directive('pendingRequests', function () {
return {
scope: {},
bindToController: {},
controller: controller,
controllerAs: 'pendingReqCtrl',
require: {
parent: '^otherDirective'
},
controller: function ($scope) {
console.log(this.parent); // doesn't logs
this.$onInit = function () {
console.log(this.parent); // logs your item object
};
}
}
});
Did you try $onInit to initialize your code?
angularjs doc says this:
Deprecation warning: although bindings for non-ES6 class controllers
are currently bound to this before the controller constructor is
called, this use is now deprecated. Please place initialization code
that relies upon bindings inside a $onInit method on the controller,
instead.
I updated to 1.6.2 and I had to put my code into $onInit method to access the bindings.
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>'
};
});
I am building a huge form that calls various directives to build a complete form. The Main Page calling the Form Builder passes the ng-model data like this:
<div form-builder form-data=“formData”></div>
Then the Form Builder Page calls various child directive to build various sections of the Form:
FormBuilder.html:
<div form-fields></div>
<div photo-fields></div>
<div video-fields></div>
.. etc.. etc...
When using $scope in controller, I had no problem accessing the $scope in the child directives like this:
function formBuilder() {
return {
restrict: 'A',
replace: true,
scope: {
formData: '='
},
templateUrl: 'FormBuilder.html',
controller: function($scope) {
$scope.formSubmit = function() {
// Submits the formData.formFields and formData.photoFields
// to the server
// The data for these objects are created through
// the child directives below
}
}
}
}
function formFields() {
return {
restrict: 'A',
replace: true,
templateUrl: 'FormFields.html',
controller: function($scope) {
console.log($scope.formData.formFields);
}
}
}
function photoFields() {
return {
restrict: 'A',
replace: true,
templateUrl: 'PhotoFields.html',
controller: function($scope) {
console.log($scope.formData.photoFields);
}
}
}
... etc..
But ever since I got rid of the $scope and started using ControllerAs, I am having all sorts of trouble accessing 2 way binding with the Parent - Child Controllers.
function formBuilder() {
return {
restrict: 'A',
replace: true,
scope: {
formData: '='
},
templateUrl: 'FormBuilder.html',
controller: function() {
var vm = this;
console.log(vm.formData); // Its fine here
vm.formSubmit = function() {
// I cannot change formData.formFields and formData.photoFields
// from Child Directive "Controllers"
}
},
controllerAs: ‘fb’,
bindToController: true
}
}
function formFields() {
return {
restrict: 'A',
replace: true,
templateUrl: 'FormFields.html',
controller: function() {
var vm = this;
console.log(vm.formData.formFields);
// No way to access 2 way binding with this Object!!!
}
}
}
function photoFields() {
return {
restrict: 'A',
replace: true,
templateUrl: 'PhotoFields.html',
controller: function() {
var vm = this;
console.log(vm.formData.photoFields);
// No way to access 2 way binding with this Object!!!
}
}
}
Whatever I try, I am reaching a road block. Things I have tried are:
Isolated Scopes: I tried passing formData.formFields and
formData.photoFields as isolated scopes to the child directive,
but I then end up getting the $compile: MultiDir error due to
nested isolated scopes so it is not possible.
If I don’t have
individual directives for each form section and have all of them in
1 directive under formBuilder directive, then it becomes a
humungous directive. The above is just a sketch but each child
directive builds 1 big form put together in the end. So merging them
together is really the last resort since it does become hard to
maintain and unreadable.
I don’t think there is a way to access
Parent directive’s ControllerAs from Child Directive's Controller any other way
from what I have seen so far.
If I use the parent’s ControllerAs in
the child directive template’s ng-model like <input type=“text” ng-model=“fb.formData.formFields.text" />, that works fine, but I
need to access the same from the Child directive’s controller for
some processing which I am unable to do.
If I get rid of the
controllerAs and use the $scope again, it works like before but I am
trying to get rid of the $scope altogether to prepare myself for
future Angular changes.
Since it is an advanced form, I need to have separate directive to handle various form sections and since nested isolated scopes are not allowed since Angular 1.2, it is making it ever harder especially when trying to get rid of $scope using ControllerAs.
Can someone guide me what are my options here please? I thank you for reading my long post.
Basically you need to use require option of directive (require option is used for communicate directive with directive). Which will give access to its parent controller by just mentioning require option in child directive. Also you need to use bindToController: true which will basically add isolated scope data to the directive controller.
Code
function formBuilder() {
return {
restrict: 'A',
replace: true,
bindToController: true,
scope: {
formData: '='
},
templateUrl: 'FormBuilder.html',
controller: function($scope) {
$scope.formSubmit = function() {
// Submits the formData.formFields and formData.photoFields
// to the server
// The data for these objects are created through
// the child directives below
}
}
}
}
Then you need to add require option to child directives. Basically the require option will have formBuilder directive with ^(indicates formBuilder will be there in parent element) like require: '^formBuilder',.
By writing a require options you can get the controller of that directive in link function 4th parameter.
Code
function formFields() {
return {
restrict: 'A',
replace: true,
require: '^formBuilder',
templateUrl: 'FormFields.html',
//4th parameter is formBuilder controller
link: function(scope, element, attrs, formBuilderCtrl){
scope.formBuilderCtrl = formBuilderCtrl;
},
controller: function($scope, $timeout) {
var vm = this;
//getting the `formData` from `formBuilderCtrl` object
//added timeout here to run code after link function, means after next digest
$timeout(function(){
console.log($scope.formBuilderCtrl.formData.formFields);
})
}
}
}
function photoFields() {
return {
restrict: 'A',
replace: true,
require: '^formBuilder',
templateUrl: 'PhotoFields.html',
//4th parameter is formBuilder controller
link: function(scope, element, attrs, formBuilderCtrl){
scope.formBuilderCtrl = formBuilderCtrl;
},
controller: function($scope, $timeout) {
var vm = this;
console.log(vm.formData.photoFields);
//to run the code in next digest cycle, after link function gets called.
$timeout(function(){
console.log($scope.formBuilderCtrl.formData.formFields);
})
}
}
}
Edit
One problem with above solution is, in order to get access to the controller of parent directive in directive controller it self, I've did some tricky. 1st include the the formBuilderCtrl to the scope variable from link function 4th parameter. Then only you can get access to that controller using $scope(which you don't want there). Regarding same issue logged in Github with open status, you could check that out here.
Trying to call a function inside the directive:
angular.module('abc').directive('autocomp', function () {
return {
replace: true,
scope: true,
controller: 'Controller',
controllerAs: 'ctrl',
srchtxt: '#',
scope: {
src: '&'
},
templateUrl: '/dom/abc/auto.html',
link: function (scope, element, attrs) {
scope.$watch('srchtxt', function () {
scope.src(scope.srchtxt});
});
}
}
});
==========================================================================
Method inside the controller:
function search(srchtxt) {
console.log('srchText:', srchText);
}
===========================================================================
HTML:
<abc src="rc.search()"></abc>
I am writing a directive for the md-autocomplete control to which method name can be passed as attributes from the HTML pagee, and then try to execute the method inside the directive.
There's a watch on the textchange in the control in which I call the method to be executed (scope.src(scope.srchText}))
When I type in something into the autocomplete control I get the following error:
TypeError: Cannot use 'in' operator to search for 'rc' in g
Could any one please suggest on what am I missing or doing wrong ?
When you use & to bind controller method into directive, you have to declare exact parameter in your html like
<sml-auto-complete datasrc="cr.query(searchText)"></sml-auto-complete>
// ^
Then when you call this method from directive you need to pass the parameter as object like
scope.$watch('searchText', function () {
scope.datasrc({searchText: scope.searchText}); //not simply scope.datasrc(scope.searchText)
});
Check this demo
I'm aware how I can get parent's directive controller in the child directive's link function.
However, I'd prefer to avoid using link function (and $scope all-together) and have all my code under the controller function of the directive.
angular.directive('parent', function(){
return {
templateUrl: '/parent.html',
scope: true,
bindToController: true,
controllerAs: 'parentCtrl',
controller: function(){
this.coolFunction = function(){
console.log('cool');
}
}
}
});
angular.directive('child', function(){
return {
templateUrl: '/child.html',
require: '^parent',
scope: true,
bindToController: true,
controllerAs: 'childCtrl',
controller: function() {
// I want to run coolFunction here.
// How do I do it?
}
}
});
Any help is appreciated!
The proper pattern for that would be
app.directive('child', function(){
return {
templateUrl: '/child.html',
require: ['child', '^parent'],
scope: true,
bindToController: true,
controllerAs: 'childCtrl',
controller: function() {
this.coolFunction = function () {
this._parent.coolFunction();
}
},
link: function (scope, element, attrs, ctrls) {
var childCtrl = ctrls[0];
var parentCtrl = ctrls[1];
childCtrl._parent = parentCtrl;
}
}
});
The bad thing is that _parent is being exposed to scope with controllerAs, but it will rarely be a problem.
Notice that you won't have access to parent controller from child until link glues them together. Which is fine as long as you use parent controller in child methods.
Controller provides methods and initial properties to view model (and it does it cleaner with controllerAs), link glues the stuff, that's how directives work.
Both $scope and link have their purposes in Angular 1.x and are indispensable even with latest community developments. Banishing them for no valid reason is overzealous and may lead to bad design solutions. The absence of 'link' and 'scope' words in code won't help to make the app easier to port to 2.x. Though learning Angular 2 now and developing proper habits for 1.x will.
You could inject '$element' into the controller and access the parent controller like -
controller: ($element) ->
var parentCtrl = $element.parent().controller('parent');
parentCtrl.coolFunction();
//..........
//..........
This may not be the most transparent way of accessing 'any' parent controller because it requires the specific name of the directive and it is jqlite and not pure Angular.
Found this thread useful - How to access parent directive's controller by requiring it recursively?
EDIT: Thanks to #Dmitry for figuring out that angular doesn't need '.parent' to get the controller. Updated code -
controller: ($element) ->
var parentCtrl = $element.controller('parent');
parentCtrl.coolFunction();
//..........
See here or here
var parentForm = $element.inheritedData('$formController') || ....
var parentForm = $element.controller('form')