Passing data dynamically to directive's transcluded controller - angularjs

I have a directive for a single popup element. Depending on which of many hundreds of elements that are clicked on, I need to pass different data to the popup when it is displayed. Using a service to store/fetch the data seems like a side-band hack. Storing the data in my top-level controller and allowing the popup controller to inherit seems like poor encapsulation. I'd like to have an isolate scope in the directive and have the popup controller inherit from that. This doesn't seem to work. Any thoughts? Is $broadcast my only option?

I assume that you are working with AngularJS 1.x. I think directive Transclusion is what you are looking for. Transclusion provides a way to include markup that is bound to the parent controller inside the content area of a directive.
Check out this Plunker which is taken from the AngularJS documentation.
.directive('pane', function() {
return {
restrict: 'E',
transclude: {
'title': '?paneTitle',
'body': 'paneBody',
'footer': '?paneFooter'
},
template: '<div style="border: 1px solid black;">' +
'<div class="title" ng-transclude="title">Fallback Title</div>' +
'<div **ng-transclude="body"**></div>' +
'<div class="footer" **ng-transclude="footer"**>Fallback Footer</div>' +
'</div>'
};
<div ng-controller="ExampleController">
<input ng-model="title" aria-label="title"> <br/>
<textarea ng-model="text" aria-label="text"></textarea> <br/>
<pane>
<pane-title><a ng-href="{{link}}">{{title}}</a></pane-title>
<pane-body><p>{{text}}</p></pane-body>
</pane>
</div>
Pane is the directive. Note that the content contained in pane-title and pane-body are being compiled against the parent controller, but are displayed in the ng-transclude portions of the directive mark-up.
You should be able to use this to get your custom data into your popup without the need of a service or $broadcast.

Related

ngTransclude directive doesn't work, can't realize where I'm wrong

Trying to use ngTransclude first time to make custom directive, to achieve the floating label functionality as shown here: Floating label pattern — Angular JS, but it doesn't work.
Here is my directive code:
.directive('floatingLabel', function () {
return {
restrict: 'A',
scope: {
label: '#',
value: '='
},
transclude: true,
templateUrl: 'floating-label-template.html'
}}
)
Directive's template:
<div class="field">
<label ng-show="value" class="show-hide">{{label}}</label>
<div ng-transclude></div>
</div>
I'm trying to use it in the following way:
<input floating-label label="Floating" value="floatingDirective" type="text" class="form-control" ng-model="floatingDirective"/>
Plunker with my code: https://plnkr.co/edit/MC8G4H3B9zEleaBZ7ijJ?p=preview
P.S. I'm using AngularJS 1.4.9
Fixed plunker.
Read this : What is ng-transclude
Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
Why your code not working: you have nothing to be 'transclude'.
A general markup of use directive with ng-transclude may like this:
<directive-with-ng-transclude>
<node-to-transclude></node-to-transclude>
</directive-with-ng-transclude>
After compiled, <node-to-transclude></node-to-transclude> will be appended to the template of your directive where you put ng-transclude.
Notice : ng-model inside ng-transclude , that's why I use a dotted ng-model floatingDirective.

ngModel doesn't change the original variable when in a custom directive with ngInclude

The variable inside the directive changes but outside the directive it doesn't change. Here is the actual code:
main.html
<div class="row" ng-repeat="row in template">
<fx-widget type="{{row.type}}" value="post[row.name]"></fx-widget>
{{post[row.name]}}
</div>
app.js
app.directive('fxWidget', function() {
return {
restrict: 'E',
scope: {
value: '='
},
link: function($scope, $element, $attributes) {
$scope.typeUrl = '/partials/' + $attributes.type + '.html';
},
template: '<div ng-include="typeUrl"></div>',
};
});
partials/text.html
<input type="text" ng-model="value">
Here are my tests and notes on finding the problem:
First I tested to see maybe the ngModel doesn't like binding the value through dictionary/array, so I added the following in main.html:
<input ng-model="post[row.name]"> {{post[row.name]}}
This one worked.
I tested to see if the problem is at ng-include by changing the template variable in the directive:
template: '<input ng-model="value">
and this one actually worked, so the problem is in ng-include
After digging deeper I realized that the ng-include doesn't even send the data back to the directive:
template: '<div ng-include="typeUrl"></div> {{value}}'
// ^^ doesn't work
However inside the partial file it works:
partials/text.html
<input type="text" ng-model="value"> {{value}}
What I am guessing is that ng-include creates a copy of the scope and that's why it doesn't change in the parent scope. How can I make it change the scope?
Also an offtopic question. How can I get rid of ng-include alltogether and manually load the partials? The templateUrl parameter can accept a function with attributes but it doesn't compile the data binds to actual variables, so that's out of the question.
Thank you!

Exposing AngularJS directive property value to parent controller

I am working on an AngularJS app. I am trying to write a reusable component to use throughout my app. For the sake of demonstration, we'll just use a text box. I've created a fiddle of my attempt here. Basically, I'm trying to use a control like this:
<div ng-app="myApp">
<div ng-controller="myController">
<my-control textValue="{{controlValue}}"></my-control>
<br />
You entered: {{controlValue}}
</div>
</div>
Unfortunately, I cannot figure out how to bind a property between the scope of the controller and the scope of the directive. I'm not sure what I'm doing wrong in my fiddle. Can someone please tell me what I'm doing wrong?
Thank you!
You have created directive with isolated scope and you are trying to print value from isolate scope. It isn't right, you can write your directive like this, without isolated scope:
return {
restrict: 'EAC',
template: '<input type="text" ng-model="controlValue"></input>'
};
if you want to setup directive with isolated scope, you should isolate your scope then use $watch for do changes
You were not so far, but you need to be carefull when using {{}}
Remove braces and don't use camelNotation for text-value attribute :
<div ng-app="myApp">
<div ng-controller="myController">
<my-control text-value="controlValue"></my-control>
<br />
You entered: {{controlValue}}
</div>
</div>
Use ng-model attribute :
angular.module('ui.directives', []).directive('myControl',
function() {
return {
restrict: 'EAC',
scope: {
textValue: '='
},
template: '<input type="text" ng-model="textValue"></input>'
};
}
);

AngularJS : transcluding multiple sub elements in a single Angular directive

I was reading about ng-transclude in the AngularJS docs on Creating a Directive that Wraps Other Elements and I think I understand properly what it does.
If you have a directive that applies to an element that has content inside it such as the following:
<my-directive>directive content</my-directive>
it will allow you to tag an element within the directive's template with ng-transclude and the content included in the element would be rendered inside the tagged element.
So if the template for myDirective is
<div>before</div>
<div ng-transclude></div>
<div>after</div>
it would render as
<div>before</div>
<div ng-transclude>directive content</div>
<div>after</div>
My question is if it is possible to somehow pass more then a single block of html into my directive?
For example, suppose the directive usage would look like this:
<my-multipart-directive>
<part1>content1</part1>
<part2>content2</part2>
</my-multipart-directive>
and have a template like:
<div>
this: <div ng-transclude="part2"></div>
was after that: <div ng-transclude="part1"></div>
but now they are switched
<div>
I want it to render as follows:
<div>
this: <div ng-transclude="part2">content2</div>
was after that: <div ng-transclude="part1">content1</div>
but now they are switched
<div>
Perhaps I could somehow bind the HTML value of a node to the model so that I will be able to use it in such a way without calling it "transclude"?
Starting Angular 1.5, it's now possible to create multiple slots. Instead of transclude:true, you provide an object with the mappings of each slot:
https://docs.angularjs.org/api/ng/directive/ngTransclude
angular.module('multiSlotTranscludeExample', [])
.directive('pane', function(){
return {
restrict: 'E',
transclude: {
'title': '?pane-title',
'body': 'pane-body',
'footer': '?pane-footer'
},
template: '<div style="border: 1px solid black;">' +
'<div class="title" ng-transclude="title">Fallback Title</div>' +
'<div ng-transclude="body"></div>' +
'<div class="footer" ng-transclude="footer">Fallback Footer</div>' +
'</div>'
};
})
Cool question. I'm not sure there is a built in way, but you can do it yourself in a pretty generic way.
You can access the transcluded element by passing in the $transclude service like this:
$transclude(function(clone, $scope) {
Where clone will be a copy of the pre-linked transcluded content. Then if you label the content in the element like this:
<div id="content">
<div id="content0">{{text}}</div>
<div id="content1">{{title}}</div>
</div>
You can loop over the content and compile it like this:
$scope.transcludes.push($compile(clone[1].children[i])($scope));
Great! now you just need to put the content in the correct place in your template
'<div id="transclude0"></div>' +
'<div id="transclude1"></div>' +
Then you can in your link function assign the content correctly
angular.element(document.querySelector('#transclude' + i)).append(scope.transcludes[i]);
I set up a fiddle you can play with that has this set up.
Hope this helped!
In our project we have modeled multi site trasclusion after JSF 2's ui:composition, ui:insert, ui:define (see ui:composition).
Implementation consists of three simple directives: ui-template, ui-insert, ui-define (see angularjs-api/template/ui-lib.js).
To define a template one writes the following markup (see angularjs-api/template/my-page.html):
<table ui-template>
<tr>
<td ui-insert="menu"></td>
</tr>
<tr>
<td ui-insert="content"></td>
</tr>
</table>
and declares a directive (see angularjs-api/template/my-page.js):
var myPage =
{
templateUrl: "my-page.html",
transclude: true
};
angular.module("app").
directive("myPage", function() { return myPage; });
and finally, to instantiate the directive one needs to write (see angularjs-api/template/sample.html):
<my-page>
<div ui-define="content">
My content
</div>
<div ui-define="menu">
File
Edit
View
</div>
</my-page>
The working sample can be seen through rawgit: sample.html
See also: Multisite Transclusion in AngularJS

When to use transclude 'true' and transclude 'element' in Angular?

When should I use transclude: 'true' and when transclude: 'element' ?
I cant find anything about transclude: 'element' in the angular docs, they are pretty confusing.
I would be happy if someone could explain this in simple language.
What is the benefit of each option? What is the real difference between them?
This is what I have found :
transclude: true
Inside a compile function, you can manipulate the DOM with the help of transclude linking function or you can insert the transcluded DOM into the template using ngTransclude directive on any HTML tag.
and
transclude: ‘element’
This transcludes the entire element and a transclude linking function is introduced in the compile function. You can not have access to scope here because the scope is not yet created. Compile function creates a link function for the directive which has access to scope and transcludeFn lets you touch the cloned element (which was transcluded) for DOM manipulation or make use of data bound to scope in it. For your information, this is used in ng-repeat and ng-switch.
From AngularJS Documentation on Directives:
transclude - compile the content of the element and make it available to the directive. Typically used with ngTransclude. The advantage of transclusion is that the linking function receives a transclusion function which is pre-bound to the correct scope. In a typical setup the widget creates an isolate scope, but the transclusion is not a child, but a sibling of the isolate scope. This makes it possible for the widget to have private state, and the transclusion to be bound to the parent (pre-isolate) scope.
true - transclude the content of the directive.
'element' - transclude the whole element including any directives defined at lower priority.
transclude: true
So let's say you have a directive called my-transclude-true declared with transclude: true that looks like this:
<div>
<my-transclude-true>
<span>{{ something }}</span>
{{ otherThing }}
</my-transclude-true>
</div>
After compiling and before linking this becomes:
<div>
<my-transclude-true>
<!-- transcluded -->
</my-transclude-true>
</div>
The content (children) of my-transclude-true which is <span>{{ something }}</span> {{..., is transcluded and available to the directive.
transclude: 'element'
If you have a directive called my-transclude-element declared with transclude: 'element' that looks like this:
<div>
<my-transclude-element>
<span>{{ something }}</span>
{{ otherThing }}
</my-transclude-element>
</div>
After compiling and before linking this becomes:
<div>
<!-- transcluded -->
</div>
Here, the whole element including its children are transcluded and made available to the directive.
What happens after linking?
That's up to your directive to do what it needs to do with the transclude function. ngRepeat uses transclude: 'element' so that it can repeat the whole element and its children when the scope changes. However, if you only need to replace the tag and want to retain it's contents, you can use transclude: true with the ngTransclude directive which does this for you.
When set to true, the
directive will delete the original content, but make it available for reinsertion within
your template through a directive called ng-transclude.
appModule.directive('directiveName', function() {
return {
template: '<div>Hello there <span ng-transclude></span></div>',
transclude: true
};
});
<div directive-name>world</div>
browser render: “Hello there world.”
The best way of think about transclusion is a Picture Frame.A picture frame has its own design and a space for adding the picture.We can decide what picture will go inside of it.
When it comes to angular we have some kind of controller with its scope and inside of that we will place a directive that supports transclusion. This directive will have it’s own display and functionality . In non-transluded directive, content inside the directive is decided by the directive itself but with transclusion,just like a picture frame,we can decide what will be inside the directive.
angular.module("app").directive('myFrame', function () {
return {
restrict: 'E',
templateUrl:"frame.html",
controller:function($scope){
$scope.hidden=false;
$scope.close=function(){
$scope.hidden=true;
}
},
transclude:true
}
});
Content inside the directive
<div class="well" style="width:350px;" ng-hide="hidden">
<div style="float:right;margin-top:-15px">
<i class="glyphicon glyphicon-remove" ng-click="close()" style="cursor:pointer"></i>
</div>
<div ng-transclude>
/*frame content goes here*/
</div>
</div>
Call Directive
<body ng-controller="appController">
<my-frame>
<span>My Frame content</span>
</my-frame>
</body>
Example

Resources