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.
Related
I use Meteor 1.2.1. I am currently developing a Course Catalog Web App.
I'm trying to create a filtering feature just like Angular Filters. It should let the user specify his needs and then the course list refreshes automatically as he changes the filters.
This is my code:
app.html
<!-- ********** -->
<!-- courseList -->
<!-- ********** -->
<template name="courseList">
<div class="list-header">
<div>Course Name</div>
<div>Description</div>
<div>Category</div>
<div>Modality</div>
<div>Unit</div>
</div>
<div class="list-body">
{{ #each course }}
{{ > courseRow}}
<hr />
{{ /each }}
</div>
</template>
app.js
Template.courseList.helpers({
'course': function(){
Session.setDefault('course', Courses.find().fetch());
return Session.get('course');
}
});
So when I run this, I get an empty set for the course helper above, even though there's some dummy data present on the database.
It's seems to me that the issue is with Session.setDefault(). When I console.log the Session.course variable right after the find, I get an empty array [], maybe because there was no time to get the data from the server (or maybe not, because I'm developing with autopublish for now, I don't really know).
After I apply some of the filters (code not shown here), everything goes back to normal. This initialization only is the problem.
I've tried to call Session.set() inside Template.courses.rendered(), Template.courses.created(), Template.courses.onRendered(), Template.courses.onCreated() (yes, I was kinda desperate) but none of them worked.
Can someone please advise on that issue? Maybe I'm not trying the correct Meteor aproach, as I am a Meteor beginner.
Thanks!
I think you try to use template subsciption. so you can send some terms for publication. example;
Template.foo.onCreated(function() {
this.name = new reactiveVar("");
this.autorun( () => {
const terms = {
name: this.name.get()
};
this.subscribe("Posts_list", terms);
});
});
Template.foo.helpers({
lists: function() {
return Posts.find().fetch();
}
});
There are two things here. First thing is that your helper will run whenever either the result of Courses.find().fetch() or Session.get('course') changes, since both of them are reactive data sources. To avoid this, you can do
Template.foo.helpers({
lists: function() {
return Posts.find().fetch();
}
});
If you want to set the session variable so that you can use it somewhere else, then you can use Session.set instead of Session.setDefault, because Session.setDefault will only assign value only is the session variable is not already available. But I don't know why you want to do that, since you can use Posts.find().fetch() where ever required instead of session variable. Both are reactive.
Template.foo.helpers({
lists: function() {
Session.set('course', Courses.find().fetch());
return Session.get('course');
}
});
If you want to assign the course session variable only for the first time when you get non-empty data then you might want to do something like this.
Template.foo.helpers({
lists: function() {
if (Courses.find().count() > 0) {
Session.setDefault('course', Courses.find().fetch());
return Session.get('course');
} else {
return [];
}
});
If you can tell why you need the session variable, we might think of a different approach to solve the problem.
Hope it helps.
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.
I am seriously having a hard time retrieving nested data from Firebase with AngularJS. I try to save data this way in Firebase, btw I am using Angularfire to save and retrieve data from the database. How can I retrieve the value of foo with ng-repeat? I also find that the documentations is scarce and tutorials are outdated. I like the real time updates and that is why I want to use it. There are no errors in the console btw. Thanks in advance for any suggestions.
--Unique id
--0
--foo:"bar"
--1
--foo:"bar"
Now when I do this in my HTML it gives me back the index number:
<div ng-repeat="(item, id) in list">
<span>{{item}}</span><button ng-click="remove(id)">x</button>
</div>
</div>
But when I do this, it does not give me the values of foo:
<div ng-repeat="(item, id) in list">
<span>{{item.foo}}</span><button ng-click="remove(id)">x</button>
</div>
</div>
Javascript
angular.module("app", ['firebase'])
.controller("ctrl", function ($firebase, $scope) {
var ref = new Firebase('https://testing12344.firebaseio.com/');
var x=[{foo:"bar"}, {foo:"bar"}];
var sync = $firebase(ref);
var list = sync.$asArray();
list.$add(x);
$scope.list = list;
console.log($scope.list);
$scope.remove = function (id){
list.$remove(id);
}
})
As I already commented: Firebase's documentation recommends against building nested data structures; partially because of the reason you encounter. The only time I find myself nesting data structures like this, is when a user is typically "inside" one of these top-level children. In that case the outermost ID is part of the context and I won't need a nested loop, like you do.
That said, you can easily make your code/view work if you ensure it matches the data structure correctly. Since you have a collection (with children 0 and 1) inside a collection (with the generated IDs), you'll need to ng-repeats to reach foo:
<ol>
<li ng-repeat="item in list">
{{item.$id}}
<ol>
<li ng-repeat="child in item">{{child}}</li>
</ol>
</li>
</ol>
A snippet from the output:
1. -Jgv9EmOXmXYNrYPG8jK
1. {"foo":"bar"}
2. {"foo":"bar"}
2. -Jgv9GEXJLnaQmYeYR2u
1. {"foo":"bar"}
2. {"foo":"bar"}
3. -JgvHQ1YJsgF9THdfmd7
1. {"foo":"bar"}
2. {"foo":"bar"}
I also noticed that you're logging console.log($scope.list);, which is a common mistake. By the time that console.log statement executed, the data may not have been loaded from Firebase's servers yet. Sometimes it shows up correctly in your browser's JavaScript console, but sometimes it doesn't.
The proper way to log the data once it is loaded is:
list.$loaded().then(function(list) {
console.log('Initial child count', list.length);
console.log(list[0][0].foo);
});
Note that $loaded will only trigger for the initially downloaded data, not for subsequent updates. If you care about those, you should look into AngularFire's $watch. But to be honest: you should normally not have a need for that. Once you bind your AngularFire data to the view correctly, it should update automatically in most use-cases.
Since I began learning AngularJS I've seen different approaches for calling controller functions from a view.
Suppose we have a Todo list application in AngularJS where you can add and remove Todo list items:
function TodoListCtrl() {
var vm = this;
vm.addItem = addItem;
vm.removeItem = removeItem;
activate();
function activate() {
vm.list = [];
}
function addItem() {
vm.list.push(vm.newItem);
// reset the form
vm.newItem = null;
}
function removeItem(item) {
vm.list.splice(vm.list.indexOf(item, 1));
}
}
And our HTML:
<h3>Todo List</h3>
<ul>
<li ng-repeat="item in vm.list">
{{ item }} <a ng-click="vm.removeItem(item)">Remove</a>
</li>
</ul>
<h4>Add Item</h4>
<input type="text" ng-model="vm.newItem" /> <button ng-click="vm.addItem()">Add</button>
In this example the addItem function depends on vm.newItem being set in order to add the new list item. However, it could also be re-written as:
function addItem(item) {
vm.list.push(item);
// reset the form
vm.newItem = null;
}
With our HTML updated as so:
<button ng-click="vm.addItem(vm.newItem)">Add</button>
I can see that this makes the function easier to test since we're not dependent on the state of the controller but we don't avoid it completely since we're resetting vm.newItem after an item is added.
Are there any best practices for when we should pass parameters from our views and when we can just rely on the internal state of the controller?
Passing vm.newItem means you have it in 2 places in the View. While it may be clear, it's also repeating yourself and leaves you open to possibly having one get out of sync. And at what additional value? I think it is clear already without that like this.
<input type="text" ng-model="vm.newItem" />
<button ng-click="vm.addItem()">Add</button>
Otherwise you have this duplication.
<input type="text" ng-model="vm.newItem" />
<button ng-click="vm.addItem(vm.newItem)">Add</button>
You say it is easier to test, but why? You are testing a function on the controller, so it's perfectly fine to expect that function is use a property on the same controller. You mock dependencies to the controller, but not members of it.
In both cases you showed, the function addItem creates side-effects on the internal state of the controller (with vm.newItem = null;), so it cannot be tested in isolation.
In the second case, however, this doesn't even make sense to pass a different variable, since it would make the statement vm.newItem = null; potentially erroneous.
To make the function completely state-less:
vm.addItem(item){
vm.list.push(item);
}
you'd need to reset the form from the View:
<input type="text" ng-model="vm.newItem">
<button ng-click="vm.addItem(); vm.newItem = null">Add</button>
This could be acceptable if resetting the form is a View-only concern. If not, then this approach would not work altogether because it could leave your controller in an erroneous state (where vm.newItem is still pointing to the newly added item of the list)
At the end of the day, it depends on your particular use case. If you always have only a single item that you can "add", then passing an explicit parameter is redundant at best.
If, however, addItem can be called for any newly created item in the View, then passing the reference explicitly is probably the only way to go.
Testing-wise, you should always test that a controller is in a consistent state after an operation.
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.