I am building a real time application with socket.io. I've ran across a problem using ng-repeat with a nested form inside an ng-repeat tag. Here is the jist of the code:
<div class="row" id="thingArea">
<div class="thing text-center col-sm-{{12/bigObject.columns.length}} col-xs-{{12/bigObject.columns.length}}" ng-repeat="column in bigObject.columns" data-columnindex="{{$index}}" id="column{{$index}}">
<h3 class="title">
<span class="text header-text"><font size="7" ng-bind-html="column.title"></font> </span>
</h3>
<form ng-submit="addRow($index, rowValue)" class="thing-form" >
<div class="form-group">
<input type="text" class="form-control" ng-model="rowValue" placeholder="{{column.placeholder}}">
<br/>
<br/>
<div class="thing-column" as-sortable="thingSortOptions" ng-model="column.rows">
<div class="alert alert-{{column.color}} alert-dismissible" role="alert" ng-model='row' ng-repeat="row in column.rows" as-sortable-item>
<div as-sortable-item-handle>
<button type="button" class="close" ng-click="deleteRow(column, row)" aria-label="Close"><span aria-hidden="true">×</span></button>
{{row.value}}
</div>
</div>
</div>
</div>
</form>
</div>
</div><!--//row-->
The schema of bigObject is as follows:
var RowSchema = new Schema({
value: String
});
var ColumnSchema = new Schema({
title: String,
placeholder: String,
color: String,
selected: String,
rows: [RowSchema]
});
var BigObjectSchema = new Schema({
_id: {
type: String,
unique: true,
'default': shortid.generate
},
name: String,
startTime: Date,
endTime: Date,
columns: [ColumnSchema],
info: String,
active: Boolean
});
Basically bigObject can have N columns, with N rows inside of it. This is where I'm using ng-repeat to first iterate over the columns, which contains a form with an input to add rows. Then within the column I then iterate over the rows.
Okay. Here's the my problem. Since I'm using socket.io to sync real time bigObject updates between clients when the bigObject changes it re renders ALL of the ng-repeat's...and in dosing so blows away the input. As in, if one client is typing in an input, and another client causes an update, the user receiving the update page re renders and whatever they are type gets blown away.
My thoughts so far:
Take the input box out of all the ng-repeat tags, so its unaffected from updates. This adds complexity however, again I could have N columns...means N input boxes. It's nice having them correlate.
Ditch the idea of being completely dynamic in terms of N columns since in reality the UI with be fixed to only allow 2-3 columns. But ya, that seems lame.
Find some fancy angular/css tricks to hack around this....which haven't been super fruitful
Am I approaching this all wrong? I usually favor the simplest option..the first option adds code complexity, and the second adds code duplication but more simple.
Thanks.
You could change it like this:
<div....ng-repeat="column in bigObject.columns" ng-init="columnIndex = $index"....>
Replace ng-model="rowValue" to something like ng-model="temp[columnIndex].rowValue" , where temp is another object in your model, unrelated to BigObject. This way your temporary (not yet saved data, and available to only the current client) is in a different place than the one available to all clients, and the update of BigObject shouldn't interfere with the text in your input.
Edit:
Not sure if I was clear enough(or if it's the recommended way to do it), but temp should be on the model, not only on the scope(since the scope gets replaced and it loses the content)
Related
In my controller, I have an object:
$scope.selectedMap = {
map: '', mapDesc: '', stops_json: "", stopLength: []
};
The stops_json key in this object holds another object, that looks like this:
{
desc: "PRG-BUD-HR-PAY",
stop1:"DEPT",
stop2:"ACCT",
stop3:"ACCT2",
stop4: 158,
stop5:"CCT",
stop6:"CCA",
stop7: 156
}
I'm using this child object in an ng-repeat, and generating inputs with it for each stop:
<p class="row col-md-12">
<strong>Description: </strong>{{ selectedMap.stops_json.desc }}
</p>
<div class="row col-md-12 float-left">
<md-input-container ng-repeat="stop in selectedMap.stops_json" ng-if="!$first" ng-model="stop" class="col-md-12">
<label>Stop {{ $index }}:</label>
<input class="col-md-8 float-left mapStops" ng-model="stop" aria-label="Route Stop Description">
</md-input-container>
</div>
My issue is, that when I change the value of the input tags, the stops_json object does not change. I was under the impression that it was supposed to, since that is the object that is modeled. I am clearly mistaken, can anyone educate me?
A few problems to mention first:
The notation for iterating over objects with ng-repeat is (key, value) in obj
If you choose to use object iteration with ng-repeat then your ng-if dependent on $first is non-deterministic (i.e. iterating in key definition order is not guaranteed)
Applying an ng-model binding on the ng-repeat with the intention of making its constituents editable is incorrect—the models of each item should be have ng-model applied instead
I've created a stripped down example of your snippet to demonstrate making the repeated inputs able to change the stops_json model.
See here.
Basically, you need to ensure the input elements inside the ng-repeat directly bind to your stops_json model in order for any edits to affect it.
Also, note that all any numeric properties should use the type="number" variant of input, otherwise any edits will set the values to strings instead.
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 have found a lot of examples on how to structure many-to-many relationships in Firebase. Following the most recommended way I've seen to set it up I have some basic posts and tags data in Firebase:
/posts:
-K_GOdSQvCQ2sAcHfo1x
- descpripton: "This is a post..."
- title: "This is a title..."
- tags:
-K_aBTTDKVUovZe3l0lX: true
.....
-K_GFDQjPoSmCJM3YAlB:
- description: "Another post..."
- title: "This title..."
- tags:
-K_aBTTKDhYsnbFv1Tuc: true
......
/tags:
-K_aBTTDKVUovZe3l0lX
- name: "Sport"
- postIds:
-K_GOdSQvCQ2sAcHfo1x: true
.....
-K_aBTTKDhYsnbFv1Tuc:
- name: "Movies"
- postIds:
-K_GFDQjPoSmCJM3YAlB: true
.....
My problem is figuring out how to handle these relationships in my views. I haven't been able to find any tutorials or examples covering it.For example, in the index view I wish to list all posts with their respective tags attached.
So far I've been simply returning all posts and all tags and storing them in separate $scope variables. That's fine but I haven't been able to figure out how to display only the tags relative to each post. I first thought it should be possible trying a nested ng-repeat structure. But it's proving trickier than I expected to get this working.
This is the idea of what I am trying to achieve.
<!-- For each tag in post object iterate tags -->
<div data-ng-repeat="t in posts.tags">
<span data-ng-repeat="tag in tags">
<!-- display only tags here that match with post.tag.id -->
</span>
</div>
I'm thinking that it is perhaps possible using Angular's filter in conjunction with ng-repeat but I've had no luck finding any examples to work from. Maybe what I am trying to do is not possible in the view alone or too cumbersome/ugly. If anyone can give me some pointers on how I might approach it that would be great!
UPDATE
Ok, I have found a way to achieve this but I'm sure it's not optimal. If anyone can inform me on a better approach I'd be grateful.
For example, in the code below I feel that using <h5 ng-if="key === post.$id"> isn't the right way to match the posts to the tags. Maybe there is a standard way of using the boolean values to do this? I'm still a bit new to Firebase/AngularFire so I am eager to learn any emerging design patterns for solving this problem.
Again, the goal is pretty standard. The posts and tags are coming from separate Firebase nodes that have a many-to-many relationship(allowing posts to have many tags and tags to belong to many posts).
In the controller for the index view all posts and all tags are retrieved and saved separately to their own $scope variables. The following view code is how I managed to display the relevant tags inside each post preview.
<li data-ng-repeat="post in posts">
........
<div ng-repeat="tag in tags"> // Iterate tags
<div ng-repeat="(key, value) in tag.postIds"> // Iterate postIds in each tag
// Check tag.postId key against the current post id.
// Should be using booleans some way here instead??
<h5 ng-if="key === post.$id">
<span class="label label-default">
{{tag.name}}
</span>
</h5>
</div>
</div>
</li>
Short answer: denormalize your data.
A bit longer answer: You need to save the data in a way that helps you be efficient when you query it. So for this particular case, you could save the tag's name instead of just true which only shows association.
So instead of
{
posts:
-K_GOdSQvCQ2sAcHfo1x: {
descpripton: "This is a post...",
title: "This is a title...",
tags: {
-K_aBTTDKVUovZe3l0lX: true
}
}
}
You could save it like
{
posts:
-K_GOdSQvCQ2sAcHfo1x: {
descpripton: "This is a post...",
title: "This is a title...",
tags: {
-K_aBTTDKVUovZe3l0lX: "Sport" // <----- Notice the change
}
}
}
Watch this (and others, like this playlist) videos from the official Firebase chanel. Extremely helpful.
I have a rather large list with todo items for which I want to add in place editing for:
[ ] first todo
[ ] second todo
[ ] third todo
after clicking on the second todo, the text of the second todo can be edited but you can also set some properties on the todo:
[ ] first todo
-----------------------------------------------------------
| [ ] second todo__________________ |
| due: __/__/____ |
| assigned: ______________ |
| |
| [save] [cancel] |
-----------------------------------------------------------
[ ] third todo
Now I can do something like:
<div ng-repeat="todo in todos">
<div ng-show="!doedit">
<input type="checkbox"> {{todo.title}}
</div>
<div ng-show="doedit" class="boxed">
<input type="checkbox"><input type="text" ng-model="todo.title"><br>
<input type="date" ng-model="todo.due"><br>
<input type="text" ng-model="todo.assigned"><br>
<button ng-click="doedit = false">save</button>
</div>
</div>
This should work (ignoring how the cancel button should work) but if I have a large todo list (100+ items) it will create a large amount of hidden elements which are probably never used but still bound to variables.
Is there a better way to do this? I looked at angular-xeditable which seems to dynamically add elements but it only works for simple input elements.
Or is having a large amount of hidden elements not an issue in angular?
ng-if removes the element from DOM entirely if it evaluates to false, including any watchers. For example:
<div ng-if="doEdit"></div>
This post explains some of the differences between ng-if and ng-show.
Having large amounts of hidden elements is not an issue only with the js but also with the DOM itself. you should not be doing that.
What can be done instead is
1. Using ng-if - keep a state of all the element lets say isEdited after the user clicks on the todo. Set the variable / state isEdited = !isEdited. This would set it to true if it is not already. Then inside your DOM write HTML using ng-if. Something like this
<div class="to-do-item"></div><br>
<div ng-if="isEdited">
<!--UI for the edited part-->
</div>
Since ng-if does not render the html till the condition is true. There wont be too much load on the DOM.
2. If the template for editing the to do item is same for all the todo items. You should prefer using ng-include with ng-if. This way the template would be downloaded the first time you try to edit the element. Then you can cache it so not network lag. Also there is no extra hidden html. Whatever needs to be on the page is on the page and is not hidden.
Wrapping all of that editing section into one container with an ng-if would reduce the number of internal watches down to one for the whole section when it wasn't active
As a way to learn Ember, I'm trying to build a simple app, now without the crutches of a tutorial.
The app will do the following --
User writes his own city
User writes the city of person he wants to meet
Function will spit back up all the times that are mutually good for eachother. I.e times where neither of them will have to wake up too early to stay too late)
I'm a bit confused as to how to structure the data the Ember way. How would I go about it?
I'm thinking --
There will be an index.handlebars
- This will have the 2 input fields for both person A and person B's cities
- This will be backed by the 'Meeting' Model(attributes : cityA, cityB)
When the submit button is clicked, this is where I get confused --
I can imagine that there is a model called MeetingTime (with personA's time, and personB's time).
In what way would I present that, following Ember's best practices? What routes, controllers, templates etc?
Currently, this is what I have ->
RightTime.Router.reopen({
location: 'history'
});
RightTime.Meeting = DS.Model.extend({
myCity: DS.attr('string'),
theirCity: DS.attr('string'),
meetingTimes: function() {
if (myCity && theirCity) {
// get back a collection of meeting times
}
return false;
}.property('myCity','theirCity')
});
RightTime.IndexRoute = Ember.Route.extend({
model: function() {
//return this.store.createRecord('meeting');
//the above return is giving the error 'unedfined is not a function'
}
});
#index.handlebars
<div class="container">
<div class="col-md-6 col-md-offset-3 col-lg-4 col-lg-offset-4">
<header class="animated fadeInDown">
<h1><span class="twenty-four-header">24</span>Time</h1>
<h3>Get Great Meeting Times Instantly</h3>
</header>
<section class="main-options animated fadeInDown">
<div class="form-group">
{{input value=myCity placeholder="What's your city?" class="form-control input-lg"}}
</div>
<div class="form-group">
{{input value=theirCity placeholder="What their city?" class="form-control input-lg"}}
</div>
<div class="form-group">
<button {{action 'getTimes'}} class="btn btn-lg get-times btn-block btn-primary">Get Times!</button>
</div>
</section>
</div>
</div>
Ended up making it! www.24time.co
The code can be found at www.github.com/stopachka/right-time
There's one model called Meeting.
A person chooses a city by interacting with the Places Autocomplete API. When they choose, it returns latLng positions, which are attached to the meeting. As soon as the model has both positions for the cities, it makes an ajax request to /api/meeting, sending the location parameters.
The API returns back all the best times. I'm pretty sure I'm not doing it the ember way though, perhaps the model can be broken down differently, and the times can be models themselves.