I need some help on how to pass controllers definitions to inner directive nested in outer directive. Please see http://plnkr.co/edit/Om2vKdvEty9euGXJ5qan for a (not)working example.
Is there any way to make angular interpolate what is passed on script.js#46 as item.ctrlName?
How to use controllerAs syntax in inner directive?
1) if you need the inner directive to have the parent controller you can use the require params on the inner directive. Something like this
angular.module('docsTabsExample', [])
.directive('outer', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
templateUrl: '...', // or template
controllerAs: 'outer',
bindToController: true, // This bind the scope with the controller object
controller: function(scope, element, attrs) {
}
}
})
.directive('inner', function() {
return {
require: '^outer',
restrict: 'E',
transclude: true,
scope: {
title: '#'
},
controllerAs: 'inner',
bindToController: true, // This bind the scope with the controller object
templateUrl: '...', // or template
controller: function(scope, element, attrs, tabsCtrl) {
// tabsCtrl and all the methods on the outer directive
},
};
});
2) You have set controller: controller and controller is a empty function, but you can set there a function like i did before and make sure of put the bindToController: true
I found the solution going step down (up ?) with the abstraction. I'm dynamically constructing the whole directive configuration object and then lazy registering it.
See http://plnkr.co/edit/pMsgop6u51zPLqkfWaWT
angular.module('app', ['moduleLazyLoader'])
.controller('mainCtrl', ['$log', function ($log) {
this.list = [
{
name: 'asd',
ctrl: [
'ItemAsdCtrl',
function () {
$log.debug('ItemAsdCtrl');
}
]
},
{
name: 'xyz',
ctrl: [
'ItemXyzCtrl',
function () {
$log.debug('ItemXyzCtrl');
}
]
}
];
}])
.directive('outer', ['factoryLazyLoader', '$log', '$compile', function (factoryLazyLoader, $log, $compile) {
function controller () {}
return {
restrict: 'E',
controller: controller,
controllerAs: 'outer',
bindToController: true,
scope: {
list: '=list'
},
link: function (scope, element, attributes) {
var directives = [];
scope.outer.list = scope.outer.list.map(function (ele, idx) {
var directiveSuffix = ele.ctrl[0];
directiveSuffix[0].toUpperCase();
var directiveName = 'item' + directiveSuffix,
directiveAttrName = directiveName.split(/(?=[A-Z])/).join("-").toLowerCase();
directives.push(directiveAttrName);
factoryLazyLoader.registerDirective([
directiveName,
function () {
return {
restrict: 'E',
replace: true,
controller: ele.ctrl[1],
controllerAs: ele.ctrl[0],
bindToController: true,
template: '<div>{{' + ele.ctrl[0] + ' | json}}</div>',
scope: {
item: '=item'
}
}
}
])
return ele;
});
var tpl = '<div>';
angular.forEach(directives, function (val, idx) {
tpl += '<' + val +' item="outer.list[' + idx + ']">' + '</' + val + '>';
});
tpl += '</div>'
// debugger;
element.replaceWith($compile(tpl)(scope))
}
};
}])
Related
I'm using angular 1.5 to build a directive that I can use in different situations. I want to be able to pass in data from the controller to the directive as an object. My set up is something like this:
Controller:
function ControllerWrapper($scope, $location, $window){
$scope.depth= 99;
$scope.filter = {filters: [{column: 'stage', values: ['Closed']}]};
}
HTML:
<div ng-controller="ControllerWrapper">
<my-great-directive max-depth="{{depth}}" filters="filter"></my-great-directive>
</div>
Directive return
return {
restrict: 'E',
templateUrl: function(element, attributes) {
return attributes.templateUrl;
},
link: function(scope, element, attributes) {
var obj = scope.$eval(attributes.filters);
var dir = scope.$eval(attributes.dir);
scope.$watch("filters",function(newValue,oldValue) {
if(scope.vm.dir != null){
scope.vm.dir.rows = scope.filterTreeGrid();
}
}, true);
},
controller: DirectiveController,
controllerAs: 'vm',
bindToController: true,
scope: {
maxDepth: '#',
filters: '=?'
}
};
From what I've read the filters: '=' should be enough to pass the data through but I'm getting a null after it tries to resolve it. Any suggestions?
Using Angular 1.6 I am defining an directive like so:
angular
.module('myApp')
.directive('lyEntity', lyEntity);
function lyEntity() {
return {
restrict: 'E',
scope: {
model: '=',
type: '#'
},
controller: 'lyEntityController',
controllerAs: 'vm',
bindToController: true,
templateUrl: function (elem, attrs) {
return 'components/_templates/' + attrs.type + '.html'
}
};
}
But using it in another template like so:
<ly-entity model="vm.entity" type="{{vm.type}}"></ly-entity>
will result in templateURL components/_templates/{{vm.type}}.html
How can I hand over the value of vm.type to be used in my templateUrl function?
Yeah, it can't be done the way you're trying to do it because templateUrl function is called before the attributes are interpolated. One way to achieve this would be using ng-include.
return {
restrict: 'E',
scope: {
type: '#'
},
link: function(scope, element, attrs){
$scope.templateUrl = 'components/_templates/' + attrs.type + '.html';
},
template: '<ng-include src="templateUrl"/>'
};
So, construct the template url in controller, have ng-include as the template and point the src to the constructed template url.
Here's a good article on how to have dynamic templates: https://medium.com/angularjs-meetup-south-london/angular-directives-using-a-dynamic-template-c3fb16d03c6d#.mizywdk6s
Thanks to #Chantu, I found a solution which is working for me:
Directive:
angular
.module('myApp')
.directive('lyEntity', lyEntity);
function lyEntity() {
return {
restrict: 'E',
scope: {
model: '=',
type: '='
},
controller: 'lyEntityController',
controllerAs: 'vm',
bindToController: true,
template: '<ng-include src="templateUrl"/>'
};
}
Controller:
$scope.templateUrl = 'components/_templates/' + vm.type + '.html';
and call it:
<ly-entity model="vm.entity" type="vm.type"></ly-entity>
I have created an AngularJS directive as shown below.
In the associated controller, I compute the value of a variable text as "SomeText". I want this text to replace Hello World!! in the template attribute of the directive. How can I do it?
My HTML:
<myp-directive myarg="myObject"></myp-directive>
My Directive:
myApp.directive('mypDirective',function(){
return {
restrict:'E',
scope: {
myarg: '='
},
controller: 'DirectiveCtrl',
controllerAs: 'directiveCtrl',
bindToController: true,
template: 'Hello World!!'
};
}
);
My Controller:
myApp.controller('DirectiveCtrl', function($scope){
var self = this;
$scope.$watch(function() {return self.prediction;}, function (newVal, oldVal)
{
if (newVal !== oldVal && newVal !== null){
var text = "SomeText";
}
});
});
Since you use the controllerAs: 'directiveCtrl' configuration you can simply assign "SomeText" as a variable of the controller (self) and it will be available in the template.
Pascal Precht wrote quite an extensive explanation about controllerAs.
Controller
myApp.controller('DirectiveCtrl', function($scope){
var self = this;
self.text = "Hello World!!";
$scope.$watch(function() {return self.prediction;}, function (newVal, oldVal)
{
if (newVal !== oldVal && newVal !== null){
self.text = "SomeText";
}
});
});
Directive
myApp.directive('mypDirective',function(){
return {
restrict:'E',
scope: {
myarg: '='
},
controller: 'DirectiveCtrl',
controllerAs: 'directiveCtrl',
bindToController: true,
template: '{{directiveCtrl.text}}'
};
}
);
Use scope. Bind the text 'Hello World' to a scope variable (data) and bind it in the template as {{data}}. The change the value of the scope variable from the controller.
Take a look at this fiddle
Directive
myApp.directive('mypDirective', function() {
return {
restrict: 'E',
scope: {
myarg: '='
},
controller: 'DirectiveCtrl',
controllerAs: 'directiveCtrl',
bindToController: true,
template: '{{data}}',
link: function(scope, elem, attr, directiveCtrl) {
scope.data = "Hello World!!!"
}
};
});
I have this directive definition and want to pass currentScriptPath to the TestController.
How do I do that?
(function(currentScriptPath){
angular.module('app', [])
.directive('test', function () {
return {
restrict: 'E',
scope: {},
templateUrl: currentScriptPath.replace('.js', '.html'),
replace: true,
controller: TestController,
controllerAs: 'vm',
bindToController: true
};
});
})(
(function () {
var scripts = document.getElementsByTagName("script");
var currentScriptPath = scripts[scripts.length - 1].src;
return currentScriptPath;
})()
);
TestController.$inject = ['$scope'];
function TestController($scope) {
// try to access $scope.currentScriptPath here
}
As you want to access currentScriptPath in your directive controller. You need to only attach that variable into your current scope inside link function of directive & that scope would be make currentScriptPath available to you controller TestController scope because you have used bindToController: true, in your directive.
Markup
<div ng-controller="Ctrl">
<test></test>
</div>
Directive
(function(currentScriptPath) {
angular.module('app', [])
.directive('test', function() {
return {
restrict: 'E',
scope: {},
templateUrl: currentScriptPath.replace('.js', '.html'),
replace: true,
controller: TestController,
controllerAs: 'vm',
bindToController: true,
link: function(scope, element, attrs) {
scope.currentScriptPath = currentScriptPath; //will update the value of parent controller.
}
};
});
})(
(function() {
var scripts = document.getElementsByTagName("script");
var currentScriptPath = scripts[scripts.length - 1].src;
return currentScriptPath;
})()
);
Controller
function TestController($scope, $timeout) {
var vm = this;
$timeout(function() {
alert($scope.currentScriptPath) //gets called after link function is initialized
})
}
Demo Plunkr
I want to use the Controller As syntax in my Angular directives for two reasons. It's more plain JS and there's no dependency on the $scope service which will not be available in Angular 2.0.
It works great for a single directive but I cannot figure out how to print a property from the controller of a parent directive in a child directive.
function parentCtrl () {
this.greeting = { hello: 'world' };
}
function childCtrl () {}
angular.module('app', [])
.controller('parentCtrl', parentCtrl)
.controller('childCtrl', childCtrl)
.directive('myParent', function () {
return {
scope: {},
bindToController: true,
controller: 'parentCtrl',
controllerAs: 'parent',
template: '<my-child></my-child>'
}
})
.directive('myChild', function () {
return {
scope: {
greeting: '='
},
bindToController: true,
controller: 'childCtrl',
controllerAs: 'child',
template: '<p>{{ greeting.hello }}</p>'
}
});
You have to require the parent controller, the use the link function to inject the parent to the child. The myChild directive would become:
.directive('myChild', function () {
return {
scope: {
// greeting: '=' // NO NEED FOR THIS; USED FROM PARENT
},
bindToController: true, // UNNECESSARY HERE, THERE ARE NO SCOPE PROPS
controller: 'childCtrl',
controllerAs: 'child',
template: '<p>{{ child.greeting.hello }}</p>', // PREFIX WITH VALUE
// OF `controllerAs`
require: ['myChild', '^myParent'],
link: function(scope, elem, attrs, ctrls) {
var myChild = ctrls[0], myParent = ctrls[1];
myChild.greeting = myParent.greeting;
}
}
});
I found that you can use element attributes to pass properties from the parent directive controller's scope to a child.
function parentCtrl () {
this.greeting = 'Hello world!';
}
function myParentDirective () {
return {
scope: {},
controller: 'parentCtrl',
controllerAs: 'ctrl',
template: '<my-child greeting="ctrl.greeting"></my-child>'
}
}
function childCtrl () {}
function myChildDirective () {
return {
scope: {
greeting: '='
},
bindToController: true,
controller: 'childCtrl',
controllerAs: 'ctrl',
template: '<p>{{ ctrl.greeting }}</p><input ng-model="ctrl.greeting" />'
}
}
angular.module('parent', [])
.controller('parentCtrl', parentCtrl)
.directive('myParent', myParentDirective);
angular.module('child', [])
.controller('childCtrl', childCtrl)
.directive('myChild', myChildDirective);
angular.module('app', ['parent', 'child']);