I'm currently making a switch from Knockout to Angular. The main problem I'm having right now is in transferring my original templates to something Angular will recognise.
Specifically, here's a bit of code I'm having trouble transferring:
<!-- ko template: { name: 'SquareTempl', data: Squares[5] } --><!-- /ko -->
In Knockout, this will attach Squares[5] to SquareTempl, so that when the template gets rendered, it does so using the members within Squares[5](or whatever data that gets attached).
I need to repeat the process for Squares[0]~Squares[11]. I can't use ng-repeat though since I won't be iterating through them in numerical order.
Ideally, it would be nice if I could do something along the lines of
<td class="Square" id="five" ng-include src="'SquareTempl.html'" ng-data="Squares[5]">
Any ideas?
Here's a JSFiddle I've written to outline a failed attempt I've tried using ng-model.
http://jsfiddle.net/fZz3W/9/
Two things: First, you can make ng-data be available by implementing it yourself YourApp.directive("ngData", function() {}) Secondly, do you need the HTML to be part of another file? An easy way to accomplish what you're looking for in Angular is with ng-repeat like:
<td ng-repeat="item in Square">
<div>{{item.name}}</div>
</td>
When the Square array is updated an additional post will be made.
Review your modified JSFiddle: http://jsfiddle.net/TdWMF/1/
So this is mostly a hack to achieve what you want, until I can offer you a better solution:
Second update using mixed order ng-repeat: http://jsfiddle.net/TdWMF/3/
Basically:
<div ng-repeat="index in [4, 2, 0, 3, 1]">
square index: {{index}}<br />
square: {{Squares[index]}}
</div>
Pretty ugly, and non-ideal, I know. I'd also recommend performing the order array generate in a function and then doing: ng-repeat="index in displayOrder(Squares)"
Related
I am using oi-select library, i have customized it according to my project need and i have written directive for it. The problem is its taking too much of time say 10secs to load around 3k array data. I want to minimize the time for it. Here I have created plunker for this.
I have directive which loads all factory data and provides it to oi-select, in my code here is html
<small><b>select {{MPhardwaresListDropDown.length}}</b></small>
<div style="padding-top: 7px">
<div title="" class="selected-multiple-items">
{{MPselectedHardwares.length}} selected
</div>
<grid-multi-select id="hardwareId" clean="clean" optionlist="MPhardwaresListDropDown" colval="name"></grid-multi-select>
</div>
HTML code in directive looks like
<div>
<div ng-repeat="optionVal in tempOptionList | orderBy : ['-originalcheck','+label']" prevent-close>
<div ng-if="optionVal.label">
<label class="checkbox" ng-if="!typeFilterOptions || (optionVal.label.toString().toLowerCase().indexOf(typeFilterOptions.toLowerCase()) > -1)">
<input type="checkbox" name="checkbox1" ng-checked="optionVal.check" ng-model="optionVal.check"/>
<span class="checkbox__input"></span>
<span class="checkbox__label" style="color:#A9A9A9;">{{optionVal.label}}</span>
</label>
</div>
<div ng-if="!optionVal.label">
<label class="checkbox" ng-if="!typeFilterOptions || (optionVal.val.toString().toLowerCase().indexOf(typeFilterOptions.toLowerCase()) > -1)">
<input type="checkbox" name="checkbox1" ng-checked="optionVal.check" ng-model="optionVal.check" ng-change="checking(typeFilterOptions)"/>
<span class="checkbox__input"></span>
<span class="checkbox__label" style="color:#A9A9A9;">{{optionVal.val}}</span>
</label>
</div>
</div>
angular code is too big to mention in this question please refer plunker, but this is how it loops
scope.selection = scope.selectionorg;
scope.checkedall=scope.checkedallorg;
scope.OptionList=parentScope[scope.parentListName].sort();
scope.tempOptionList=[];
var i=0;
for(i=0;i<scope.OptionList.length;i++) {
scope.tempOptionList[i] = {val: scope.OptionList[i], check: false, originalcheck: false};
}
if(scope.optionListSelectedList.length>0) {
angular.forEach(scope.optionListSelectedList, function(obj){
angular.forEach(scope.tempOptionList, function(obj1){
if(obj===obj1.val){
obj1.check=true;
obj1.originalcheck=true;
}
});
});
}
else{
scope.checkedall=false;
}
};
I want something like which will load partial data on scroll it loads more data, any help will be appreciated. Thank you so much.
EDIT
Now i have edited my plunker with limitTo in ng-repeat, for that i have written new directive which will trigger addmoreitems function when scroll will reach bottom. updatedPlunker
Now problem is when i am typing and searching something its searching in only available records with respect to limitTo its not searching in all data, say now the limitTo is 50 then search is happening only in 50 records not in 3k records.
Correct way of doing this kind of requirement is to do with pagination, since you are loading data from the server side, you should make your api to support pagination.
It should accept three parameters such as number of items, start, limit and you should initially get the number of items and repeat it until you get the whole result set.
There should be another request to get the total number of items. By doing this you can retrieve all the elements at once loaded in the client side. Until that you should have a loading indicator, or you could load this data when the login process/application starts.
limitTo will not be able to help with the search , because you are limiting the results.
The problem is not the array, the browser can easly handle that, the problem is that you're rendering all the 3k DOM elements, that's really heavy work even for an actual machine, also since there is bindings in each dom element {{}} they're being watching by AngularJs, I got the same problem and solved using Virtual Repeat from AngularJS Material, what this does is it doesn't render the whole 3k DOM elements generated by the ng-repeat, instead it just renders the ones that are visible, also I've found another library if you don't want to use Angular Material, this seems to work the same way: Angular VS-Repeat
You may try the limitTo filter in ng-repeat in angularjs which takes the additional argument to start the iteration.
https://docs.angularjs.org/api/ng/filter/limitTo
On scroll, you can then change that argument based on the number of items pending or left for rendering or the number of items already rendered. This should help you in approach of selective loading of data on scroll.
I would like to know how to get the Full, Plain HTML of an Angular ngRepeat.
I need it to create reports, alert part of code etc.
I used the $compile and I've been able to get the HTML of simple tags by compiling
<p>I am {{surname}}, {{name}} {{surname}}</p>
and what I got is
<p>I am Bond, James Bond<p>
But when I try to do the same with
<p ng-repeat="number in array">{{number}}</p>
I only get the Angular comment saying
<!-- ngRepeat -->
Instead of the series of paragraphs.
Please check this Fiddle for examples.
In order Angular to compile ngRepeat directive it must be in DOM. Angular finds corresponding comment and replaces it with actual HTML structure.
So all you need to do is to append compiled comment node to DOM and after it's rendered you can retrieve HTML.
$scope.html = $compile('<p ng-repeat="el in arr">{{el}}</p>')($scope);
document.getElementById('app').appendChild($scope.html[0]);
Demo: http://jsfiddle.net/d4p0qLx2/2/
And in the next digest cycle you can get rendered HTML: http://jsfiddle.net/d4p0qLx2/4/
I have the following fiddle setup to demonstrate what I am experiencing.
http://jsfiddle.net/BjornJohnson/ndxnG/25/
My controller uses these objects: page => rows => columns => pieces. A page has rows. A row has columns. A column has pieces.
There is a custom directive ("piece"). The point of the directive is to be able to provide different types of HTML elements based on the "type" of the piece (so-called dynamic templating).
I keep running into problems when I remove pieces from a column. I am modifying the model, but it's not reflected in the view. It works when the ng-repeat is on a wrapper, but not on the custom directive itself, so I think something is clashing with the ng-repeat directive.
I apologize in advance for using all caps, but I CANNOT USE A WRAPPER DIV FOR THE NG-REPEAT :)
The code in question is both in the view and the directive.
Here's part of the custom directive:
var template = templates[scope.piece.key];
var replacement = $($compile(template)(scope));
element.replaceWith(replacement);
Here's part of the view:
<div ng-repeat="column in row.data.columns" ng-class="column.data.clss">
<!-- notice wrapper for ng-repeat - THIS VIOLATES MY REQUIREMENTS -->
<div ng-repeat="piece in column.data.pieces">
<piece></piece>
</div>
</div>
versus...
<div ng-repeat="column in row.data.columns" ng-class="column.data.clss">
<!-- notice ng-repeat on custom directive -->
<piece ng-repeat="piece in column.data.pieces"></piece>
</div>
Alright, newbie Knockout question here.
In this example: http://learn.knockoutjs.com/WebmailExampleStandalone.html#Inbox
How does the mail detail view replace the folder list view?
That is, what feature causes the divs to be toggled? In inspecting the dom, I see that what happens is the div's are actually rendered empty when not displayed.
Can someone enlighten me? I know this is rather basic, but its the last piece I need to click into place for my understanding.
Just to be 100% clear: when you click on a row in the folder list, what causes the folder view to be emptied and the mail detail to display? Is it the with binding? (That doesn't seem right.)
You are on the right track with the with binding: in this example the views are changed using the with binding relaying on this feature of the binding:
The with binding will dynamically add or remove descendant elements depending on whether the associated value is null/undefined or not
So in the viewmodel code you will see something like this:
this.get('#:folder', function () {
self.chosenFolderId(this.params.folder);
self.chosenMailData(null);
$.get("/mail", { folder: this.params.folder }, self.chosenFolderData);
});
this.get('#:folder/:mailId', function () {
self.chosenFolderId(this.params.folder);
self.chosenFolderData(null);
$.get("/mail", { mailId: this.params.mailId }, self.chosenMailData);
});
So the functions which are "chaining" the view nulls out one of the properties while filling in the other which toggles the views defined as:
<!-- Chosen mail -->
<div class="viewMail" data-bind="with: chosenMailData">
...
<div/>
<!-- Mails grid -->
<table class="mails" data-bind="with: chosenFolderData">
</table>
This is not the nicest solution but don't forget that Knockout is a Databind/MVVM library and not a full blown SPA framework so it does not have concepts for layouting and higher level view composition.
However this could be made nicer with using the template binding:
<div id="mainView" data-bind="{template: {name: templateName, data: activeView}}">
</div>
And turning the views into templates:
<script type="text/html" id="ChosenMail">
<div class="viewMail">
...
<div/>
</script>
<script type="text/html" id="MailsGrid">
<table class="mails">
...
</table>
</script>
And in the routing only set the activeView property and lookup the corresponding template name for it:
this.get('#:folder', function () {
$.get("/mail", { folder: this.params.folder }, function(data) {
self.activeView(data);
self.templateName('ChosenMail');
});;
});
this.get('#:folder/:mailId', function () {
$.get("/mail", { mailId: this.params.mailId }, function(data) {
self.activeView(data);
self.templateName('MailsGrid');
});
});
But because this is quite much manual and error prone work I would use something like Durandal.js which is a real SPA framework and it was designed for this kind scenarios.
That is just a Demo on a light weight SPA scenario, with binding is just a inline template binding. Not very useful for a dynamic SPA. Like Nemesv suggests use the template binding.
The problem with the template binding is that its very verbose to use, I have addressed this in my Binding convention library (One of many features)
Instead of doing
<div id="mainView" data-bind="{template: {name: templateName, data: activeView}}">
</div>
You do
<div id="mainView" data-name="activeView">
</div>
My library will do the rest, check out the wiki on templates here
https://github.com/AndersMalmgren/Knockout.BindingConventions/wiki/Template-convention
And a little fiddle
http://jsfiddle.net/xJL7u/11/
I've used Knockout templates before, so I'm not sure why this isn't working for me.
I tried two different styles of ko markup, neither work.
<!-- more nesting levels -->
<div class="cal-day-tps" data-bind="foreach: timePeriods">
<div class="cal-day-tp-cont">
<div data-bind="template: { name: 'tp-ed-templ', data: $data }"></div>
//both of these methods fail
<!-- ko template: { name: 'tp-ed-templ', data: $data } -->
<!-- /ko -->
</div>
</div>
<!-- /more nesting levels -->
<script type="text/html" id="tp-ed-templ">
<!-- bunch of markup -->
</script>
I just get the error "Cannot find template with ID tp-ed-templ".
Probably just a typo, but I haven't been able to find it.
I'm using KO in the context of Durandal, though this shouldn't make a difference.
Tried declaring the template before usage, didn't help.
Someone else ran into the same thing with no solution either
It seems to be an issue with Durandal, not Knockout.
I tried some extremely simple cases in vanilla durandal setups, and it still does the same thing. Even tried putting the script in the same nested location as the binding, no dice.
The short answer: You can't currently use Knockout templates inside of Durandal.
However, as nemesv pointed out, if you put your named template outside of Durandal, ko is able to find them. For example, anywhere outside of the <div id="applicationHost"></div> element.
The other workarounds are to either use Durandal's compose functionality, or just inline the templates as anonymous.
Knockout templates will probably be supported in the near future.
I finally dug these answers up on the Durandal google group,
Mixing knockout templates with durandal compose
knockout can't find templates inside of views
The issue is that the KO template element must exist in the DOM before the Durandal view is bound. This is because the view is bound before it is inserted into the DOM so any contained templates cannot be resolved by ID.
Using a function that returns an observable can be used to later re-trigger a template binding .. it works, but is wonky. (An if binding could be used for similar effect.)
// bind to this in markup:
// <div data-bind="template: {name: $root.templateName, .. }">
vm.templateName = function () {
return vm.TemplateId();
};
// Changing this will trigger an observable in the KO template binding;
// don't ask me why we have to pass in a function to 'name' ..
vm.TemplateId = ko.observable("dummy-template-id-that-exists");
// After the view is attached the correct template element is in the DOM
// so we can trigger the template to (re-)bind and it will find it.
function viewAttached () {
vm.TemplateId("the-real-template-id");
}