I'm building a complex, variable layout with infinite scroll UI in AngularJS.
Think something like Flipboard with a load of repeating data items, each item containing the same thing (title, description, image etc). Each item is pre-loaded into JSON, sorted and then organised into a series of pages, selected from a series of variable layouts. Each page has a variable number columns and each column a variable number of rows.
In order to achieve this, I'm using multiple Directives for each page layout, compiled at run time. Inside each layout Directive, I'm then building a variable number of columns, each of which contains an ng-repeat for the variable number of rows. Each row can be a different Directive, depending on how it needs to be styled. Again, all of this is happening at runtime.
I'm 99% of the way there.
The Directives are being compiled correctly and calling up the correct column Directives, which in turn are compiling the correct number of rows. Once we get to the row/item level, I'm using a Service which brings back the correct Directive for each item. The reason I choose the Service approach is that I want the items to be reused inside other modules.
The problem I'm having, is that once the page layout is compiled, it sets up the columns and then the rows but does not execute the ng-repeat. I need to get the ng-repeat to loop and also call the specific Directives I use for each item.
I think is a problem to do with $compile and what Angular knows about the DOM. Maybe I need to do a $compile or $apply at some stage to get the ng-repeat to kick in and bind the final Directives to the data items.
**
Side note: if I don't use a Service, but simply use define each layout Directive with a templateUri it works perfectly!
I can go with this approach, but ideally, I'd like to get away from downloading a bunch of templates at runtime. Also, by passing parameters into a Service, I can design more layouts, much quicker and easier than having to build individual template files.
I've set up a JS fiddle with quick example of how I'm approaching all of this:
http://jsfiddle.net/joecarney/vE3Ls/6/
Some SO required code just to post this comment:
<div ng-app="app" ng-controller="appPageController">
<div>
<div my-page></div>
</div>
</div>
If you use scope: {} in a directive it creates a new scope so your directives will not have access to controller scope unless you allow it, which you started out doing but appears you then copied and pasted the same thing for all :)
The directive compiling items in ng-repeat had no access to items. I removed scope from directives and used the attrs value to access the attributes
http://jsfiddle.net/vE3Ls/7/
Related
I have a situation where I am generating a LOT of rows and columns using Angular and the performance is terrible!!
I'm assuming that it's because my ng-repeat is creating a new scope for each of the elements that it creates, however, this particular function doesn't need to watch anything. I'm simply using ng-repeat as a loop to generate HTML that will never change.
Is there another angular method that I can use to loop through model data and generate HTML without using ng-repeat that will improve performance?
You can create a directive which will accept the ng-repeat values and then do whatever you want to do in that directive e.g. create a table which will have those values.
You actually need the scope to access the data for the current iterations item.
However, as your data never changes, you might get take a look at bindonce or the slyRepeat directive.
So I'm trying to build a drag & drop list with ctrl/shift selection in Angular. For some reason, when I attempt to load my template / directive, angular uses the last array loaded at any given point. What I mean by this is, if I load two arrays, each in separate <ul> elements, it will use the second one for both of them. Overall, it's pretty confusing why it happens.
See my fiddle here.
You're initializing the same scope attribute twice:
ng-init="itemList=itemsInTrash"
...
ng-init="itemList=itemsThatAreReal"
So, once the template has been initialized, this itemList variable references the itemsThatAreReal array, and the two included templates thus display this array.
I have an Angular app with a custom element directive (<uber-table>). <uber-table> is supposed to take a collection of objects, render them into a <table> and add some functionality (click row to toggle the underlying object as selected, search box for live filtering, action links on each row with customized click callback(s) for each object). I created a Plunker with the relevant code. Please note that Plunker is giving an error about map (Object [object Object] has no method 'map'), but locally I am not getting any errors.
The post-link function's element parameter is not the <uber-table> element as I expected. Instead it is the template's <div class="uber-table"> element. This is preventing me from extracting data from <uber-table>. What am I doing wrong? Any help will be much appreciated.
Here's a rundown on some of the issues.
First main issue is you want existing content already within the uber-table markup to exist, as well as a new template. Unless told otherwise the existing content ( columns) in this case will be overwritten. In order to include existing content in a directive that has a template, you need to set transclude:true then identify within template where this existing content needs to be placed using ng-transclude on element that will be parent of the content.
Here's demo with transclude fixed
New problems arise now where you are trying to use jQuery to loop over columns and return angular attrs => column.attrs . This throws undefined error.
I haven't tried to unravel this enough to sort out the columns issues yet. They should likely be handled by directive of their own
UPDATE: here's slightly revised error free version using jQuery to parse column count. I'm afraid am still confused a bit by structure of this. I don't see need to use jQuery to parse colunms, this could be converted to directive or pass column definitions into main directive from controller scope
After trying several things and looking at the documentation again, I finally got it working. The solution was to put the post-link function inside the compile function. Also I had to update my isolated scope to use =, set replace to true and set transclude to 'element'.
I updated Plunker if anybody wants to see the changes. The Plunker version isn't working, but since it is working locally, I'm not going to spend too much time on it.
I have a page that has a number of directives. There are a number of directives in the header/navigation each with there own scope. There is also a ng-repeat of 25 items and each one of those creates a directive each with its own scope.
One of the directives includes a form that includes a custom filter to display form errors, it looks like this:
<span>{{ createProjectForm.name.$error | nagParseErrors }}</span>
Now the concern I have right now is that nagParseErrors is being executed about 33 times when anything in any scope changes even though this data createProjectForm.name is binded to (with ng-model) is only contained in the controller scope and the directive's scope containing the form (which is just being passed to the directive from the controller scope). I know it is related to the number of scopes (or directives) on the page because if I limit the ng-repeat from 25 items to 1, the filter is only called 9 times. This also happend for built-in filters (like json, and it even runs more times).
Is there something I might be doing wrong here or is this in fact how it should work in AngularJS?
BTW, I realize now that displaying the errors might be better off as a directive than a filter I am planning on going the directive route however I would like to clear up my understanding of filters here since I will probably run into this at some point down the road.
This has been addressed numerous times. All AngularJS expressions will be constantly re-evaluated throughout the lifecycle of the app. This is how two-way databinding in AngularJS works.
So, there's nothing wrong with your code. It's just that you need to make sure your filter is idempotent (returns the same output given the same input).
For more info take a look at Why Scope.$apply() calls $rootScope.$digest() rather than this.$digest()? and scope docs.
I'm very(!) new to Angular.js and am trying to understand where to put the various parts of my logic in order to follow best practices and separate business and presentation logic.
My specific use case is that I have a list of courses with a number of signups and a number of available seats. Based on these values I want to present a progress bar (or, if the available seats is not set, just a text).
My question is where to put the various parts of the logic, and how to pass the values along properly. So far I've created the HTML-part of a directive, like so:
<signupprogress available="{{course.available_seats}}" filled="{{course.filled_seats}}"></signupprogress>
My question is then (first and foremost) if a directive is the proper way to do this and, of so, if the logic for constructing the progress bar should go in the compile function, link function, in a template, or some other place. To me the compile of link function seems to be most correct, but I don't want to fill them with HTML, nor am I able to properly get the attribute values from the HTML (the attrs.$observe examples I've seen only implement the getting of one attribute).
Yes, use a directive since you need to modify the DOM.
If all of the HTML for the progress bar and the alternative text can be placed in the directive's template, then do that. And, if that is possible, use '#' for one-way binding, which makes it clear that the directive does not need to modify the "available" and "filled" values. If you find you need a linking function, then as #ShaiRez mentioned, '=' is probably easier. (BTW, here is a way to $watch multiple attributes, instead of using $observe. Maybe the same technique can be used for $observe.)
To display either a progress bar or the alternative text in the template, use ng-hide or ng-show in the template. Here's a simple example of that (which also uses '#').
The directive is the way to go in my opinion.
I would have my HTML content inside of a template, the logic inside of the link function (the compile function is usually more for repeaters etc).
And use the "scope" property in the directive definition, set to "=", that way changes are reflected automatically.