ng-repeat inside compiled html for directive - angularjs

I have two directives:
window.app.directive('placeholder', function ($compile, $route, $rootScope) {
return {
restrict: 'AC',
link: function (scope, element, attr) {
// Store the placeholder element for later use
$rootScope["placeholder_" + attr.placeholder] = element[0];
// Clear the placeholder when navigating
$rootScope.$on('$routeChangeSuccess', function (e, a, b) {
element.html('');
});
}
};
});
window.app.directive('section', function ($compile, $route, $rootScope) {
return {
restrict: 'AC',
link: function (scope, element, attr) {
// Locate the placeholder element
var targetElement = $rootScope["placeholder_" + attr.section];
// Compile the template and bind it to the current scope, and inject it into the placeholder
$(targetElement).html($compile(element.html())(scope));
element.html('');
}
};
});
I use them to basically swap out one section with html in another.
If I have the following html:
<div placeholder="footer"></div>
<div section="footer">
<ul ng-model="items">
<li ng-repeat="item in items"> {{item.Description}}</li>
</ul>
</div>
The ng-repeat doesn't seem to be working. If I simply output {{items}} below the , it displays fine. Also, I know binding is working because I can change items and it will update.
Lastly, if I move the ul outside the section it works fine.
So, my question is why does this not work (compile ng-repeat inside directive).
Am I missing something?
EDIT:
What is confusing me, is I can do this:
<div section="footer">
<!-- This Works -->
{{items}}
<!-- This also works -->
<input type="text" ng-model="items[0].Description" />
<!-- This doesn't work -->
<ul ng-model="items">
<li ng-repeat="item in items"> {{item.Description}}</li>
</ul>
</div>

This isn't going to work. It can't evaluate something from another scope without having an exact copy of it in its scope. If you want two directives to communicate use require and setup a way for them to do that if they aren't in a parent child relationship.
A couple of things you should think about. Essentially what you are doing is called transclusion. Section directive would use ng-transclude to capture the client's defined code. Use transclusion and maybe you can evaluate the template into html in the scope of section then using directive communication allow it to pass the HTML block (already evaluated) to the other directive. The only problem is making sure this happens when things change through binding. You're probably going to need some $watches on variables in section in order for placeholder to be notified when things change.
You will probably need a 3rd directive so allow section and placeholder to communicate through. In this example say I have a 3rd directive called broadcaster. Then section and placeholder will require broadcaster (ie require: '^broadcaster'), and it will define some interface for each of the directives to send HTML from section -> placeholder. Something like this:
<div broadcaster>
<div placeholder="footer"></div>
<div section="footer">
<ul>...transcluded content</ul>
</div>
</div>

Related

Manually moving transcluded content

I have a directive foo whose template includes a list via ng-repeat:
<div>
<h5>I want to insert transcluded template into body of the li:</h5>
<ul>
<li ng-repeat='item in items'>
<!-- need item template here -->
</li>
</ul>
</div>
I want the template for each item to (optionally) to be specifiable at the point of usage of the directive:
<foo items='people'>
<h5>{{item.name}}</h5>
</foo>
<foo items='people'>
{{item.name}} is {{item.age}} years old.
</foo>
So I need the innerHTML of the directive (e.g. <h5>{{item.name}}</h5>) to be copied to the marked location in the directive template.
<ng-transclude> does this, but it gives the transcluded items the scope of the directive rather than the item. I also need to be able to optionally pull the item template from somewhere else. So I need to do the transclusion manually.
I have access to the transcluded content during link:, but at that point the list item in question is gone!
<div>
<h5>I need to insert transcluded template into body of the li:</h5>
<ul>
<!-- ngRepeat: item in items -->
</ul>
</div>
I think I need to do it during compile, but the transclusion function passed to the compile function is deprecated.
Found a way to do it with a second directive, but that seems unnecessary...
You can achieve that using $interpolate service and changing a bit your approach please see demo here http://plnkr.co/edit/bSb7fEWiMTdNVJYyiXD8?p=preview
set your dynamic template as attribute in directive.
<foo template="'<h1>{{item.name}}</h1>'" items="people"></foo>
and change your directive to :
app.directive('foo', function($interpolate) {
return {
scope: {
items: '=',
template:'='
},
restrict: 'E',
transclude: true,
templateUrl: 'foo-template.html',
link: function(scope, element, attributes, controller, transclude) {
//interpolate your template like below
scope.getValue= function(item) {
var exp = $interpolate(scope.template);
var result =exp({item:item})
return result;
}
}
}
});
and in your template use ng-bind-html
<li ng-repeat='item in items'>
<div ng-bind-html="getValue(item)"></div>
</li>
don't forget to add ngSanitize module to your app

Can an angular directive contain a controller?

Or perhaps a better question is, should a directive contain a controller?
For reasons of separation, my index.html is a simple file. Everything is rendered into it via templates. So my index.html is real simple:
<body ng-app="myapp"><mainmenu></mainmenu><div ng-view></div></body>
I don't really need a directive for mainmenu, but it allows me to put the menu in a separate template file. The main menu itself contains user info, login/logout, and a search box.
<div class="leftmenu" ng-show="isLogin()">
<ul class="menu">
<li>Part1</li>
<li>Part2</li>
<li>Part3</li>
</ul>
<div ng-controller="Search" class="Search><input type="text" ui-select2="s2opts" style="width:250px;" ng-model="search" data-placeholder="search"></input></div>
</div>
<div class="rightmenu">
<ul ng-show="isLogin()" class="menu">
<li>My Account</li>
<li>Logout</li>
</ul>
<ul ng-show="!isLogin()" class="menu">
<li>Login</li>
<li>Register</li>
</ul>
</div>
So there is the menu part, with its own controller, and the search, with its own, embedded between the two parts.
Of course, my mainmenu directive unit tests fail because SearchController isn't defined. But this leaves me wondering if I am going about this wrong. Should I even have it like this, a section of html with an explicit ng-controller defined inside it? Doesn't this create all sorts of weird dependencies?
How should I better structure this? A search directive that is included so I can unit test it separately? Something feels wrong here structurally...
UPDATE:
As requested, a basic fiddle: http://jsfiddle.net/nj4n44zx/1/
As specified by the Angular documentation, the best practice is to define a controller inside a directive only to expose an API to another directive. Otherwise the link function is sufficient.
See at the bottom of :
Angular directives
By experience using controllers inside a directives shadow what you are doing in your scope. It does not help to have a easy readable code.
I do prefer using the main controller where the directive is included. With a non isolated scope you have access to everything from the link function.
I usually deal with it like that:
app.directive('topMenu', function() {
return {
restrict: 'E', // or whatever You need
templateUrl: '/partials/topmenu', //url to Your template file
controller: function($scope) {
$scope.foo = "bar";
}
};
});
Then, in that template You don't have to add ng-controller.
sure your directive can contain a controller because you declare a directive like this
myApp.directive('mainMenu', function() {
return {
restrict: 'E',
replace: true,
scope: true,
templateUrl: 'menu.html',
controller:['$scope', function($scope) {
//define your controller here
}]
};
});

Accessing controller name outside of ng-view

Is there a way to access the name of a controller when the controller is defined seperate from ng-view?
<div ng-controller="Ctrl1">
<!-- Some code -->
</div>
<div ng-view>
<!-- Configured with ngRoute -->
</div>
Within ng-view I'm able to use $route.current.controller to retrive the current controller name. If possible how would I achieve the same thing for "Ctrl1"?
Thanks a bunch!
Unlike the ng-view, it's possible that there are more than one ng-controller in the same page.
Therefore, it's impossible to have something like $route.current.controller to get the current controller.
But if your goal is to just print every controller name that get initialized via ng-controller, you could write a custom directive with the same name ngController.
app.directive('ngController', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
console.log(attrs.ngController);
}
};
});
This will output the value inside the ng-controller attribute whenever any ngController directive has been compiled.
Hope this helps.

Angular Directive not replacing Element

I'm trying to create a directive that will work against xml that i am injecting into the dom via a service.
Here is my a relatively reduced example, having removed the async service call for data and also the template: jsBin - transforming elements using directive
Everything works well with regard getting my post elements' header attribute into an h2 tag but it is leaving a element within my page which will fail for some browsers.
to clarify, this is what i get:
<post class="ng-isolate-scope ng-scope" heading="test heading">
<div class="ng-scope">
<h2 class="ng-binding">test heading</h2>
<div>test</div>
</div>
</post>
and this is what i would expect:
<div class="ng-scope">
<h2 class="ng-binding">test heading</h2>
<div>test</div>
</div>
I think Adam's answer is the better route, but alternatively and easier if you include jquery you can do this in your link function:
var e =$compile(template)(scope);
element.replaceWith(e);
You aren't using template correctly in your directive. Your link function should not applying your template as you are in the example code.
var linker = function(scope, element, attrs) {
var template = getTemplate();
element.html(template);
$compile(element.contents())(scope);
};
Instead of that, just do this:
return {
restrict: 'E',
replace: true,
scope: {
heading: '#'
},
template: '<div><h2>{{heading}}</h2><div>test</div></div>'
};
In your post directive. 'replace: true' will not impact anything if you are compiling and manipulating the DOM yourself.
At the same time, though, I have no idea what your compile directive is for and why you have a factory that returns an HTML string. All of that code looks like it could be reduced to a single directive. I can't really comment on what you're trying to do, but once you start using $compile all over the place, odds are you aren't doing things the 'Angular way'.

Avoiding too many Angularjs Directives

I have the following directive:
.directive('myDirective', function() {
restrict: 'A',
templateUrl: 'app/templates/someTemplate/html',
});
in my template (someTemplate.html) I have the following:
<div>
<div>Some div</div>
<input type="button" value="button" />
</div>
I want to add behavior to the button and div. I can go the route of adding directives like so:
<div>
<div div-click>Some div</div>
<input type="button" value="button" button-click />
</div>
and adding more directives and binding click events via element.bind(... but is there a best practice? Should I be adding behavior in the 'myDirective' containing those elements? via jQuery or jQlite . The clickable elements inside the template are not meant to be resuable..so should I just use jQuery to find those elements and bind event listeners to them? I can see how their can be a directives explosion by constantly using the directive route, what is the best practice?
The question for me would be on what exactly the directives should be for.
It sounds to me, as if you are trying to wrap functionality that you know from other frameworks like jQuery into directives. this leads to stuff like:
var app = angular.module("module.directives", []);
app.directive('myDirective', function() {
restrict: 'A',
templateUrl: 'app/templates/someTemplate/html',
link: function(scope, el) {
el.on("click", function() { console.log(42); });
}
});
While certainly possible, this is (at least for me) considered "bad" style.
The difference with Angular is, that it does not use the DOM as the "Model" part of the framework, like jQuery or Prototype do. Coming from these libraries this is something to wrap your head around, but actually, for starters, it boils down to this:
Work with the scope and let the changes to the scope be reflected in the DOM.
The reflection part is actually the short and easy one: Angular does this out of the box (i.e. "most of the time").
Reconsidering your example with the click - Angular provides excellent event handlers in the form of directives. ng-click is a very good example for this:
<div>
<div ng-click="method()">Some div</div>
<input type="button" value="button" ng-click="method2()" />
</div>
This directive takes an expression - it looks a bit like the old days, where you would bind javascript directly to elements, like this:
here
It's way different though - Angular will look for the names method and method2 on the current scope you are in. Which scope you are currently in depends on the circumstances (I heavily suggest the docs at this point)
For all of our intents and purposes, lets say, you configure a controller inside your directive from earlier:
var app = angular.module("module.directives", []);
app.directive('myDirective', function() {
restrict: 'A',
templateUrl: 'app/templates/someTemplate/html',
controller: ['$scope', function(scope) {
scope.active = false;
scope.method = function() { console.log(42); };
scope.method2 = function() { scope.active = !scope.active };
}]
});
You can define this in many places, even as late as during the link phase of a directive. You can also create an extra controller in a separate module. But let's just stick with this for a moment:
In the template - when you click on your div the scope's method will be called. Nothing fancy, just console output. method2 is a little bit more interesting - it changes the active variable on the scope. And you can use this to your advantage:
<div>
<div ng-click="method()">Some div</div>
<input type="button" value="button" ng-click="method2()" />
<span ng-show="active">Active</span>
</div>
When you click on your button, the span will be turned on and of - the ng-show directive handles this for you.
This has gotten a bit longer than expected - I hope though, that this sheds some light on the "best practises" (which are quite dependent on what you actually want to accomplish).

Resources