AngularJS and nesting Directives - angularjs

I want to create a directive that roughly represents a table. Also I want to create a directive that defines the columns in that table. The table directive ng-repeats on the tr and displays the custom columns per row.
I'm pretty new to angular. Any help would be appreciated, here is the code and jsfiddle link.
As you can see from the fiddle this doesn't work yet.
http://jsfiddle.net/P6Nq4/
HTML:
<div ng-app='myApp' ng-controller='myController'>
Table of People:
<my-table>
<my-td my-data-name='Name'></my-td>
<my-td my-data-name='Description'></my-td>
</my-table>
<button>done</button>
</div>
Javascript:
angular.module('myApp', [])
.controller('myController',function($scope){
$scope.items = [{Name: 'John', Description: 'Awesome'},
{Name: 'Pat', Description: 'Ambiguous'}];
})
.directive('myTable', function(){
return {
link: function(scope,element,attrs){
},
transclude:true,
template: '<table><tr ng-repeat="item in items" ng-transclude>'
+'<td>static column at the end</td></tr></table>'
};
}).directive('myTd', function(){
return {
link: function (scope,element,attrs){
scope.data = attrs.myDataName;
},
template: '<td><input ngModel="eval(\'item.\' + data)" /></td>'
}});

A few things you need to change:
restrict property of the directive definition object. By default, directives are restricted to being html attributes - if you want to use an element (like you have), you need to set restrict to include an E. Link to docs.
You had ngModel="eval(\'item.\' + data)". First issue with this is that in html, angular uses a hyphenated form, and not camelCase, so change ngModel to ng-model. Secondly, the ng-model attribute is automatically evaluated against the scope, so you don't need to use eval. The better approach is to use javascript's bracket notation to access the property. So your ng-model binding becomes:
Finally, if you are re-using a directive, you almost always want to create a new scope, if not an isolate scope, otherwise scopes can interfere with eachother. In your case, the data from the second column overwrites the first column. To fix this, force the creation of a new scope with scope: true
You can see all of the changes in the updated fiddle. I would suggest commenting out the scope line and making sure you understand what happens if you don't have separate scopes, as it's a little bit subtle.
EDIT
Regarding your static column, I think it makes more sense (and it's much simpler) if you include your static column as static html. So instead of putting it in your my-table template, you add it like so:
<my-table>
<my-td my-data-name='Name'></my-td>
<my-td my-data-name='Description'></my-td>
<td>static column</td>
</my-table>
I think this is simpler, and also keeps the html semantic, and representative of what it will be compiled into. This shows it working

Related

Why is binding lost when nesting directive in directive?

The Issue
Using my directive within another directive causes certain bindings to be lost, specifically in the ng-repeat usage.
The directive is used in many areas of my application without issue. It renders a list of inputs which is passed to it's scope from a parent template, like so:
<filter-header filters="filters"></filter-header>
Working Scenario
I have used the following scenario throughout the application and not come across an issue to date.
$routeProvider resolves filters list with WebAPI call for a controller
controller assigns list to its own scope, like so: $scope.filters = filters
template uses filter-header element and passes filters from it's scope to the directive, like so: <filter-header filters="filters"></filter-header>
The filter-header directive then renders the filters using an ng-repeat without issue. $$hashKey is present in each item of the filters collection, indicating the binding's existence.
Failing Scenario
In the following scenario, the binding seems to be lost and the ng-repeat fails to render anything.
$routeProvider resolves filters list with WebAPI call for a controller
controller assigns list to its own scope, like so: $scope.filters = filters
template uses a new element directive, assigns filters from it's scope to the new directive's via an attribute.
directive's template uses filter-header element and passes filters from it's scope to the directive, like so: <filter-header filters="filters"></filter-header>
The filter-header directive then FAILS TO render the filters using an ng-repeat. $$hashKey is NOT present in any item of the filters collection.
Annoyingly, I cannot replicate this in Plunker...
Oddity
The directive has another collection of item's passed to it, columns="columns" (can be seen in the code below). Columns binds correctly and is rendered in it's own ng-repeat. I cannot see how Columns is different from Filters as both are used almost exactly the same way.
Looking Deeper...
I have debugged the process all the way. The filters object is getting all the way to the end scope successfully. If I output the contents of filters to the screen within the final directive, using {{ filters }} I can see all of the filters as expected. However, in the very next line where my ng-repeat begins, no filters are iterated through.
To be certain it is not my list causing issues, I used a list that already works using the working scenario mentioned above and the ng-repeat does not render here.
To be certain it is not my directive's code causing issues, I converted it to a controller and routed directly to it (skipping the nested directive) as in the working scenario mentioned above and the ng-repeat now works.
Using $log to inspect the list, I notice one difference. In the working scenario, all lists contain a $$hashKey property for each item in the list. In the failing scenario, the $$hashKey is missing on all items in the list. This seems to indicate that the binding is being lost for some reason.
Can someone tell me the error in my ways? The only real difference I can see in my usage is that I pass the object to an middle-man directive before before passing it on the the directive where it is used. Strangely, in the very same directive, another list is used in a very similar way and it renders without issue within it's ng-repeat, and it's item's all have the $$hashKey property appended.
Code
There's a lot of code involved, so I'll try and pick out the relevant parts.
RouteProvider
$routeProvider.when('/Pride/Admin/AuditForms/:id', {
templateUrl: '/Templates/Admin/editAuditForm.html',
controller: 'editAuditFormController',
resolve: {
sectionFilters: function (auditFormSectionRepository) {
return auditFormSectionRepository.getFilters().$promise;
},
sectionColumns: function (auditFormSectionRepository) {
return auditFormSectionRepository.getColumns().$promise;
}
}
});
EditAuditForm Controller
prideModule.controller("editAuditFormController", function ($scope, sectionFilters, sectionColumns) {
$scope.sectionFilters = sectionFilters;
$scope.sectionColumns = sectionColumns;
});
EditAuditForm Template
<audit-admin-sections audit-form="auditForm" section-filters="sectionFilters" section-columns="sectionColumns" show-deleted="false"></audit-admin-sections>
AuditAdminSections Directive
prideModule.directive('auditAdminSections', function ($log) {
return {
restrict: 'E',
templateUrl: 'templates/admin/auditFormSections.html',
scope: {
sectionFilters: '=',
sectionColumns: '='
},
controller: function ($scope, $route, $timeout, $location, filterLogic, auditFormSectionRepository) {
// do stuff
}
});
AuditFormSections Template
<filter-header filters="sectionFilters" columns="sectionColumns"></filter-header>
FilterHeader Directive
prideModule.directive('filterHeader', function($log) {
return {
restrict: 'E',
templateUrl: 'templates/common/filterHeader.html',
scope: {
filters: '=',
columns: '='
},
controller: function ($scope, filterItemsRepository) {
$log.info("$scope.filters");
$log.info($scope.filters);
// This will log the filters as expected, however the $$hashKey property is missing from the items
}
});
FilterHeader template
<!-- at this point, {{ filters }} produces the list of filters -->
<form class="form-horizontal" ng-repeat="filter in filters">
<!-- at this point, nothing renders -->
<label class="col-sm-2 control-label">{{ filter.friendlyName }}</label>
</form>
Update 1
I pulled the code out of the directive and into a new controller to mimic the Working Scenario mentioned above. The ng-repeater now functions as expected and the $$hashKey is present again. So something is definitely related to the difference between route->controller->directive->directive vs route->controller->directive.
Worth mentioning, on top of the code above, there are watches on the filters among other usages.
Update 2: Offender Uncovered
I've nailed it. But it makes no sense yet. It seems as though the form element is to blame. Changing to a div resolves the issue. I'm thinking this may be an angular bug as I'm struggling to see why this could work in one scenario and not the other.
Have discovered two fixes so far, but they are more hacks than fixes as I cannot understand the cause of the original problem. I'm still interested if anyone can point out the real issue, but until then this is the best I have:
Solution 1
After much researching, debugging, head scratching, rewriting, I by luck came across a solution. I'm not happy with it, as it doesn't make sense (unless someone can elaborate for me).
The problem seems to be with the form element and using the ng-repeat attribute on it while being nested in angular directives...!!! This has to be a bug, right?
Solution was as simple as changing this:
<form class="form-horizontal" ng-repeat="filter in filters">
<label class="col-sm-2 control-label">{{ filter.friendlyName }}</label>
</form>
to this:
<div class="form-horizontal" ng-repeat="filter in filters">
<label class="col-sm-2 control-label">{{ filter.friendlyName }}</label>
</div>
Solution 2
It seems that the $scope variables were also playing a part in the issue. If I rename each directive's $scope variable name for this particular variable (ie, filters) so that it is unique for each directive, the form ng-repeat works. This makes it seem like there is some sort of conflict within the directives' isolated scopes, however why this is only an issue for form ng-repeat baffles me. As such, it still doesn't explain to me what is the root cause of this behavior.

AngularJS Child Scope for Element

I have a reusable template called profile.html. It looks something like this:
<div>
{{firstName}}
</div>
I have it embedded in another template which is bound to a dedicated controller:
<div ng-include src="'templates/profile.html'"></div>
I want a child $scope created for this div. In the controller for the parent template, I have something like:
$scope.profile = theProfile;
I want the child scope for the profile.html template to be the parent $scope.profile. Something akin to:
<div ng-include src="'templates/profile.html'" ng-scope="{{profile}}"></div>
How can I do this?
It looks like you're basically reinventing directives, by trying to set both the template and scope like that. Also, $scope is an object with a large amount of other properties/objects on it, so setting it to a be another object would be... problematic.
The following would create a directive that merges a passed in profile to the $scope using angular copy, if you really want to do it that way. I'd recommend just using a $scope.profile, though.
.directive('profile', [function(){
return{
templateUrl:'templates/profile.html',
scope:{profile:'='},
controller: function($scope){
angular.copy($scope.profile, $scope) // if you really, really want the properties right on the scope.
}
}
}]
ngInclude automatically creates a child scope. You shouldn't need to explicitly pass some data to it since it can access its parent scope via prototypical inheritance (this might become a problem if your template changes the scope).
The problem here is that your template expects a firstName property to exist in the scope, but it doesn't. So you could change your template to
<div>
{{profile.firstName}}
</div>
but that would couple the template to the profile object, which might be a bad idea.
Another solution would be to manually create the firstName property in the correct scope:
<div ng-include src="'templates/profile.html'"
ng-init="firstName=profile.firstName">
</div>
I'm not very fond of this solution, though, because it can easily get out of hand if the template needs more properties and it breaks the template encapsulation to some extent.
And finally, you could wrap that template within a directive:
directive('whateverMakesSense', function() {
return {
restrict: 'E',
template: '<div>{{data.firstName}}</div>',
scope: { data: '=' }
};
});
...
<whatever-makes-sense data="profile"></whatever-makes-sense>
If you find yourself using that template in many places, I suggest you go for the custom directive approach. It will give you more control, things will be better encapsulated and as a bonus your markup will be more semantic - if you use anything but whatever-makes-sense, of course. :)

Use ngModel with plain ngController instead of directive?

In my application I would like to preserve the option of using plain controllers for certain sections of code - as opposed to creating directives for one-off things that will never be re-used.
In these cases I often want to publish some data from the controller to be used in the contained section. Now, I am aware that I could simply bind items in the controller's scope, however I'd like to specify the "model" location explicitly just to make the code more maintainable and easier to read. What I'd like to use is ng-model as it would be used on a custom directive, but just along side my plain controller:
<div ng-controller="AppController" ng-model='fooModel'>
{{fooModel}}
</div>
However I can see no way to get a reference to the generated ngModelController without using a directive and the 'require' injection.
I am aware that I could make my own attribute fairly easily by injecting the $attr into my controller and do something like:
<div ng-controller="AppController" my-model='fooModel'>
{{fooModel}}
</div>
In which case I just manually take or parse the myModel value and stick my model into the $scope under that name. However that feels wrong in this case - I really only need one "model" for a controller and I'd prefer not to have to add this boilerplate to every controller when ngModel exists. (It's the principle of the thing!)
My questions are:
1) Is there some way to use ngModel along with a plain controller to get the effect above?
2) I have been trying to figure out where ngModelControllers are stored so that I could look at the situation in the debugger but have not been able to find them. When using an ngModel directive should I see these in the scope or parent scope? (Where do they live?!?)
UPDATE: As suggested in answers below $element.controller() can be used to fetch the controller. This works (http://plnkr.co/edit/bZzdLpacmAyKy239tNAO?p=preview) However it's a bit unsatisfying as it requires using $evalAsync.
2) I have been trying to figure out where ngModelControllers are stored so that I could look at the situation in the debugger but have not been able to find them. When using an ngModel directive should I see these in the scope or parent scope? (Where do they live?!?)
The answer depends slightly on where you want to access the controller from.
From outside the element with ng-model
It requires "name" attributes on both the element with the ng-model attribute, and a parent form (or ngForm). So say you have the form with name myForm and the element with ng-model attribute with name myInput, then you can access the ngModelController for myFoo from the parent scope as myForm.myInput. For example, for debugging purposes:
<p>myFoo: {{myForm.myInput.$modelValue}}<p>
<form name="myForm">
<div ng-controller="InnerController" name="myInput" ng-model="model.foo"></div>
</form>
as can be seen at http://plnkr.co/edit/IVTtvIXlBWXGytOEHYbn?p=preview
From inside the element with ng-model
Similar to the answer from #pixelbits, using $evalAsync is needed due to the order of controller creation, but you can alternatively use angular.element.controller function to retrieve it:
app.controller('InnerController', function($scope, $element) {
$scope.$evalAsync(function() {
$scope.myModelController = $element.controller('ngModel');
});
});
Used, inside the controller to view it, for debugging purposes, as:
<div ng-controller="InnerController" ng-model="model.foo">
<p>myFoo: {{myModelController.$modelValue}}<p>
</div>
As can be seen at http://plnkr.co/edit/C7ykMHmd8Be1N1Gl1Auc?p=preview .
1) Is there some way to use ngModel along with a plain controller to get the effect above?
Once you have the ngModelController inside the directive, you can change its value just as you would were you using a custom directive accessing the ngModelController, using the $setViewValue function:
myModelController.$setViewValue('my-new-model-value');
You can do this, for example, in response to a user action that triggers an ngChange handler.
app.controller('InnerController', function($scope, $element) {
$scope.$evalAsync(function() {
$scope.myModelController = $element.controller('ngModel');
});
$scope.$watch('myModelController.$modelValue', function(externalModel) {
$scope.localModel = externalModel;
});
$scope.changed = function() {
$scope.myModelController.$setViewValue($scope.localModel);
};
});
Note the extra watcher on $modelValue to get the initial value of the model, as well as to react to any later changes.
It can be used with a template like:
{{model.foo}}
<div ng-controller="InnerController" ng-model="model.foo">
<p><input type="text" ng-model="localModel" ng-change="changed()"></p>
</div>
Note that this uses ngChange rather than a watcher on localModel. This is deliberate so that $setViewValue is only called when the user has interacted with the element, and not in response to changes to the model from the parent scope.
This can be seen at http://plnkr.co/edit/uknixs6RhXtrqK4ZWLuC?p=preview
Edit: If you would like to avoid $evalAsync, you can use a watcher instead.
$scope.$watch(function() {
return $element.controller('ngModel');
}, function(ngModelController) {
$scope.myModelController = ngModelController;
});
as seen at http://plnkr.co/edit/gJonpzLoVsgc8zB6tsZ1?p=preview
As a side-note, so far I seem to have avoided nesting plain controllers like this. I think if a certain part of the template's role is to control a variable by ngModel, it is a prime candidate for writing a small directive, often with an isolated scope to ensure there are no unexpected effects due to scope inheritance, that has a clear API, and uses require to access the ngModelController. Yes, it might not be reused, but it does help enforce a separation of responsibilities between parts of the code.
When you declare directives on an element:
<div ng-controller="AppController" ng-model='fooModel'>
{{fooModel}}
</div>
You can retrieve the controller instance for any directive by calling jQlite/jQuery $element.data(nameOfController), where nameOfController is the normalized name of the directive with a $ prefix, and a Controller suffix.
For example, to retrieve the controller instance for the ngModel directive you can do:
var ngModelController = $element.data('$ngModelController');
This works as long as the ngModel directive has already been registered.
Unfortunately, ngController executes with the same priority as ngModel, and for reasons that are implementation specific, ngModel is not registered by the time that the ngController function executes. For this reason, the following does not work:
app.controller('ctrl', function ($scope, $element) {
var ngModelController = $element.data('$ngModelController');
// this alerts undefined because ngModel has not been registered yet
alert(ngModelController);
});
To fix this, you can wrap the code within $scope.$evalAsync, which guarantees that the directives have been registered before the callback function is executed:
app.controller('ctrl', function ($scope, $element) {
$scope.$evalAsync(function() {
var ngModelController = $element.data('$ngModelController');
alert(ngModelController);
});
});
Demo JSFiddle

AngularJS - ngRepeat inside a custom directive's transclusion is not working as expected

I am having troubles implementing a custom directive with transclude: true that can have a "transclusion" content that is using ngRepeat.
[My case]
I want to implement a set of directives, that are fetching the data that they are supposed to show from a $http service. For that I want to use preLink phase interceptor that Angular provides, so I can catch the data and set it to the scope. That way if I have some dynamic (since this term is well overloaded - I mean a data which structure is unknown until the request is done) data coming from the service, I rely on that, that I will be able to retrieve a list with that dynamic data and store it inside the scope, then loop through that data via ngRepeat inside the HTML. Here comes my problem...
[My Problem]
Angular is not using the list that I am assigning to the scope during preLink.
[A plunkr]
I maded a plunker that illustrated just the problem that I am having.
http://plnkr.co/edit/XQOm4KWgKxRhn3pOWqzy?p=preview
[My question]
I really believe that such functionality is covered by angular and I am just missing something in the puzzle.
Can anyone tell me how to implement such behaviour?
Thanks!
EDIT:
Thank you rchawdry for your answer. Here are some details on my intentions. To make it simple I will try to give you an example.
Let's assume that we have these directives:
1. "page" - This directive is a labeled container for all the page content. Visually it is represented as some div - for header, for content and for other fancy stuff if needed. The directive does not know what is its data before the page loads. As the page loads the directive must retrieve the information for itself and its children from a REST resourse! Then the directive is setting the information needed for itself (label and other stuff) and stores its children content in childrenList scope variable. It creates a scope.
2. "section" - This section can be child of "page". Since "page" is retrieving its data from a server, then the information about how many "section"s does our "page" have is dynamic and we don't know how many "section"s we need to show on the screen. This depends on sectionList that is coming from the back-end. The section itself is almost the same as "page" - it is a labeled container, with the differences that - a). "section" is container of elements; b). "section" does retrieve its data from its parrent instead of making $http request. This directive creates a scope.
3. "element" - For this example, in order not to define many different elements and complicate it, let's assume that I have one element, called "element". It can consist of some "input" with "span" and "button" if needed. It is similar to the "section" with that, that it retrieves the data to show from it's parrent (in the general case, this is "section" or "page"). On the other hand it is different than "section" by the fact that it has no transcluded content.
Now after we have some of the concept here is what I am trying to achieve:
<page>
<element id='element1' someOtherStuffHere...></element>
<section id='static_section1' someOtherStuffHere...>
<element id='element2' someOtherStuffHere...></element>
</section>
<div class="row" ng-repeat="section in sections">
<section dynamic_id='dynamic_section'>
<div class="row" ng-repeat="elem in elements">
<element dynamic_id='dynamic_element'></element>
</div>
</section>
</div>
</page>
well, I believe that what your trying to achieve will be able by adding a ng-repeat attribute to the transcluded template.
by letting angular know about the 'repeat', it is supposed to work.
since plunkr is currently unavaliable, I can't prodivde any preview and do not have your original code. Ill try to recall it:
template: "<div id='container'>" +
"<div class='content' ng-repeat='item in [1]' ng-transclude'></div>" +
"</div>"
edit: http://plnkr.co/edit/xba4pU666OGxBtKtcDwl?p=preview
You've got a scope problem. The controller is using a variable that isn't defined in the controller (arrayListItemsPre and arrayListItemsPost). While they are declared in the directives, accessing them in a transcluded scope is a little tricky.
The easy way is to do the following. This will present the scope variables up to the controller where they can be used.
app.directive('container', function($compile) {
return {
transclude: true,
scope: true,
replace: true,
restrict: 'E',
template: "<div class='container'>" +
"<div class='content' ng-transclude></div>" +
"</div>",
compile: function(cElem, cAttrs) {
return {
pre : function preLink(scope, iElement, iAttrs) {
console.log("Hello from Container preLinkPhase");
scope.$parent.arrayListItemsPre = [1, 2];
},
post : function postLink(scope, iElement, iAttrs) {
scope.$parent.arrayListItemsPost = [1, 2];
}
};
}
};
});
There are other ways to do this that are better but it requires understanding why you're trying to iterate on variables that are defined in the directive. If you're going to be using this directive in another page that has different array elements, you'd have to change the directive code.

Tricky scope binding in angularjs directives

I want to write 'edit in place' directive in angularjs.
I want that directive is reusable, therefore I have following requirements on the directive:
it must be an attirbute that can deocorate any element, that makes sense (div,span,li)
it must support edit button, clicking on that will change set ot displayd elements into input fileds. Typically properties of one object, e.g. contact (number, name)
I disocvere trickery behaviour of scope visibility in the directive that can be seen in this fiddle http://jsfiddle.net/honzajde/ZgNbU/1/.
Comenting out in the directive: template and scope -> contact.number and contact.name are displayed
Comenting out in the directive: scope -> contact.number only is displayed
Not commenting out anything -> nothing is displayed
=> when both are commented out just adding template to the directive makes it render contact.number even though template is not used.
I am asking what are the rules of the game?
<div>
<div ng-controller="ContactsCtrl">
<h2>Contacts</h2>
<br />
<ul>
<li ng-repeat="contact in contacts">
<span edit-in-place="" ng-bind="contact.number"></span> |
<span edit-in-place="" >{{contact.name}}</span>
</li>
</ul>
<br />
<p>Here we repeat the contacts to ensure bindings work:</p>
<br />
<ul>
<li ng-repeat="contact in contacts">
{{contact.number}} | {{contact.name}}
</li>
</ul>
</div>
</div>
var app = angular.module( 'myApp', [] );
app.directive( 'editInPlace', function() {
return {
restrict: 'A',
//scope: { contact:"=" },
template: '<span ng-click="edit()" ng-bind="value"></span><input ng-model="value"></input>',
link: function ( $scope, element, attrs ) {
// Let's get a reference to the input element, as we'll want to reference it.
var inputElement = angular.element( element.children()[1] );
// This directive should have a set class so we can style it.
element.addClass( 'edit-in-place' );
// Initially, we're not editing.
$scope.editing = false;
// ng-click handler to activate edit-in-place
$scope.edit = function () {
$scope.editing = true;
// We control display through a class on the directive itself. See the CSS.
element.addClass( 'active' );
// And we must focus the element.
// `angular.element()` provides a chainable array, like jQuery so to access a native DOM function,
// we have to reference the first element in the array.
inputElement[0].focus();
};
// When we leave the input, we're done editing.
inputElement.prop( 'onblur', function() {
$scope.editing = false;
element.removeClass( 'active' );
});
}
};
});
app.controller('ContactsCtrl', function ( $scope ) {
$scope.contacts = [
{ number: '+25480989333', name: 'sharon'},
{ number: '+42079872232', name: 'steve'}
];
});
You are running into problems because you are misusing angular.
First, a directive should be self-contained, but you are pulling functionality out of it, which makes it less universal and less reusable. In your code, you have functionality in the DOM and in the controller that belongs in the directive. Why?
Second, it's also unclear from your markup and javascript specifically want you want to accomplish when all these pieces are strung together.
Third, in most cases, directives should have their own isolated scope, which is done by declaring a scope object with attributes it should bind. You shouldn't be passing an expression (i.e. {{contact.name}}) inside the directive as it will break the binding and your contact will not be updated when the edit-in-place finishes. The proper way is to establish bi-directional binding through an = property on the scope. ng-bind isn't what you want here: that's scope-specific, so we use it inside the directive's scope. As Valentyn suggested, you could do some magic to get around this, but it's not a good idea and it's super-simple to set it up the right way. What's the issue with doing this by an attribute?
This is all bad Ju-ju.
As I pointed out in your other question on this same topic, you must make your directive self-contained and work with angular, rather than against it. Here's an attribute-based version of the fiddle I gave you previously, meeting the first of your requirements. Please let me know what is wrong specifically with this implementation and we can talk about the angular way of fixing it.
Lastly, if you provide further context on what you need in terms of a "button", I'll incorporate that into the fiddle too.
[update]
It is possible to make the directives work your way, but you will run into problems eventually (or right now, it would seem). All components in an angular app (or any app for that matter) should be as self-contained as is feasible. It's not a "rule" or limitation; it's a "best practice". Similarly, communication between directive components can occur through a controller, but it shouldn't. Ideally, you shouldn't reference the DOM in a controller at all - that's what directives are for.
If your specific purpose is a row that is editable, then that is your directive. It's okay to have a lower-level generic edit-in-place directive that the larger directive uses, but there is still the higher-level directive too. The higher-level directive encapsulates the logic between them. This higher-level component would then require a contact object.
Lastly, no, there isn't necessarily a big difference between ng-bind="var" and {{var}}. But that's not the issue; the issue was where that binding takes place. In your example, a value was passed to the directive instead of a bi-directionally-bound variable. My point was that the directive needs access to the variable so it can change it.
Summary: You are coding in a very jQuery-style way. That's great for coding in jQuery, but it doesn't work so well when coding in Angular. In fact, it causes a lot of problems, like the ones you're experiencing. In jQuery, you would, for example, dynamically insert DOM elements, declare and handle events, and manually bind variables all within a single code block, all manually. In Angular, there is a clean separation of concerns and most of the binding is automatic. In most cases, it leads to javascript code at least two-thirds smaller than the jQuery alternative. This is one of those cases.
That said, I have created a Plunker that contains a more sophisticated version of both the edit-in-place as well as a new higher-level directive to incorporate additional features: http://plnkr.co/edit/LVUIQD?p=preview.
I hope this helps.
[update 2]
These are the answers to your new round of questions. They may be good for your edification, but I already gave you the "angular way" to fix your problem. You will also find that I already addressed these questions (in broader strokes) earlier in my original answer as well as in my update. Hopefully, this makes it more apparent.
Question: "Comenting out in the directive: template and scope -> contact.number and contact.name are displayed"
My Reply: When you do not specify a scope, the directive inherits its parent scope. You bound and interpolated the name and number within the context of the parent, so it "works". Because the directive will alter the value, however, this is not a good way way to solve it. It really should have its own scope.
Question: "Comenting out in the directive: scope -> contact.number only is displayed"
My Reply: You bound a scope property of the parent to the "contact.number" directive, so it will get placed inside during the $digest loop - after the directive has been processed. On the "contact.name", you put it inside the directive, which can only work if the directive codes for transclusion.
Question: "Not commenting out anything -> nothing is displayed"
My Reply: Right. If the directive has its own scope (and this one definitely should), then you must use a defined directive scope property to communicate values, as my several code samples demonstrate. Your code, however, tries to use the parent scope in the directive when we explicitly forbid that by using the scope property in its definition.
Summary: While this second update may be informative (and I hope that it is), it doesn't answer the question beneath your questions: how do I use angular components correctly so that the scope I'm using is always what I think it is? My first post and the subsequent update, answer that question.
Here is little bit updated your fiddle, but it need further improvements to meet full list of your requirements: http://jsfiddle.net/5VRFE/
Key point is:
scope: { value:"=editInPlace" },
Some notes: its better to use ng-show ng-hide directivies for visual appearing-hiding instead of changing css classes. Also its better to spread functionality into different directives to have better separation of concerns (check ngBlur directive)
About your confusion of scope check guide about scopes paragraph "Understanding Transclusion and Scopes": Each directive have separate isolated scopes, if you want to have access from directive's template to controller's scope use directive scope binging ("scope" field of directive definition object). And also transcluded elements have a scope of from where you defined transcluding template.
From the first view those isolated scope sounds little bit strange, but when you have good structured directives (note also that one directive can require another and share bindings) you can find it extremly usefull.

Resources