How to bind a transcluded template to a different controller? - angularjs

I'm trying to pass a template through a directive to a dynamic controller (known at runtime from the directive perspective).
Something like this:
<my-dir ctrl="PersonCtrl">
<h1>{{person.name}} - {{person.age}}</h1>
</my-dir>
var data = {
name: "Alex",
age: "24"
};
function PersonCtrl($scope){
$scope.person = data;
}
myApp.directive('myDir', function($controller){
return {
restrict: "EA",
scope: {
ctrl: "#"
},
transclude: true,
controller: function($scope, $element, $attrs) {
},
template: "<div>{{ctrl}}</div><div ng-transclude></div>",
link: function($scope, $element, $attrs) {
$controller($attrs.foo, {$scope: {}});
}
};
});
see jsFiddle
The controller is found and instantiated, but somehow the binding of the transcluded template to it doesn't work. Do I miss some order requirement or is there a way to bind this controllers scope to the transcluded template?

Found it - should have binded the controller to the $$nextSibling scope!
$controller($attrs.ctrl, {$scope: $scope.$$nextSibling});
updated jsFiddle

Related

Angular - pass object from directive to parent scope on click

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;
})
});
}]);

Get transcluded text in directives controller

I can get some text from my directive into my directives controller like this:
The html:
<my-directive text="Some text"></my-directive>
In the directive, I can get hold of the text like this:
bindToController: {
text: "#"
};
And I could use it like this in the directive's controller:
controller: function() {
this.textUpperCase = this.text.toUpperCase();
}
But how could can I get hold of the text in the directives controller via transclusion? So that I can have the html like this:
<my-directive>Some text</my-directive>
As mentioned in the comments you could use element.html() or transclusion.
But I would prefer transclusion because that's easier to work with the data. You can use $transclude in your controller or transcludeFn in compile or link method.
Here I think the best would be the controller.
Please have a look at this fiddle or the demo below.
I think injecting the $element into controller won't work becasue you would get the uncompiled template with-out the data you're looking for.
angular.module('demoApp', [])
.controller('mainCtrl', function($scope) {
$scope.hello = 'hello world from scope';
})
.directive('upperCase', function() {
return {
restrict: 'E',
transclude: true,
scope: {
},
template: '<div>{{text}}</div>',
controller: function($scope, $transclude, $element) {
$transclude(function(clone, scope) {
//console.log(clone.text(), scope.hello);
console.log(clone);
$scope.text = clone.text().toUpperCase();
//transcludedContent = clone;
//transclusionScope = scope;
});
//console.log($element.html()); // not working will get the uncompiled template from directive
console.log($scope.text); // can be used here too
},
link: function(scope, element, attrs, ctrl, transclude) {
var text = element.html();
//console.log(transclude);
//element.html(text.toUpperCase()); // also working (add ng-transclude to your template)
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demoApp" ng-controller="mainCtrl">
<upper-case>hello world</upper-case>
<upper-case>hello angular</upper-case>
</div>

Unable to pass controller and directive scope into child directive using require

I am trying to pass the controller scope of parent controller and parent directive into a child directive but facing an error saying that the controller is not available. Here is a plunk for that
http://plnkr.co/edit/aahgOK9oFFjcP2y5VkVa?p=preview
HTML:
<div ng-controller="MainCtrl as mc">
<new-dir>
<data-customer-details customer="mc.customers[0]" logger="mc.logger()" version-age="{{mc.age}}"></data-customer-details>
</new-dir>
</div>
OK, so I tinkered with your plunker a bit. I couldn't get it working using Controller As...I had to change it over to $scope injection on the main controller. Then I created a new scope on newDir by setting scope: true.
You don't actually need to require the MainCtrl because these directives are automatically children of that scope anyway.
I changed your 'MainCtrl' to this:
angular.module('plunker').controller('MainCtrl', function ($scope) {
$scope.name = 'World';
$scope.customers = [{
"name": "angularjs 1.4",
"version": "1.4"
}, {
"name": "angularjs 1.3",
"version": "1.3"
}, {
"name": "angularjs 1.2",
"version": "1.2"
}];
$scope.age = 30;
$scope.logger = function() {
console.log('clicked');
}
$scope.ctrlScopeVariable = 'im in controller scope';
})
Minor change to newDir:
function newDir (){
return {
scope: true, //you need this
controller: function($scope){
$scope.val= 'someval';
console.log($scope.$parent.ctrlScopeVariable)
},
link: function(scope, el, attr, ctrl) {
console.log(scope.$parent.name)
}
}
}
And the last directive:
function CustomerDetails() {
var directive = {
scope: {
customer: '=',
logger: '&',
myNewAge: '#versionAge'
},
restrict: 'EA',
require: ['^newDir'],
controllerAs: 'cd',
templateUrl: 'customer-details.html',
link: linkFunction,
controller: function($scope){
console.log($scope.$parent.$parent.ctrlScopeVariable);
var cd = this;
cd.newval = 'new val';
}
};
function linkFunction(scope, elem, attributes, controllers, transclude) {
console.dir(controllers);
scope.fromMainCtrl = scope.$parent.$parent.ctrlScopeVariable
}
return directive;
}
The Plunker
I added a binding to the customer details template that passes in the $scope.ctrlScopeVariable from the main controller, so you can see the MainCtrl scope is accessible form the child directive.
In regards to require, the relevant documentation is here, I think:
If it is necessary to reference the controller or any functions bound
to the controller's scope in the template, you can use the option
controllerAs to specify the name of the controller as an alias. The
directive needs to define a scope for this configuration to be used.
This is particularly useful in the case when the directive is used as
a component.
Looking back at myPane's definition, notice the last argument in its
link function: tabsCtrl. When a directive requires a controller, it
receives that controller as the fourth argument of its link function.
Taking advantage of this, myPane can call the addPane function of
myTabs.
Essentially, you can use it to reference a parent controller on which you need to access some functions or something. Notably, it becomes available under whatever alias you give it as the fourth argument of your link function.
EDIT:
In this Plunker I added a function to the controller of newDir, required newDir in the CustomerDetail directive, and then called that function in the CustomerDetail link function:
CustomerDetails directive:
//some stuff
require: '^newDir',
//some stuff
link: function(scope, el, attr, newDirCtrl) {
console.log(newDirCtrl.doubleNum(100));
}
newDir controller:
controller: function($scope){
this.doubleNum = function(num) {
return num*2
}
// some stuff
}
First you need to declare a variable as callback function:
var MainCtrlFn = function() { .... }
Then, you can set it as parameter to angularJS:
angular.module('plunker').controller('MainCtrl', MainCtrlFn);

How to access the parent scope within a directive link

So on the angular documentation website, you can define Tobias and Jeff
angular.module('docsTransclusionExample', [])
.controller('Controller', ['$scope', function($scope) {
$scope.name = 'Tobias';
}])
.directive('myDialog', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
templateUrl: 'my-dialog.html',
link: function (scope, element) {
scope.name = 'Jeff';
}
};
});
If you do The name is {{name}} it'll say
The name is Tobias
I'm trying to access Tobias in the link function. From inside the link function, how would I get the $scope.name value equal to Tobias?
Since the scope is isolated scope: {}, directive creates a new child scope. In this case the only way to directly access parent scope property is to use scope $parent, reference to parent scope object. For example:
link: function(scope, element) {
scope.name = 'Jeff';
scope.parentName = scope.$parent.name; // Tobias
}
However this is not ideal. This is why you may want to consider more flexible approach:
<my-dialog name="name"></my-dialog>
and define a scope configuration as:
scope: {
parentName: '=name'
}
You will have to use $parent property of the the scope:
scope.$parent.name = 'Jeff';
you can get it through $parent like this:
link: function (scope, element) {
scope.name = 'Jeff';
console.log(scope.name);
console.log(scope.$parent.name);
}
As you have used transclude:true, you can omit scope:{} if you do not have any local variables. Putting scope:{} does not make sense.
so the declaration would be like following
angular.module('docsTransclusionExample', [])
.controller('Controller', ['$scope', function($scope) {
$scope.name = 'Tobias';
}])
.directive('myDialog', function() {
return {
restrict: 'E',
transclude: true,
templateUrl: 'my-dialog.html',
link: function (scope, element) {
// scope.name = 'Jeff';
// if name is in your parent scope, you should be able to get it here
console.log(scope.name);
}
};
});
If you look at the template you will see ng-transclude directive has been used, this means where in template the parent scope's variables will be used there. Hope it makes sense.
I'm just wondering why would you want something like this.
This way you're creating a deppendency between the controller and the directive that shouldn't exist.
If you need input data on your directive, declare it explicitly.

Getting controller model from isolate scope directive

I know that there are a billion questions about isolate scope on here, but I could not find one that directly relates to this exact issue.
I have a property on my controller called Model, so .. $scope.Model. I then have a directive that needs to interact with the Model.
I am wanting to give the directive an isolate scope, but this is proving a bit difficult because doing that means I no longer have access to the model. I thought I could solve this by specifying the model as a two-way binding in the isolate scope, like this.
html
<body ng-app="app" ng-controller="HomeController">
<div custom-directive="Model.Tags"></div>
</body>
javascript
app.directive('customDirective', ['$parse', function($parse) {
return {
restrict: "A",
scope: {
customDirective: "=Model"
},
link: function(scope, element, attributes){
// need to access the controller's "$scope.Model" here for some things.
var model = scope.$eval(attributes.customDirective);
}
}
}])
.controller('HomeController', ['$scope', function($scope) {
$scope.Model = {
Id: "items/1",
Name: "Some Model Object",
Tags: []
};
}]);
I'm really lost as to why this doesn't work. According to all of the isolate scope tutorials I've seen, this should be fine.
notes
Passing the controller as a parameter is not an option. A third party library that I need to interact with already does this, and apparently I can't do that twice on the same HTML element.
Your usage is incorrect. This will work:
<body ng-app="app" ng-controller="HomeController">
<div custom-directive="Model"></div>
</body>
app.directive('customDirective', [function() {
return {
restrict: "A",
scope: {
customDirective: "="
},
link: function(scope, element, attributes){
console.log(scope.customDirective); // this is the $scope.Model due to 2-way binding
}
}
}])
.controller('HomeController', ['$scope', function($scope) {
$scope.Model = {
Id: "items/1",
Name: "Some Model Object",
Tags: []
};
}]);
actually under scope, property name corresponds to the directive's isolate scope property in your case it is customDirective.
SO your code should be:-
var app=angular.module("app",[]);
app.directive('customDirective', ['$parse', function($parse) {
return {
restrict: "A",
scope: {
customDirective: "=model"
},
link: function(scope, element, attributes){
// need to access the controller's "$scope.Model" here for some things.
console.log(scope.customDirective);
}
}
}]);
app.controller('HomeController', ['$scope', function($scope) {
$scope.Model = {
Id: "items/1",
Name: "Some Model Object",
Tags: []
};
}]);
http://jsfiddle.net/4bb4dozv/

Resources