watching ng-repeat objects used outside of of ng-repeat - angularjs

Ok I have created a ng-repeat to get all users created by an $http.get. This get request updates every 5 secs by using $interval and displays individual user data when clicked by calling $scope.goInfo(data). This $scope.goInfo(data) is used throughout the page to show user data, but is created by the ng-repeat (but not always used in ng-repeat). How can I have this data obj created by ng-repeat update every 5 secs outside of ng-repeat? I can't wrap $scope.goInfo() in a $interval.
EXAMPLE
//CONTROLLER//
function liveFeed(){
$http.get('some URL').then(function (user) {
$scope.user = user.data;
console.log('user data is', $scope.user);
});
}
//Updates get req every five secs//
$interval(liveFeed, 5000);
//gets data obj from ng-repeat, needs to be updated every 5 secs.//
$scope.goInfo = function (data) {
$scope.name = data.name;
$scope.beats = data.beats;
}
HTML
<table>
<th>First Name: John</th>
<th>Last Name:</th>
<tr ng-repeat="data in user" ng-click = "goInfo(data)">
<td>{{data.name}}<td>
</tr>
</table>
<span>{{beats}}</span><!--needs to update every 5 secs, outside of ng-repeat and be binded to the user that was clicked on-->

You need to reset selected object after you retrieve new data. Basically, you just need to find corresponding records in new array of objects and set it as selected again.
Something like this should do the trick:
function liveFeed() {
$http.get('some URL').then(function(user) {
$scope.user = user.data;
// Find the record that was selected before this update
if ($scope.selectedUser) {
$scope.selectedUser = $scope.user.filter(function(obj) {
return obj.name === $scope.selectedUser.name; // or compare by unique id
})[0];
}
});
}
// Updates get req every five secs
$interval(liveFeed, 5000);
// Gets data obj from ng-repeat, needs to be updated every 5 secs
$scope.goInfo = function(data) {
$scope.selectedUser = data;
}
and HTML will use selectedUser:
<table>
<tr>
<th>First Name: John</th>
<th>Beats:</th>
</tr>
<tr ng-repeat="data in user" ng-click="goInfo(data)">
<td>{{data.name}}<td>
<td>{{data.beats}}</td>
</tr>
</table>
<span>{{selectedUser.beats}}</span>

Related

AngularJS UI-Router nested state view in table row (inline edit)

Working on an AngularJS + UI-Router project. Got a task with these (simplified here) requirements:
display a list of items in a table with Edit button at the end of the table row
clicking Edit button should turn a table row into item edit form (inline edit)
Item list and item edit views should be accessible via url.
So I have defined my states:
// app.js
$stateProvider
.state("list", {
url: "/",
component: "listComponent"
})
.state("list.edit", {
url: "/{id}/edit",
component: "editComponent"
});
}
ListComponent template looks like this:
<table>
<tr>
<th>ID</th>
<th>Name</th>
<th> </th>
</tr>
<!-- Hide this row when NOT in edit mode -->
<tr ng-repeat-start="item in $ctrl.items" ng-if="$ctrl.editIndex !== $index">
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>
<button type="button" ng-click="$ctrl.onEditClick($index, item.id)">Edit</button>
</td>
</tr>
<!-- Show this row when in edit mode -->
<tr ng-repeat-end ng-if="$ctrl.editIndex === $index">
<td colspan="3">
<ui-view></ui-view>
</td>
</tr>
</table>
And main parts from ListComponent itself:
function ListController($state) {
this.editIndex = null;
this.items = [
{ id: 1, name: "Y-Solowarm" },
// ...
{ id: 10, name: "Keylex" }
];
this.onEditClick = function(index, id) {
this.editIndex = index;
$state.go("list.edit", { id: id });
};
}
Problem:
When I was working on EditComponent I noticed that it initiates http requests twice. After a couple of hours later I came up with such EditComponent that showed what actually was happening:
function EditController() {
// random number per component instance
this.controllerId = Math.floor(Math.random() * 100);
this.$onInit = function() {
console.log("INIT:", this.controllerId);
};
this.$onDestroy = function() {
console.log("DESTROY:", this.controllerId);
};
}
Console showed this output:
DESTROY: 98
INIT: 80
DESTROY: 80
INIT: 9
When clicking Edit for a second time this output shows that
EditComponent#98 is destroyed as we navigate away from it (expected)
EditComponent#80 is created and immediately destroyed (unexpected)
EditComponent#9 is created as we are now 'editing' new item (expected)
This just shows me that many <ui-view>s together with ng-ifs does not play very nice but I have no idea how to fix that.
One thing that I have tried was I created one <ui-view> in ListComponent and was moving it around on ui-router state change by means of pure javascript. But that did not work as I soon started getting errors from ui-router's framework that were related to missing HTML node.
Question:
What am I doing wrong here? I think that angular's digest cycle (and related DOM changes) end later than ui-router starts transitions and related component creation and destruction and that might be a reason of EditComponent#80 being created and quickly destroyed. But I have no idea how to fix that.
Here is a codepen showing what is happening:
https://codepen.io/ramunsk/project/editor/AYyYqd
(don't forget to open developer console to see what's happening)
Thanks
Let's say you're switching from index 2 to index 3. I think this might be what is happening:
The ui-view at index 2 is currently active. In the click handler you call state.go and the ui-view at index 2 briefly receives the updated state parameter id: 3. Then it is destroyed when the ng-if takes effect and the ui-view at index 3 is created.
Change your code so it destroys the ui-view at index 2 first. Add a timeout so it calls state.go shows the second ui-view in the next digest cycle.
this.onEditClick = function(index, id) {
this.editIndex = null;
$timeout(() => {
this.editIndex = index;
$state.go("list.edit", { id: id });
});
};
https://codepen.io/christopherthielen/project/editor/ZyNpmv

Delay in ng-repeat

How can I insert delay between every ng-repeat iterations so my table will generate records slower. Is there any way to do it without using ngAnimate.
<table>
<tr ng-repeat="x in records">
<td>{{x}}</td>
</tr>
</table>
[Suggestion]
If you data is loading slow, maybe is because you have duped keys, so for test it you can try with track by $index like this
<table>
<tr ng-repeat="x in records track by $index">
<td>{{x}}</td>
</tr>
</table>
[Solution ]
If you still want to control the interaction of ng-repeat, it is best to create a dynamic variable that is manipulated as time passes, then you can have a primary array with all records
$scope.records = [
{
"name": "name1",
"data2": "data2.1",
"data3": "data3.1"
},
{
"name": "name2",
"data2": "data2.2",
"data3": "data3.2"
},
{
"name": "name3",
"data2": "data3.3",
"data3": "data3.3"
}
];
Then you could use setTimeout to call a function that passes data from the primary array to another final array, an index per interaction
//start to proccess
setTimeout(function(){$scope.Result();},1000);
//Here pass data from Records to FinalResult in each interaction
$scope.Result=function(){
dif=$scope.records.length-$scope.FinalResult.length;
currentRow=$scope.FinalResult.length;
if(dif>0){
$scope.FinalResult.push($scope.records[currentRow]);
}
if($scope.records.length>$scope.FinalResult.length){
setTimeout(function(){$scope.Result();},1000);
}else{
console.log('Finish Load');
$scope.FinishRender=true;
}
//refresh
$scope.$apply();
}
And finally deliver this variable with another function...
//get the finish Array
$scope.getFinalResult=function(){
return $scope.FinalResult;
}
and HTML
<body>
<div ng-controller="recordsCtrl">
<table style="border:1px solid black">
<tr ng-repeat="x in getFinalResult()">
<td>{{x.name}}</td>
<td>{{x.data2}}</td>
<td>{{x.data3}}</td>
</tr>
</table>
<div ng-if="FinishRender" style="color:red;font-weight:bold">Data Loaded!!!</div>
</div>
</body>
Please feel free to check a solution in punkler
[Optional]
Also you could use a directive to control the last interaction like this
myApp.directive('onFinishRender', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attr) {
console.log(element);
if (scope.$last === true) {
console.log('Finish Load');
}
}
}
});
and html
<table>
<tr ng-repeat="x in getFinalResult()" on-finish-render="onFinishRender">
....
...
</tr>
</table>
note:I'm not really sure but I think it's possible to capture every interaction with this method
A possible solution in your situation could be to take the source array and populate the ng-repeat array in increments with a delay using _.chunk and $timeout, as such:
index.html
<table>
<tr ng-repeat="x in records track by $index">
<td>{{x}}</td>
</tr>
</table>
appCtrl.js
$scope.sourceData = [data, data, data];
$scope.records = [];
/**
*#param source (array): the array with the data used to populate the ng-repeat array
*#param target (array): the array to which ng-repeat points
*#param delay (integer): the render delay, in milliseconds
*#param renderSize (integer): the amount of list items to render between each delay
*
**/
function delayedRender(source, target, delay, renderSize) {
var promise = $q.resolve();
function scheduleRender(partial) {
Array.prototype.push.apply(target, partial);
// the timeout will ensure that your next render won't occur before the delay
return $timeout(function(){}, delay);
}
// _.chunk is a Lodash function that takes an array and chops it into smaller chunks.
// 'renderSize' is the size of these chunks.
var partials = _.chunk(source, renderSize);
var next;
// here we schedule renders to occur only after
// the previous render is finished through the use of $q promises
_.forEach(partials, function(partial) {
next = scheduleRender.bind(null, partial);
promise = promise.then(next);
});
}

Angular Updates Model but no Two Way Binding w/ Custom Filter

I recently wrote a simple custom filter which only displays items in my model given a specific model property and it works great. It is below..
Filter
app.filter('status', function() {
return function(input, theStatus) {
var out = [];
for (var i = 0; i < input.length; i++) {
var widget = input[i];
if (widget.status === theStatus)
out.push(widget);
}
return out;
};
});
The filter is applied as such on an ng-repeat.
<tr ng-repeat="widget in pendingWidgets = (widgetList | status: 0)">
<td><span class="glyphicon glyphicon-usd" /></td>
<td><span class="glyphicon glyphicon-usd" /></td>
<td><span class="glyphicon glyphicon-usd" /></td>
<td><span class="glyphicon glyphicon-usd" /></td>
</tr>
And on a panel heading as so
<div class="panel-heading"><span class="badge">{{pendingWidgets.length}}</span></div>
When the glyph is clicked ng-click runs updateStatus() as below...
$scope.updateStatus = function(theId, newStatus) {
widgets.setStatus(tagNumber, newStatus);
$scope.displayAlert = true;
};
And the widget.setStatus() is as such..
app.factory('widgets', ['$http', function($http) {
var o = {
widgets:[]
};
o.setStatus = function(aWidget, theStatus) {
return $http.put('/widgets/' + aWidget, { 'status': theStatus }).success(function (data) {
// do I need to put something here?
});
};
return o;
}]);
My question lies in
How can I get my page to refresh on the ng-click action when the updateStatus() call is made on my model? When the glyph is clicked the model is updated but the page is not. Only on a page refresh or when I visit a different page and then come back does the page display the updated model accurately with respect to the custom filter.
It doesn't look like you're updating the status for a particular widget (on the client side). You're telling your server about the update, but on the client side, no update happens.
That's why when you refresh (i imagine you're loading the widgets from the db / backend) you see the update.
Where you have: // do I need to put something here? you need to do something like:
aWidget.status = data.status; // where data is the updated widget object
(this assumes that your backend is returning the updated widget - which if you're following the same conventions that I'm used to - it should be).

ng-table data not showing on page load

I have integrated ngTable into my mean.io stack and I'm having trouble with populating the table on page load. If I select one of the column headers, the data shows up and the table works as advertised.
Here is my html
<table ng-table="tableParams" class="table">
<tbody ng-repeat="p in $data">
<tr id="tr{{p._id}}" ng-class-odd="'odd'" ng-class-even="'even'">
<td class="rowTd" data-title="'Task Code'" sortable="'task_code'">{{p.task_code}}</td>
<td class="rowTd" data-title="'Task Name'" sortable="'task_name'">{{p.task_name}}</td>
<td class="rowTd" ><input type=button id="editRowBtn{{p._id}}" value="edit"
ng-click="setEditId(p._id)"></td>
</tr>
<tr ng-show="editId===p._id" ng-if="editId===p._id">
<td colspan="7" ng-include src="'editRow.html'"></td>
</tr>
</tbody>
</table>
Here is my controller code.
var data = GeneralTasks.query();
$scope.tableParams = new ngTableParams({
page: 1,
count: 10
},{
total: data.length,
getData: function($defer, params) {
params.total(data.length);
$defer.resolve(data.slice((params.page() - 1) * params.count(), params.page() * params.count()));
}
});
$scope.editId = -1;
$scope.setEditId = function(pid) {
$scope.editId = pid;
};
I am new to using this table so i'm sure there is something i'm overlooking.
Wanted to provide the answer to my question so it may help others. Anytime an item in the table is added or removed, the table must be reloaded. Since $save and $remove invoke a callback function, just inserted the following for updating the table.
$scope.add = function() {
if (!$scope.tasks) $scope.tasks = [];
var task = new GeneralTasks({
task_code: $scope.task_code,
trade: $scope.trade,
task: $scope.task,
task_name: $scope.task_name
});
task.$save(function(response) {
$scope.tasks.push(response);
var data = $scope.tasks;
$scope.tableParams.total(data.length);
$scope.tableParams.reload();
});
this.task_code = this.trade = this.task = this.task_name = '';
};
First i update the $scope list with the response and then update the tables data and length. Then just call reload.
As I've stated earlier, i do this for $save and $remove. Here is the $remove code.
$scope.remove = function(task) {
for (var i in $scope.tasks) {
if ($scope.tasks[i] === task) {
$scope.tasks.splice(i, 1);
}
}
task.$remove();
var data = $scope.tasks;
$scope.tableParams.total(data.length);
$scope.tableParams.reload();
};
I have noticed that when I edit a name in the list and then cancel, the name does not reset. I suppose I should add similar code for the cancel action but I'm lazy and that's the least of my worries for now. :)
Hope this helps someone else.

ng-repeat ng-click when the click function has already been called earlier in the code

First off, I read the plethora of other questions and answers regarding ng-click, ng-repeat, and child and parent scopes (especially this excellent one.)
I think my problem is new.
I'm trying to call a function using ng-click within a table. The app allows for the sorting of Soundcloud likes. The problem is that when I try to call the ng click function using new data, it still tries to call the function using the old data. Let me explain better with the example:
Controller:
function TopListCtrl($scope, $http) {
$scope.sc_user = 'coolrivers';
$scope.getData = function(sc_user) {
var url = 'http://api.soundcloud.com/users/'+ $scope.sc_user +'/favorites.json?client_id=0553ef1b721e4783feda4f4fe6611d04&limit=200&linked_partitioning=1&callback=JSON_CALLBACK';
$http.jsonp(url).success(function(data) {
$scope.likes = data;
$scope.sortField = 'like.title';
$scope.reverse = true;
});
}
$scope.getData();
$scope.alertme = function(permalink) {
alert(permalink);
};
}
HTML
<div id="topelems">
<p id="nowsorting">Now sorting the Soundcloud likes of <input type=text ng-model="sc_user"><button class="btn-default" ng-click="getData(sc_user);">Sort</button></p>
<p id="search"><input ng-model="query" placeholder="Filter" type="text"/></p>
</div>
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Song</th>
<th>Artist</th>
<th>Likes</th>
<th>Played</th>
<th>Tags</th>
</tr>
</thead>
<tr ng-repeat="like in likes.collection | filter:query | orderBy:sortField:reverse">
<td width="30%"><a href="{{ like.permalink_url }}">{{like.title}}</td>
(Trying to call it here) <td>{{like.user.username}}</td>
<td>{{like.favoritings_count}}</td>
<td>{{like.playback_count}}</td>
<td>{{like.tag_list}}</td>
</tr>
</table>
</div>
</body>
</html>
So I have that function getData. I use it to load the data in using the soundcloud api. I have a form at the top that uses getData to load a new user's Soundcloud data in. I also call the getData function in the controller so that there is an example on the page upon loading.
The problem is when I try to load a new user's data from a <td> I want to be able to click on the user to see and sort their likes.
How do I 'clear' the function or the global namespace (am I even refering to the right thing)? How can I reuse the getData function with a new variable?
Working Jsfiddle for this
In your getData function you have this line:
var url = 'http://api.soundcloud.com/users/'+ $scope.sc_user +'/favorites.json...
but you are passing in the variable sc_user to your getData function and should be using it like this (no $scope):
var url = 'http://api.soundcloud.com/users/'+ sc_user +'/favorites.json...
That being said... your initial data load fails because you are calling:
$scope.getData();
and not:
$scope.getData($scope.sc_user);

Resources