AngularJS ng-repeat directive loses data after update data parent $scope - javascript

I have a notifications page generated by a ng-repeat looking like this:
The list is generated like this:
<div id="notifications" class="page_content">
<div class="notification center" notification-view notification="notification" providers="notifications.providers" ng-repeat="notification in notificationsObjects">
</div>
After the user completed the notification (and the data is updated in the database) I update the notificationsObjects array by removing the completed notification. I do this with the following function:
$scope.removeNotificationFromNotificationList = function(notification){
for(var i = 0; i<$scope.notificationsObjects.length; i++){
if($scope.notificationsObjects[i].id == notification.id){
var indexToRemove = $scope.notificationsObjects.indexOf($scope.notificationsObjects[i]);
var updatedArray = $scope.notificationsObjects;
updatedArray.splice(indexToRemove, 1);
$scope.notificationsObjects = updatedArray;
}
}
}
But after updating the array, this happens although the data is still available:
The 2 arrays (before and after updating) look exactly what I expect but for some reason the directive for a single notification doesn't have the notification object anymore. These are the 2 notificationsObjects arrays (before and after removing the completed notification):
I thought by just recalculating the notifications and reset the new $scope.notificationsObjects would solve my problem, and it does, but it reloads my whole DOM and I don't want that. I just want the completed notification to disappear from the array (and so, remove it from the DOM). Does anyone have a clue what causes this problem, or does anyone have a better solution to solve this?
Thanks in advance!

I solved it now but didn't really find the cause. I noticed some variables in the single notification directive not being set on the $scope. Even though they were not depending on changes, I coupled them to the $scope and the problem was solved.

Related

Ionic, List populating via search

I am using a backemnd service (parse in this case but that doesn't really matter for this question) and wanted to simply search it. I have a textbox that upon text being entered searches the server and returns an array of matchs.
My next step is to simply display my returned objects nicely in a list. Easy enough with ng-repeat but because the view has already been loaded the UI won't update to reflect the array being loading into the list. Does that make sense?
I was wondering if there was a technique to Refresh the list and show the returned search elements, and hopefully I am not being to greedy here but doing it in a way that looks good and not clunky.
I did a lot of googling with NO luck :( any advice would be amazing.
Without any code provided it is hard to guess what is wrong. Angular has two-way binding, so view should be updated automatically after changing content of an array. If it's not, it means that you probably did something wrong in your code. I present an example code which should work in this case.
Controller
angular.module('moduleName')
.controller('ViewController', ['ViewService', ViewController]);
function ViewController(ViewService) {
var self = this;
self.arrayWithData = [];
self.searchText = "";
// ---- Public functions ----
self.searchData = searchData;
// Function which loads data from service
function searchData(searchText) {
ViewService.getData(searchText).then(function(dataResponse) {
// Clear the array with data
self.arrayWithData.splice(0);
// Fill it again with new data from response
angular.forEach(dataResponse, function(item) {
self.arrayWithData.push(item);
});
});
}
// --- Private functions ---
// Controller initialization
function _initialize() {
self.searchData(self.searchText);
}
_initialize();
}
View
<div ng-controller="ViewController as view">
<input type="text" ng-model="view.searchText" />
<input type="button" value="Search!" ng-click="view.searchData(view.searchText)" />
<!-- A simple ngRepeat -->
<div ng-repeat="item in view.arrayWithData">
<!-- Do what you want with the item -->
</div>
</div>
Conclusion
By using splice() and push() you make sure that reference to your array is not changed. If you are using controllerAs syntax (as in the example), assigning new data with '=' would probably work. However, if you are using $scope to store your data in controller, losing reference to the array is the most probable reason why your code doesn't work.

Is there a way to "watch" an IndexedDB record so I can respond when it's been changed?

I am building an Angular + TypeScript application that uses IndexedDB for storing data locally.
I have an Angular directive that sets the value of a scope variable to be some data that was returned from an IndexedDB request. It's pretty simple and does something like:
Directive A:
// getGroup() is making the requests to IndexedDB:
this.dataService.getGroup().then((groupOne) => {
this.scope.items = groupOne.items;
});
The view for my directive loops through each of these items:
<div ng-repeat="item in items">{{ item }}</div>
I have another Angular directive (let's call it Directive B) that is updating/inserting the items associated with groupOne.
Directive B:
groupOne.items.push("a new item");
this.dataService.updateGroup(groupOne).then(() => {
// groupOne has been changed!
// how can I let Directive A know about this, so Directive A can update its scope.items?
});
Of course, Directive A does not know about the changes Directive B made to groupOne unless it does another request. And therefore, my view is "static".
I know I could wrap Directive A's IndexedDB request into an interval and be checking for updates, but that seems like a strange way to solve this problem.
Is there a way with IndexedDB to be notified of this change? Is there something Angular provides that could help with this (something similar to $scope.$watch()) ?
It's not quite what I wanted (which is why I accepted dgrogan's answer), but in case anyone who stumbles upon this question is curious about what I ended up doing:
Manually broadcasting a custom event whenever I change groupOne (or anything else I care about) solves my problem for now.
I used $scope.$broadcast() from a controller (that's managing
a few interactions between directives) to let Directive A know about
the change Directive B made using $scope.$on().
This article was really helpful: http://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
There is not yet a way to do this with pure IndexedDB but it is being prototyped. The name is "Observers". You can try to use the polyfill if you are so inclined.

ngResource remove record from query object with $delete

Fairly new to angular. I want to use angular's $resource library to consume our API services. I'm a little lost on the proper way to delete a record obtained via the query() method. Specifically, we have an endpoint for user notifications. We want to, on page load, get all user notifications, use ng-repeat to loop over the results and display the notifications in the nav bar. When a user clicks a remove icon, the corresponding notification should be deleted. Here's the stripped down version of the code I currently have:
Js:
angular.module('myapp', ['ngResource']).factory('Notifications',function($resource){
return $resource('/apiv2/user/notifications/:id', {id:'#id'});
}).controller('NavigationController',['$scope','Notifications',function($scope, Notifications){
$scope.notifications = Notifications.query();
$scope.deleteNotification = function(notification){
notification.$delete();
};
}]);
HTML:
<ul>
<li ng-repeat="notification in notifications">
<i class="icon-remove" ng-click="deleteNotification(notification)"></i>
</li>
</ul>
With this code, when a user clicks on the remove icon, the individual notification object is passed to the deleteNotification method and is properly deleted from the backend via the api. Up until this point, everything works as intended. However, if I look at the $scope.notifications object after the fact, the notification that was just deleted remains with broken data:
{$promise:undefined, $resolved:true}
Ideally, I want this record wiped from the object returned via the .query() method to reflect its state on the back end, without having to do a new .query().
Any help would be appreciated! I apologize for vague descriptions and/or incomplete/innaccurate code, I typed this all from memory via my phones keyboard whilst out at dinner, so god knows if I missed something.
Better way of doing it: (see AngularJS ngResource delete event)
$scope.deleteNotification = function (index) {
$scope.notifications[index].$delete();
$scope.notifications.splice(index, 1);
}
and in your markup just do
ng-click="deleteNotification($index)"
There probably is a better way to do this as this throws a console error (but still works), but this is what I am doing:
$scope.notifications = Notifications.query();
$scope.deleteNotification = function(notification){
notification.$delete();
$scope.notifications = $scope.notifications.filter( function(n)
return (n != notification);
}); // filter everything but
};
if you use underscore there is a more beautiful way to write the remove thing.

How to retain local UI state when using $resources with AngularJS

My problem is part of a larger program but I extracted a simulation of the problem in a simpler jsfiddle.
I'm implementing a "detailed view" show/hide controller for a todo item. When I click on the link, a detailed view is shown or hidden. The detailed view state is stored in todo.showDetails. This works fine.
When I mark a todo as completed, I persist its current state on the server using:
// Persist immediately as clicked on
$scope.checkTodo = function (todo) {
todo.$save();
};
This causes my detailed view to go hidden as the todo.showDetails state is lost because it's not part of the persisted item (i.e., it's overridden with the server's view of the todo item, which doesn't contain the UI state todo.showDetails). I'm not surprised, this makes sense.
My question is: how can my todo items contain both local UI state and data that's persisted on the server? When I todo.$save(), I'd like the todo's data to be saved on the server while retaining the value of todo.showDetails in the client code. I.e., whenever I call todo.$save(), I'd like my UI state to remain unchanged.
Is there an idiomatic way to do this in AngularJS? I wouldn't want to persist UI values like showDetails on the server.
One way I've been thinking of implementing this would be to have a separate service that stores UI state for each todo item. When I need to access local state, instead of accessing like todo.showDetails, I could do something like AppState.lookup(todo.id).showDetails or similar. But perhaps there's a simpler way..
-- UPDATE 2013-02-25 --
I did as in the below answer and simply separated UI state from todo items created by $resource.get/query calls. It was a lot simpler than I thought (see commit):
My controller changes:
$scope.todos = Todo.query();
+ $scope.todoShowDetails = [];
+ $scope.toggleShowDetails = function (todo) {
+ $scope.todoShowDetails[todo.id] = !$scope.todoShowDetails[todo.id];
+ }
+
+ $scope.showDetails = function (todo) {
+ return $scope.todoShowDetails[todo.id];
+ }
Template changes:
- <label class="btn btn-link done-{{todo.done}}" ng-click="todo.showDetails = !todo.showDetails">
+ <label class="btn btn-link done-{{todo.done}}" ng-click="toggleShowDetails(todo)">
..
- <div ng-show="todo.showDetails" ng-controller="TodoItemCtrl">
+ <div ng-show="showDetails(todo)" ng-controller="TodoItemCtrl">
It's simple when I put it in code. My jsfiddle example is a little bit contrived, it makes more sense in the context of my todo app. Perhaps I should just delete the question if it's either difficult to understand or too trivial?
Is it possible for you to extract that UI state from your todo object? You'd still place the state object on the scope, just not inside the todo object. It sounds like that type of state doesn't belong in the todo object anyway, since you don't want to persist it.

KnockoutJS data update writes internal function to UI

I'm having this odd issue when I update my viewmodel...basically with every update, there appears to be a random chance that each observable will contain this data:
function observable() {
if (arguments.length > 0) {
// Write
// Ignore writes if the value hasn't changed
if ((!observable['equalityComparer']) || !observable['equalityComparer'](_latestValue, arguments[0])) {
observable.valueWillMutate();
_latestValue = arguments[0];
observable.valueHasMutated();
}
return this; // Permits chained assignments
} else {
// Read
ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
return _latestValue;
}
}
I've been using KnockoutJS for a while, and I've never seen anything like this. My guess is that it has something to do with my template binding, but I'm really not sure. I'm going to dig into it, but I figured I'd post it here in case anyone else is having this issue, or has a solution. Like I said, it doesn't happen consistently, only on occasion.
//// More Information ////
So Matt below referenced this (http://stackoverflow.com/questions/9763211/option-text-becomes-a-function-string-after-updated-with-fromjs), which is roughly the same issue. The only difference is that I'm using the native template binding in a style like this:
<div data-bind="template: {name: 'issueTemplate', data: incidents}"></div>
<script id="dashboardIssueTemplate" type="text/html">
<!--ko foreach: $data-->
<div data-bind="text: title"></div>
</script>
It was my assumption that KnockoutJS handled the unwrapping by itself when you pass the observableArray into the template binder. I know I can't say "title()" in this example, because that doesn't exist. Am I supposed to be binding with a command like $root.title()?
//// Even More Information ////
It appears that this problem occurs as a result of having two "applyBindings" on one page. My application contains an external widget which adds it's DOM to the host page DOM at runtime. That widget is using the ko.applyBindings(vm, ROOTNODE) syntax which should allow for the host page to run it's own ko.applyBindings(hostVm).
In fact, it does, and it works correctly every refresh. The problem however is when the host page does a viewModel update with no refresh. Somehow, the UI rendering spits out this internal function on EVERY data-bound node. I've debugged through KnockoutJS and actually confirmed that the viewModel and rootNode are correct...something outside of the actual binding is taking over.
This has something to do with the "()" appended onto the data object in the template. What I've found is that during the first render (page load) writing the template like this:
<div data-bind="template: {name: 'issueTemplate', data: incidents}"></div>
<script id="dashboardIssueTemplate" type="text/html">
<div data-bind="text: title"></div>
</script>
works just fine. However, once you run the update on the observableArray my "title" object becomes that function. If I write the template using this style:
<div data-bind="text: title()"></div>
It seems to work on every update.
I am not certain why this is the solution. From the looks of it, the data object being passed to the Knockout binder is the exact same on both page load and update. I'll post this as an answer, but I'm not marking it as an answer until I understand why this is happening.

Categories