How do I use a view with a child controller in Angular? - angularjs

In my app I have a Main controller, within the template/view for that controller I call a second controller thru <div ng-controller="BasketCtrl"></div>. But how do I tell 'BasketCtrl' to use a certain view/template?
I don't want to use this "basket" within my '$routeProvider' since it will be used accross my site.
The basket will be a section of each template that shows the contents of a shoppingcart. Therefor I don't want to create the neccessary HTML within the DIV, that will lead to duplication of a lot of code...am I getting something wrong here perhaps?

I would create a basket directive. Something like this:
app.directive('basket', function(){
return {
templateUrl: 'basket-template.html',
link: function(scope, element, attr){
}
}
});
You could then include it in as many templates as you'd like. Read more about directives here: http://docs.angularjs.org/guide/directive

Just template the BasketController inside of the div that you added the ng-controller to. It is a nested template.
<div ng-controller="BasketCtrl">
<!-- put template inside of basketctrl -->
</div>
Alternatively, if you want your basketctrl inside of another file, you could do an ng-include inside of your BasketCtrl and include a link to that template:
<div ng-controller="BasketCtrl">
<div ng-include="'foo/bar/BasketTemplate.html'">
</div>
make sure to use both the double quotes and single quotes in there. Otherwise it won't work.

Related

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 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. :)

Angular run directive of paginated elements on page change

This is going to sound complicated but, i hope someone here is more experienced than me and can sort through what follows without much difficulty.
To keep this in perspective this is all related to a pagination section like so: There's a controller that pulls some initial data objects from a test page via an ajax request. Those data objects pertain to each "puppy block" - they fill the the "puppy-block" template, of the puppy block directive, with the correct data.
The blocks are displayed using an ng-repeat (repeating for the number of data objects pulled) - this represents a page.
The pagination is independent and just modifies the controller data via different ajax requests.
I have this here ng-repeat:
<div class="puppiesContainer" ng-repeat="p in puppies track by $index">
<puppy-block></puppy-block>
</div>
It's purpose is to repeat a directive a number of times
The <puppy-block></puppy-block> directive template is this:
<div class="puppyA" preview-pup>
<div class="clearfix puppyCover" dataPreview="{{p.dataPreview}}">
<a ng-href="{{p.link}}"><img ng-src="{{previewData[currentPosition]}}" /></a>
</div>
</div>
As you can see, there is another directive called preview-pup on every element.
The code for that directive is this:
obApp.directive('previewPup', ['$timeout',function($timeout){
return {
restrict: 'A',
link: function(scope, el, attr){
//some code that needs to run often
}
}
}]);
My pagination directive changes the data of the puppy-block directives when moving between different pages, however i also need to trigger the link function of the preview-pup directive, inside each puppy-block directive, when the change happens. Both the pagination directive and the ng-repeat directive have a common controller from which they draw data (modify data).
My problem is that i have no idea how to manually trigger a directive's link function on an ng-click, for instance. How may i do that?
Alright, found a solution:
Inject $routeScope into your controller and the directive you want to trigger a change in.
Use that to notify the child directive of a change in the parent controller. For more details - in case anyone stumbles upon this question looking for something similar - Angular.js trigger another directive
I had this same issue. Removing the "track by $index" from the ng-repeat fixed the it for me. Unfortunately I don't know why yet.

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.

Resources