Dynamically loading an external template in AngularJS - 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.

Related

Angular directive with dynamic controller

I'd like to create a panel directive with a dynamic controller and template so that I can display context-sensitive options in my application. For example, clicking edit on a "Font" setting would show the "Font Options".
I can't find much documentation on it but it looks like it's now possible to specify the controller name on a directive using the name attribute:
app.directive('dynamicPanel', function() {
return {
restrict: 'A',
scope: {
config: '=dynamicPanel'
},
controller: '#',
name: 'ctrlName'
};
});
What I was hoping is that given a panel configuration like this:
vm.panel = {
controller: 'FontCtrl',
template: 'font.template.html'
};
I could then load the panel like so:
<div class="options-panel"
ng-if="vm.panel"
dynamic-panel="vm.panel"
ctrl-name="{{ vm.panel.controller }}"
ng-include="vm.panel.template">
</div>
Unfortunately this does not work. I get the error Badly formed controller string. Also it looks like only the text binding works for the name attribute (#).
Is there a way to bind the name attribute to a dynamic value or an alternative to dynamic controllers in directives.
I figured it out in the end by embracing the component pattern. By registering my options panels as individual directives with their own controllers it simply became a question of rendering the directive markup e.g. <font-options/> and then compiling the "dynamic panel".
http://plnkr.co/edit/Ickkz1GGbDdSbUOUcvfj?p=preview

AngularJs DOM manipulation in directive

I have a directive which shows a list of Users with their names as links.
Inside the the template of this directive I have following loop:
<ng-repeat="user in myctrl.users />
<a href="" >{{user.name}}</a>
Now I want to add an attribute directive to all the anchor tags with
with name edit-confirm-popup as shown below.
<ng-repeat="user in myctrl.users />
<a href="" edit-confirm-popup>{{user.name}}</a>
What I want to do is whenever user click on link of user's name I want to show a popup with popup's html inserted as sibling of the anchor element. I don't want to repeat the popup html when directive is compiled rather I want to insert it dynamically when user clicks on the link.
I am able to achieve following things :
1) attaching click event listener on directive element that is anchor element in directive's link function.
Don't know
i) how I should insert the template as I want to?
ii) I want the current user to be available in event listener so that I can check some conditions before I show the popup.
Here is my directive code :
function editConfirmPopup() {
function linkFunction(scope, iElement, iAttrs){
console.log(iElement);
iElement.on('click',function onEditUser(e){
console.log(e);
console.log('in event handler');
});
}
var directiveDefinitionObject = {
restrict: 'A',
scope: {
user: '=',
populateUser : '&'
},
controller: 'UserEditConfirmController',
controllerAs: 'userEdit',
link : linkFunction,
bindToController: true
};
return directiveDefinitionObject;
}
angular
.module('mymodule')
.directive('editConfirmPopup', editConfirmPopup);
ng-if directive conditionally appends (or removes) - not just shows/hides - the elements on which it operates.
Using the fact that ng-repeat creates a child scope for each iteration, you can then just use a simple toggle variable to decide whether to show/hide the additional HTML (that you call "popup"):
<div ng-repeat="user in myctrl.users">
<a ng-click="showPopup = !showPopup">{{user.name}}</a>
<div ng-if="showPopup">
The "popup" HTML
</div>
</div>

Apply an Angular directive only after data is loaded

I have a directive applied to a table, the outer div has a controller applied to it:
<div ng-controller="MyController">
<table ng-mydirective>
...
</table>
</div>
The controller loads some data and then uses ng-repeat to create some rows/columns in the table.
This is fine so far.
The directive needs to access columns possibly generated by the data, but the directive runs before the controller.
Is it possible to run a directive after the data is loaded/rendered? Or is the only way to achieve this by using $watch on the dom of the table?
<table ng-show="myDataSource.length>0" ng-mydirective>
...
</table>
or how about applying that within your directive's template itself, i.e hide the table until after the data source is populated. But in both cases the problem is that what if you display a table whose query returned 0 results? That means you directive won't show at all. Either way the best way to handle this would be inside the directive. I.e. hide the table unless your $http call executed successfully.
If your directive isn't using an isolate scope, it will automatically receive access to the data member in the controller scope. If you reference that data member in the template, Angular automatically sets up a $watch on it and you don't have to do anything else; otherwise you will need to add your own $watch. If you're using an isolate scope then it's a little more complicated but a similar pattern holds.
You have two ways for doing this
Easy one is using ng-if for requested data field. For example
<table ng-mydirective ng-if="nameOfTheDataSource !== undefined">
...
</table>
Also you can send an attribute to the directive and watch it until data arrives. This is more complicated but seemless to the template level:
Template
<table ng-mydirective ng-model="nameOfTheDataSource">
...
</table>
Directive
angular.module(....)
.directive('ng-mydirective', function() {
return{
scope: {
....
ngModel: '='
}
link: function(scope, element, attrs) {
var firstload = scope.$watch('ngModel', function(value) {
if (scope.ngModel !== undefined) {
// Do what is needed
}
// Deregister itself
firstload();
}
}
}
}

how does angular compile attribute into directives

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/

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