how does angular compile attribute into directives - angularjs

I want to be able to have the second directive phone compiled to alert, how should I do this
<div ng-app="website">
<div ng-controller="MyController">
<div phonebook="phone"> PhoneBook</div>
</div>
</div>
http://jsfiddle.net/x3azn/aPWg8/1/

Your problem is that you are using ng-class as a declarative class (to instantiate directives). That will not work, as the classes that ng-class adds to the elements are added AFTER compilation, and as such are not recognized by the $compile function.
Replacing
var template = '<div ng-class="{phone2: number}" >Phone</div>';
With
var template = '<div class="phone2">Phone</div>';
Will make it work.
I did not understand why you associated the number with the phone2 directive you wanted to instantiate but I figure it is one of two things: either to include it conditionally, or to bind the numbermodel to the directive. If you want to create a conditionally appearing directive, one way would be to use ng-switch, including the directive below it.
If what you wanted was to create a data binding, however, you would do it as such:
var template = '<div class="phone2" data-number="number">Phone</div>';
including a reference to the binding in the directive:
.directive('phone2', function($compile){
return {
restrict: 'AC',
scope:{number:"="},
link: function(s,e,a,c){
Posted a slighly mended edit of your code here: http://jsfiddle.net/aPWg8/2/

Related

Dynamically loading an external template in AngularJS

I'm trying to build an editable table component in AngularJS. The way I want it to work is that when the user clicks an Edit button on a particular row that row is replaced with an "edit template" containing input fields bound to the model. You can see my progress in this Plunker.
I'm using a custom directive to allow me to define a table with editable rows like so:
<table ng-controller="peopleController as peopleCtrl">
<tr editable-row edit-model="person" edit-tmpl="'person-editor.html'" ng-repeat="person in peopleCtrl.people">
<td>{{person.name}}</td>
<td>{{person.age}}</td>
<td>
<button>Edit</button>
</td>
</tr>
</table>
In the editable-row directive's Link function I am creating the "edit template" as a string of html and using $compile to bind the expressions to the directive scope. What I would like to do instead of hard coding the html within the Link function is have the template loaded from an external file referenced from the directives "edit-tmpl" attribute. Note: Setting the templateUrl for the directive won't work as I only want the template to be loaded and injected into the DOM when the user clicks the edit button.
My question is two fold:
1) How can I load the html from the template file referred to by the "edit-tmpl" attribute within the Link function of my directive?
2) As I am new to Angular I am wondering if my approach is in keeping with the AngularJS way of things? From an angular design perspective is it a good idea to have the edit template specified in the HTML via an attribute like this and then loaded within the directive's Link function or is there a better approach that I am missing?
app.directive('editableRow', function($compile){
return{
restrict:'A',
replace:true,
scope: {editModel: '='},
link: function(scope, element, attr){
element.find('button').on('click', function(){
var htmlText = '<div ng-include="{{attr.editTmpl}}"></div>';
OR
var htmlText='<div ng-include src="{{attr.editTmpl}}"></div>';
// I don't know which would work for you. but this is the way to add dynamic template to your directive by just passing appropriate path of template to attr.editTmpl attribute;
var editTmpl = angular.element($compile(htmlText)(scope));
element.replaceWith(editTmpl);
});
}
};
});
But I just wonder with your directive. As here you are using replaceWith method (which will replace your template to existing one in row)
but how would you get your original template or ROW back after editing is done on a row? I'd like to see it brother.

TooltipHtmlUnsafe with content fetched from another div

I'm trying to show a tooltip with html content and the html I would like to be fetched from a children div which has angular markup in it.
Before I switched to ui-bootstrap I used the default bootstrap tooltip, which I created in a directive filling the content with ('.my-tooltip-content',element).html()
Now with ui-bootstrap I tried using the same directive/logic on the ui-tooltip except that now I tried setting attribute variables. The problem is, that I don't know how to get the html from my .my-tooltip-content div rendered inside the tooltip, without loosing the bindings. If I use $interpolate, I get the html of my div properly rendered, but the output is fixed then of course (doesn't update anymore), $compile I have never used before, but I thought this would be the right place for such a use, maybe I don't know how to use it, but $compile gives me an exception about circular structure.
This is a shortened version of what my app looks like:
http://plnkr.co/edit/46NsEPArtm4hph0ROlPS?p=preview
Excerpt:
<div class="hover" tooltip-html-unsafe="" tooltip-trigger="mouseenter" tooltip-placement="bottom">
<div>
<p>Hover me {{booking.name}} !</p>
<!-- about 20 more lines -->
</div>
<div class="my-tooltip-content"><!-- hidden by default -->
<p class="booker-name">{{booking.name}}</p>
<p>Does the name in this tooltip update?</p>
<!-- about 10 more complex lines -->
</div>
</div>
anApp.directive('hover', ['$compile','$interpolate', function($compile,$interpolate){
return{
restrict: 'C',
link: function(scope,element,attrs){
var html1,html2,html3;
var content = $(element).find('.my-tooltip-content');
// This works, but no binding
html1 = $interpolate(content.html())(scope);
// This I'd like to work, but I get "Can't interpolate: {{tt_content}} TypeError: Converting circular structure to JSON"
html2 = $compile(content.html())(scope);
// This brings my html into the tooltip but curlybraces are curlybraces (not compiled)
html3 = content.html();
attrs.tooltipHtmlUnsafe = html1;
}
}
}])
Is there an easy way to to get the html of .my-tooltip-content with all it's angular markup/bindings injected into the tooltip-content variable ?
PS: I know that I could squeeze all the html just directly into the tooltip-html-unsafe attribute, but I have so many lines in my real-world my-tooltip-content that this just wouldn't work (would make the tooltip-content html unreadable and unchangeable for humans).
If you need to use a child div, one solution use a function to pass the interpolated contents of the div to the tooltip. For example:
anApp.directive('hover', ['$compile','$interpolate', function($compile,$interpolate){
return{
restrict: 'C',
link: function(scope,element,attrs){
scope.myTooltip = function() {
var content = $(element).find('.tooltip-content');
return $interpolate(content.html())(scope);
};
}
}
}])
And your html would would need to be updated to use the new scope value for the tooltip:
<div class="hover" tooltip-html-unsafe="{{myTooltip()}}" tooltip-trigger="mouseenter" tooltip-placement="bottom">
Here's the updated plnkr

How do I assign an attribute to ng-controller in a directive's template in AngularJS?

I have a custom attribute directive (i.e., restrict: "A") and I want to pass two expressions (using {{...}}) into the directive as attributes. I want to pass these attributes into the directive's template, which I use to render two nested div tags -- the outer one containing ng-controller and the inner containing ng-include. The ng-controller will define the controller exclusively used for the template, and the ng-include will render the template's HTML.
An example showing the relevant snippets is below.
HTML:
<div ng-controller="appController">
<custom-directive ctrl="templateController" tmpl="template.html"></custom-directive>
</div>
JS:
function appController($scope) {
// Main application controller
}
function templateController($scope) {
// Controller (separate from main controller) for exclusive use with template
}
app.directive('customDirective', function() {
return {
restrict: 'A',
scope: {
ctrl: '#',
tmpl: '#'
},
// This will work, but not what I want
// Assigning controller explicitly
template: '<div ng-controller="templateController">\
<div ng-include="tmpl"></div>\
</div>'
// This is what I want, but won't work
// Assigning controller via isolate scope variable from attribute
/*template: '<div ng-controller="ctrl">\
<div ng-include="tmpl"></div>\
</div>'*/
};
});
It appears that explicitly assigning the controller works. However, I want to assign the controller via an isolate scope variable that I obtain from an attribute located inside my custom directive in the HTML.
I've fleshed out the above example a little more in the Plunker below, which names the relevant directive contentDisplay (instead of customDirective from above). Let me know in the comments if this example needs more commented clarification:
Plunker
Using an explicit controller assignment (uncommented template code), I achieve the desired functionality. However, when trying to assign the controller via an isolate scope variable (commented template code), it no longer works, throwing an error saying 'ctrl' is not a function, got string.
The reason why I want to vary the controller (instead of just throwing all the controllers into one "master controller" as I've done in the Plunker) is because I want to make my code more organized to maintain readability.
The following ideas may be relevant:
Placing the ng-controller tags inside the template instead of wrapping it around ng-include.
Using one-way binding ('&') to execute functions instead of text binding ('#').
Using a link function instead of / in addition to an isolate scope.
Using an element/class directive instead of attribute directive.
The priority level of ng-controller is lower than that of ng-include.
The order in which the directives are compiled / instantiated may not be correct.
While I'm looking for direct solutions to this issue, I'm also willing to accept workarounds that accomplish the same functionality and are relatively simple.
I don't think you can dynamically write a template key using scope, but you certainly do so within the link function. You can imitate that quite succinctly with a series of built-in Angular functions: $http, $controller, $compile, $templateCache.
Plunker
Relevant code:
link: function( scope, element, attrs )
{
$http.get( scope.tmpl, { cache: $templateCache } )
.then( function( response ) {
templateScope = scope.$new();
templateCtrl = $controller( scope.ctrl, { $scope: templateScope } );
element.html( response.data );
element.children().data('$ngControllerController', templateCtrl);
$compile( element.contents() )( templateScope );
});
}
Inspired strongly by this similar answer.

angularjs: click on a tag to invoke a function and pass the current tag to it

I need to handle a click on a tag that enables the opening of a popover.
I try to figure out the best way to do this with angularjs and naturally used hg-click.
<div ng-repeat="photo in stage.photos"
ng-click="openPopoverImageViewer($(this))"
>
$scope.openPopoverImageViewer = function (source) {
alert("openPopoverImageViewer "+source);
}
The issue is that I cannot manage to pass the $(this) to it.
Q1) How to pass the jQuery element?
Q2) In addition, ng-click sounds
to require the function being part of the controller: is it possible
to invoke a function in the partial instead?
You need to stop "thinking in jQuery" :)
Like #oori says, you can pass in photo.
Or better yet, create a custom directive. Directives is the way to go when you need new functionality in your dom, like an element that you can click to open an overlay. For example:
app.directive('popOver', function() {
return {
restrict: 'AE',
transclude: true,
templateUrl: 'popOverTemplate.html',
link: function (scope) {
scope.openOverlay = function () {
alert("Open overlay image!");
}
}
};
});
You can then use this as a custom elemen <pop-over> or as an attribute on regular HTML elements. Here is a plunker to demonstrate:
http://plnkr.co/edit/P1evI7xSMGb1f7aunh3G?p=preview
Update: Just to explain transclusion: When you say that the directive should allow transclusion (transclude:true), you say that the contents of the tag should be sent on to the directive.
So, say you write <pop-over><span>This will be passed on</span></pop-over>, then the span with "This will be passed on" is sent to the directive, and inserted wherever you put your ng-transclude element in your template. So if your pop-over template looks something like this:
<div>
<ng-transclude/>
</div>
Then your resulting DOM after the template has compiled will look like this:
<div>
<span>This will be passed on</span>
</div>
Pass it "photo"
<div ng-repeat="photo in stage.photos" ng-click="openPopoverImageViewer(photo)">
or the current $index
<div ng-repeat="photo in stage.photos" ng-click="openPopoverImageViewer($index)">

How to create this custom control with AngularJS directive?

I'm a bit new to AngularJS and am trying to write a custom select control based on Zurb Foundation's custom select(see here: http://foundation.zurb.com/docs/components/custom-forms.html)
I know I need to use a directive for this but am not sure how to accomplish this.
It's going to have to be reusable and allow for the iterating of whatever array is passed in to it. A callback when the user selects the item from the dropdown list is probably needed.
Here is the markup for the custom Foundation dropdown list:
<select name="selectedUIC" style="display:none;"></select>
<div class="custom dropdown medium" style="background-color:red;">
Please select item
<ul ng-repeat="uic in uics">
<li class="custom-select" ng-click="selectUIC(uic.Name)">{{uic.Name}}</li>
</ul>
</div>
This works for now. I am able to populate the control from this page's Ctrl. However, as you can see, I'd have to do this every time I wanted to use a custom dropdown control.
Any ideas as to how I can turn this baby into a reusable directive?
Thanks for any help!
Chris
If you want to make your directives reusable not just on the same page, but across multiple AngularJS apps, then it's pretty handy to set them up in their own module and import that module as a dependency in your app.
I took Cuong Vo's plnkr above (so initial credit goes to him) and separated it out with this approach. Now this means that if you want to create a new directive, simply add it to reusableDirectives.js and all apps that already have ['reusableDirectives'] as a dependency, will be able to use that new directive without needing to add any extra js to that particular app.
I also moved the markup for the directive into it's own html template, as it's much easy to read, edit and maintain than having it directly inside the directive as a string.
Plnkr Demo
html
<zurb-select data-label="{{'Select an option'}}" data-options="names"
data-change-callback="callback(value)"></zurb-select>
app.js
// Add reusableDirectives as a dependency in your app
angular.module('angularjs-starter', ['reusableDirectives'])
.controller('MainCtrl', ['$scope', function($scope) {
$scope.names = [{name: 'Gavin'}, {name: 'Joseph'}, {name: 'Ken'}];
$scope.callback = function(name) {
alert(name);
};
}]);
reusableDirectives.js
angular.module('reusableDirectives', [])
.directive('zurbSelect', [function(){
return {
scope: {
label: '#', // optional
changeCallback: '&',
options: '='
},
restrict: 'E',
replace: true, // optional
templateUrl: 'zurb-select.html',
link: function(scope, element, attr) { }
};
}]);
zurb-select.html
<div class="row">
<div class="large-12 columns">
<label>{{label || 'Please select'}}</label>
<select data-ng-model="zurbOptions.name" data-ng-change="changeCallback({value: zurbOptions.name})"
data-ng-options="o.name as o.name for o in options">
</select>
</div>
</div>
Is something like this what you're looking for?
http://plnkr.co/edit/wUHmLP
In the above example you can pass in two attribute parameters to your custom zurbSelect directive. Options is a list of select option objects with a name attribute and clickCallback is the function available on the controller's scope that you want the directive to invoke when a user clicks on a section.
Notice there's no code in the link function (this is where the logic for your directive would generally go). All we're doing is wrapping a template so that it's reusable and accepts some parameters.
We created an isolated scope so the directive doesn't need to depend on parent scopes. We binded the isolated scope to the attribute parameters passed in. The '&' means bind to the expression on the parent scope calling this (in our case the callback function available in our controller) and the '=' means create a two way binding between the options attribute so when it changes in the outter scope, the change is reflected here and vice versa.
We're also restricting the usage of this directive to only elements (). You can set this to class, attributes, etc..
For more details the AngularJs directives guide is really good:
http://docs.angularjs.org/guide/directive
Hope this helps.

Resources