(Using Angular 1.5)
I am trying to use the following design pattern for my angular app:
angular.module("app").directive("MyDirective", MyDirective);
function MyDirective () {
return {
bindToController: {
someId: "=",
otherAttr: "#"
},
controller: ["$attrs", MyController],
controllerAs: "ctrl"
};
}
function MyController ($attrs) {
var self = this;
console.log(self);
console.log($attrs);
}
I use my directive in my HTML like this:
<my-directive someId="someParentScope.model._id" otherAttr="1">
In the console, for self I see:
Object { otherAttr: undefined, someId: undefined }
and for $attrs I see:
Object { $attr: Object, $$element: Object, otherattr: "1",
someid: "someParentScope.model._id", otherAttr: undefined, someId: undefined,
$$observers: Object }
How may I accomplish what I am trying to accomplish (i.e. passing data from parent scope into my directive controller constructor), and why is even my single binding ("#" binding) otherAttr undefined in the controller constructor? Is this design pattern is flawed/misguided? Thanks!
looks like you have the naming conventions wrong on the directive, you should define the name on directive attribute as "data-content" and use on the controller as "dataContent"
Take a look this demo i've done
// directive HTML
<my-directive some-id="name" other-attr="1"></my-directive>
//app.js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
});
app.directive('myDirective', function() {
return {
restrict: 'E',
bindToController: {
someId: "=",
otherAttr: "#"
},
controller: ["$attrs", MyController],
controllerAs: "ctrl"
}
});
function MyController ($attrs) {
var self = this;
console.log(self);
console.log($attrs);
}
http://plnkr.co/edit/P3VKdO?p=preview
Related
I have 2 directives, <parent-directive> and <child-directive>. <parent-directive> has its controller called "parentCtrl" and <child-directive> has its controller called "childCtrl"
var myApp = angular.module("myApp",[]);
myApp.directive("parentDirective", function() {
return {
restrict: "E",
templateUrl: "parent.html",
controller: [
"$scope",
function($scope) {
var self = this;
var parentSayHello = function(){
console.log("Hello from parent");
};
var callChildSayHello = function(){
// want to call the say hello method defined in childCtrl here
// ...
};
}
],
controllerAs: "parentCtrl"
};
});
myApp.directive("childDirective", function() {
return {
restrict: "E",
templateUrl: "child.html",
controller: [
"$scope",
function($scope) {
var self = this;
var childSayHello = function(){
console.log("Hello from child");
};
var callParentSayHello = function(){
// want to call the say hello method defined in parentCtrl here
// ...
};
}
],
controllerAs: "childCtrl"
};
});
In html, parentDirective and childDirective are nested directives:
<parent-directive>
<child-directive></child-directive>
</parent-directive>
How could parent directive's controller call a method from child directive's controller and vice versa? (i.e., how to implement callChildSayHello & callParentSayHello as shown in the JS code above)
From doc, Best Practice is to use controller when you want to expose an API to other directives.
Detail examples could be found in the link.
You can also call parent (directive/component) method in child directive via '&' function input:
JS:
angular.module('yourModule').directive('childDirective', [function () {
'use strict';
return {
restrict: 'EA',
scope: {
callback: '&'
},
templateUrl: 'xxx.html'
};
}]);
HTML:
<child-directive callback="methdoInParentDirective()"></child-directive>
I'm using an angular directive to generate a reusable template and show some data in it. The directive also has an ng-click that should take an object and pass it to the parent controller. I'm kind of stuck, not really sure how to pass that data from the directive controller to the scope of the parent controller. I read here but the circumstances are a bit different.
The js code of the directive:
angular.module("app")
.directive('userData', function() {
return {
restrict: "E",
templateUrl: "directives/userData/userData.html",
scope: {
userObj: "="
},
controller: function($scope){
},
link: function(scope, elements, attrs, controller){
}
}
});
And this is the directive html:
<div class="style" ng-click="displayFullDetails(userObj)">{{userObj.first_name}}</div>
Parent controller:
angular.module("app").controller("parentCtrl", ['$scope', function ($scope) {
angular.element(document).ready(function () {
getDataService.getJsonData().then(function (data) {
$scope.users = data.data;
})
});
}]);
I have a directive where I user require and bindToController in the definition.
(function(){
'use strict';
angular.module('core')
.directive('formValidationManager', formValidationManager);
function formValidationManager() {
return {
require: {
formCtrl: 'form'
},
restrict: 'A',
controller: 'FormValidationManagerCtrl as fvmCtrl',
priority: -1,
bindToController: true
};
}
}());
According to the angular docs:
If the require property is an object and bindToController is truthy,
then the required controllers are bound to the controller using the
keys of the require property. This binding occurs after all the
controllers have been constructed but before $onInit is called. See
the $compileProvider helper for an example of how this can be used.
So I expect that in my controller:
(function () {
'use strict';
angular
.module('core')
.controller('FormValidationManagerCtrl', FormValidationManagerCtrl);
FormValidationManagerCtrl.$inject = ['$timeout', '$scope'];
function FormValidationManagerCtrl($timeout, $scope) {
var vm = this;
vm.API = {
validate: validate
};
//vm.$onInit = activate;
function activate() {
}
function validate() {
$scope.$broadcast('fvm.validating');
var firstInvalidField = true;
Object.keys(vm.formCtrl).forEach(function (key) {
var prop = vm.formCtrl[key];
if (prop && prop.hasOwnProperty('$setTouched') && prop.$invalid) {
prop.$setTouched();
if (firstInvalidField) {
$timeout(function(){
var el = $('[name="' + prop.$name + '"]')[0];
el.scrollIntoView();
}, 0);
firstInvalidField = false;
}
}
});
return firstInvalidField;
}
}
})();
vm.formCtrl will be populated with the form controller. However, it is undefined. Why is it undefined? Additionally, I tried to access it in the $onInit function but the $onInit function never got called. What am I doing wrong?
I am using the directive like so:
<form novalidate name="editUserForm" form-validation-manager>
I'm not sure if this is your issue, but I think your directive declaration should look like this:
controller: 'FormValidationManagerCtrl',
controllerAs: 'vm',
rather than:
controller: 'FormValidationManagerCtrl as fvmCtrl',
Looks like this is related to the angular version. I was in 1.4.6 which did not support this. I have upgraded to 1.5.0 in which the code in the OP is working great.
I'm trying to get a directive to work with its own controller:
http://jsfiddle.net/edwardtanguay/xfbgjun5/14/
However, when I click the button:
var template = '<button ng-click="vm.addItem()">add item</button>'+
'<ul><li ng-repeat="item in vm.items">{{item}}</li></ul>';
It tells me that:
Error: vm.add is not a function
even though I define controllerAs and bindToController:
return {
restrict: 'A',
scope: {
datasource: '=',
add: '&'
},
controller: controller,
controllerAs: 'vm',
bindToController: true,
template: template
};
and pass in a scope method from the main controller:
<div item-menu datasource="customers" add="addCustomer()"></div>
Why does it not recognize add as a function on vm?
ADDENDUM: The controller looks like this:
.controller('mainController', function ($scope) {
$scope.customers = ['First','Second','Third'];
$scope.score = 0;
$scope.addCustomer = function() {
$scope.score++;
}
})
You can find the answer to your question in this blog post
If you use Angular 1.3 you need to add to the directive the following line of code:
bindToController: true,
Check the blog post and the code snippets.
When you bind to attributes, you have to bind it to $scope, not the controller.
var controller = function($scope) {
var vm = this;
function init() {
vm.items = angular.copy($scope.datasource);
}
init();
vm.addItem = function() {
$scope.add();
vm.items.push('new one');
}
}
http://jsfiddle.net/xfbgjun5/15/
Using Angular I created a directive like this:
angular
.module('my-module', [])
.directive('myDirective', function () {
return {
restrict: 'E',
templateUrl: currentScriptPath.replace('.js', '.html'),
scope: {
scenarios: '='
},
controller: MyDirectiveController,
controllerAs: 'vm',
bindToController: true,
replace: true
}
});
MyDirectiveController:
MyDirectiveController.$inject = ['$scope'];
function MyDirectiveController($scope) {
var vm = this;
vm.scenarios = $scope.scenarios;
}
My directive HTML template is this:
<div>{{vm.scenarios[0].name}}</div>
In my parent view HTML I'm using the directive this way:
<my-directive scenarios="vm.scenarios"></my-directive>
The parent controller has a property:
vm.scenarios = [] // could be [{ name : "test"}]
As the vm.scenarios of the parent controller gets set after an $http call it is not available when the vm.scenarios of the directive controller is bound to the $scope.scenarios and it doesn't get updated when the parents controller vm.scenarios gets populated eventually.
When adding this to my directives controller, it works but the solution seems wrong to me:
$scope.$watch('scenarios', function(newValue) {
if (newValue !== undefined) {
vm.scenarios = $scope.scenarios;
}
});
This is how you should define your directive controller:
MyDirectiveController.$inject = [];
function MyDirectiveController() {
// nothing here
}
You don't need to use $scope because you already bind to controller instance this. It means that scope config
scope: {
scenarios: '='
},
populates controller instance this object, not $scope object, and hence $scope.scenarios is undefined. With vm.scenarios = $scope.scenarios; in controller you just overwrite correct binding with undefined value.
Demo: http://plnkr.co/edit/lYg15Xpb3CsbQGIb37ya?p=preview