AngularJS nested directive not getting evaluated - angularjs

I've just begun creating Angular directives (I'm new to the framework, as well), but am running into issues wherein a nested directive seems to be ignored. The basis for my directives' code is UI Bootstrap's "tabs" and "pane" directives.
The gist is that I want to be able to compile a list of "components" inside a "layout". Ultimately, there should also be an attribute on each "component" tag that will instruct the layout to render content from some known template location. For now, however, I can't even get the "link" function inside the component directive to fire, even though I've got two components in my template.
Here's a plunk of my situation:
http://plnkr.co/edit/K4n2Mx3kZyvVYGDyJ7t9

You're mis-using ngTransclude by placing it inside of ngRepeat. It's sort of a chicken/egg situation where since there's nothing to repeat over, nothing gets transcluded.
Also, since you're specifying components in HTML, you don't even need ngRepeat in your template.
http://plnkr.co/edit/aYjdd4skbKC3FEM3lCfY?p=preview
template:
'<section class="layout">' +
'<h4>Before all components</h4>' +
'<div ng-transclude></div>' +
'<h4>After all components</h4>' +
'</section>'

When you use ng-repeat, it creates a new scope and it makes you ng-transclude not in the right scope for injecting the transclusion.
So when you remove the ng-repeat, you get the rendered components.
Now, in order to control the layout, you can either add the elements to the controllers like you do with their scope, and then layout them accordingly in the controller:
// inside the controller
this.addComponentElement = function (componentElement) {
componentElements.push(componentElement);
};
// watch for array changes and handle layout
Or, you can use the transclude function in the compile + link combination to get a reference to the transcluded dom and manipulate its layout:
compile:function(telement, tAttrs, transcludeFn){
return function(scope, element, attrs){
transcludeFn(scope, function(transcludedDom){
// layout the transcludedDom
})
}

Related

Angular 1.5 component inside ng-repeat, template function run just once

I tried to use angular component replacing directive for creating component (a data table).
I want to use the component inside of ng-repeat like:
<div ng-repeat='item in items'>
<my-datatable></my-datatable>
</div>
The my-datatable component code look like this:
angular.module('App').component('myDatatable', {
bindings: {
ctrl: '=?',
datasource: '=?'
},
bindToController: true,
controllerAs: 'dataTable',
template: function(){},
controller: function(){}
});
First problem:
1. template function is not runnig for every instance of my-datatable component.
That mean, if inside my-datatable I have other components generated based by an unique Id of my-datatable (which can be create only in controller), it's difficult to write that code.
2. The nested isolate scope, create o much too long chains of $parent, for make a reference to some scope variable, something like this (if I use two nested my-datatable's):
ng-click="$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.select($parent.$parent.$parent.$parent.dataItem,dataItem)"
So, my question is:
It is a way to avoid this problems using angular component?
Writing controls code with directive without isolate scope, I don't have this problems. That why I want to change my code for all angular components as directives.
I start to change may component into directive, and I remember few things:
For long chains of $parent.$parent..., I create in component controller a variable $parent with reference for parent scope of component, so if I need a reference inside component to outside scope, I can use: dataTable.$parent.
The good thing for isolate scope, it's I can destroy component and recreate, very simple and clean ( all the watchers are removed when I destroyed the isolate scope), which is not possible with directive way with no isolate scope.
That mean,it remain only the problem to generate unique id for each component instance in template function. In other words: how to run template function on each instance of component.
For exemplify:
https://jsfiddle.net/bogdanim36/t3gmzb6z/3/
And made some tests and I discover that it's happen the same with directive inside ng-repeat: template function is not running for every instance of component.
Finally I generate html code in controller, to solve this issue, That mean one more digest cycle. Is not what I want, but i decided it's the best solution in my case.

AngularJs apply custom directive to HTML conditionally

Is there a right way to apply custom directive to HTML template based on some condition
Eg: <li my-custom-directive="{{item}}">
I need to apply "my-custom-directive" only if {{item}} is defined.
This feels like a design problem rather than a technical one.
Rather than apply the custom directive conditionally, simply figure out what to do inside the directive. Semantically, this makes more sense.
For instance, if item is undefined in this case, simply don't do something inside the directive.
Use ng-if, DOM is not inserted until condition is met.
AngularJS leaves a comment within the DOM for its reference,
so <li my-custom-directive="{{item}}"> would not be within the DOM at all until {{item}} is defined.
If you need to add directives dynamically to the DOM from a variable, use $compile provider. I've created myself a directive for such things
angular.module('test', []).directive('directiveName', ['$compile', function($scope) {
return {
link: function($scope, element, attrs, ctrl) {
element.replaceWith($compile(attrs.direciveName)($scope))
}
}
}]);
And you can use it as such:
<div directive-name="{{customDirectiveName}}"></div>
{{customDirectiveName}} being a $scope variable from somewhere else. From this point you could ng-repeat on JSON objects recieved from server, ect.
It depends on your requirement , if you use it has as element instead of attribute you can achieve using ng-if.
for ex in the below code li wouldnt appear in the dom as and when item is undefined,
<my-custom-directive="{{item}}" ng-if="item">
<li>{{item}}</li>
</my-custom-directive>

AngularJS : Directive shell that allows user defined content with directive scope

Here's a plunker example you can see: http://plnkr.co/edit/NQT8oUv9iunz2hD2pf8H
I have a directive that I would like to turn into a web component. I've thought of several ways as to how I can achieve that with AngularJS but am having difficulty with a piece of it. I'm hoping someone can explain my misstep rather than tell me a different way to do it.
Imagine you have a directive component that sets up some shell with css classes maybe some sub components, etc.. but lets the user define the main content of the component. Something like the following:
<my-list items="ctrl.stuff">
<div>List Item: {{ item.name }}</div>
</my-list>
The HTML for the list directive could be something like the following (with OOCSS):
<ul class="mas pam bas border--color-2">
<li ng-repeat="items in item track by item.id" ng-transclude></li>
</ul>
Normally this can be solved in the link function by linking the directives scope to the new content. And it does work for other components. However introducing the ng-repeat seems to break that portion of the control. From what I can tell, the appropriate place might be the compile function but the documentation says the transcludeFn parameter will be deprecated so I'm not sure how to proceed.
I should also note that when using the beta AngularJS, there is either a bug or a new paradigm coming, because this is no longer a problem. It seems like the transcluded content always gets access to the directives scope as well as the outer controllers scope.
I really appreciate any enlightenment on this.
It's by design that content added via ng-transclude will bind with an outer controller scope, not a scope of the current element that ng-transclude is on.
You could solve the problem by copy the ng-transclude's code and modify it a bit to give a correct scope:
.directive('myTransclude', function () {
return {
restrict: 'EAC',
link: function(scope, element, attrs, controllers, transcludeFn) {
transcludeFn(scope, function(nodes) {
element.empty();
element.append(nodes);
});
}
};
});
And replace the ng-transclude with my-transclude in your directive template.
Example Plunker: http://plnkr.co/edit/i7ohGeRiO3m5kfxOehC1?p=preview

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.

AngularJS directive template with repeater

I have directive with this template that includes a repeater:
template: '<div class="btn-group">' +
'{{option.label}}' +
'</div>'
Inside my link function, the following returns an empty array unless I put it inside a $timeout:
var tmp = element.find('.btn');
Link to fiddle: http://jsfiddle.net/dkrotts/XF3RY/1/
Shouldn't these elements be available at link time? If not, is there a cleaner way to handle this situation?
Apparently the ng-repeat happens later, after your link function is run: https://stackoverflow.com/a/13771751/215945
You may want to try using two directives, similar to the tabs and pane directives on the Angular home page example. The radio-buttons directive could define a method (using this) on its controller that the radio-button directive could call when clicked.
For details on how the tabs and pane directives communicate, see https://stackoverflow.com/a/14168699/215945

Resources