I use Ionic and AngularJS and have the following problem:
Here is the view:
<ion-list>
<ion-item ng-repeat="item in items track by $index" >
<h1>{{item.text}}</h1>
{{item.timestring}}
</ion-item>
</ion-list>
and the controller:
$scope.items = foo.getItems();
the method in foo looks like:
foo.loclStorages = JSON.parse(localStorage.getItem("foo"));
foo.getItems = function () {
if (foo.loclStorages === null) {
return [];
} else {
return jsdata.loclStorages.items;
}
};
Now the problem:
When the list is empty and I add the first element, then I first need to update the site to see it.
When I already have items in the list, i can see it instantly after the update.
What I need to do to also see it instant after adding the first element?
When your list is empty, then $scope.items equals some empty array [].
When your list isn't empty, then $scope.items is a reference to jsdata.loclStorages.items.
Since these two arrays aren't the same reference, then when your list starts as empty, adding items to localStorages.items will have no effect on the array in $scope.items.
To solve this, you need to check when you add an item if the reference in the scope is the same reference as the array in jsdata. If not, make sure to call $scope.items = foo.getItems(); again.
Related
really new to Angular here and I have a problem that I need some help with. Basically, I need to have a selection of items that user can click on. When an item is clicked, the page needs to show some of the properties that the item has like it's description, etc. The first part is not a problem, but I'm having trouble with the second part, which is displaying the data. So here is what I have:
On the front end, I have an angular ng-click chooseItem(item) function that takes the clicked item as its paramater:
<div ng-repeat="item in items" class="col-xs-2 col-md-1">
<div ng-click="chooseItem(item)" class="thumbnail">
<img src="/images/items/{{item.name}}.png"/>
</div>
</div>
This is then passed on to the items factory through items.getChosenItemData(item) function. Since the real item data is stored in Mongo and not the factory, this function queries the db to retrieve the item data. This retrieved data is stored into the chosenItem object, which is then passed back to the controller as $scope.chosenItem.
app.factory('items', ['$http', function($http){
var objects = {
items: [
// ... more items before these
{name: "Pencil"},
{name: "Pen"}
/* I use these item objects as keys to the items themselves.
The ng-repeat iterates through all of the names for each item
which allows me to display static images for each item to the page.
There aren't many items, about 100, but they have tons of json information
so to hardcode it all into here is not an option
A way to do this without any hardcoding would be nice! */
// more items after these
],
// this is used to store a currently clicked item's values
chosenItem: null
}
objects.getChosenItemData = function(name){
return $http.get('/items/' + name).success(function(data){
// console.log(data);
angular.copy(data, objects.chosenItem);
console.log("Chosen Item: ", objects.chosenItem);
})
}
return objects
}]);
app.controller('MainCtrl', [
'$scope',
'items',
function($scope, items){
$scope.items = items.items;
$scope.chosenItem = null;
$scope.chooseItem = function(item){
items.getChosenItemData(item.name);
$scope.chosenItem = items.chosenItem; //chosen item object attribute in factory
console.log("$scope item: ", $scope.chosenItem);
}
}
});
This almost all works. I can query the data of the clicked item successfully, but returning it is another story. Upon first click, the value of $scope.chosenItem is null. Then upon second click, it stores the value of the click item. This also causes the problem where if I click on n amount of items, the value stored is always the value of the n-1 item, not the current item. I need it to store the value of the clicked item on the first click, not the second.
I have a feeling I need to add a callback somewhere in here to make it work, but I'm new to Angular/JS so I'm not sure where it should even go.
Thanks for any help! Also any tips or leads on Angular design patterns would be much appreciated, since I have the feeling that this is a terrible implementation of something that seems rather simple.
I suggest you to expose the service directly:
$scope.serviceItem = items;
and then you can call it in the view like that:
{{serviceItem.chosenItem}}
It will be always updated to the latest clicked value.
I hope it helps.
I'm paginating an Angular table and want to display all the page numbers beneath the table.
I'm planning to create an array of the page numbers and then use ng-repeat to display them all:
HTML
<tr ng-repeat-start="item in c.filteredList = (c.data | dynamicFilter:c.filter | orderBy:c.sortOrder.order:c.sortOrder.reverse)">
JS
this.checkPage = function(){
this.pageNumArr = [];
for(i=0; i<this.filteredList.length/this.perPage; i++){
this.pageNumArr.push(i);
}
}
Where this.perPage is the number of items per page (set by the user).
What I can't figure out is how to trigger checkPage() whenever the filter changes.
You would be best binding your page number ng-repeat to a function that creates and returns the array of page numbers. This will create a watcher for the function and keep the array of page numbers up to date.
There will be no need to manually create a $watch in your controller.
this.pageNumbers= function(){
var pageNumArr = [];
for(i=0; i<this.filteredList.length/this.perPage; i++){
pageNumArr.push(i);
}
return pageNumArr
}
<span ng-repeat="page in c.pageNumbers()">{{page}}</span>
I think that triggering events inside a filters shouldn't be considered a best practice, probably, you need to find another approach.
By the way, there are many way:
If you can edit that filter, simply, pass the $scope reference to it and trigger the event via $scope.emit or $scope.broadcast: <li ng-repeat="item in items | myFilter:[param1, param2, paramN]"></li>
Angular supports filter inside a controller, so, probably this should be a better solution https://toddmotto.com/everything-about-custom-filters-in-angular-js/ (have a look at Filter 4: Controller/$scope filter);
Register a watcher on your model, but, this is bad for performances...
You can watch for the filteredList and call the checkPage() there:
var self = this;
$scope.$watch(
function() {
return self.filteredList.length;
},
function(newValue, oldValue) {
if (newValue !== oldValue) {
self.checkPage();
}
}
);
I have a problem with my angularjs app where my app is routing to the wrong page when using an ng-repeat array to determine the route.
data looks like this and is accessed in the person controller:
[
{
"name":"AJ lastname",
"img_name":"AJ_lastname",
"location":"Baltimore, Maryland",
"info":"stuff"
},
{
"name":"Albert lastname",
"img_name":"Albert_lastname",
"location":"Boston, Massachusetts",
"info":"stuff"
} // ... more data
]
html: (the anchor tag links to the person based on their index in the array (I believe this may be what I need to change to fix the problem, but I'm not sure)
<ul class="main-list">
<li class="list-item fade" ng-repeat="student in students | filter:filter">
<a href="/#person/{{$index}}">
<img class="portrait listimg" ng-src="/images/{{student.img_name}}.jpg" alt="portrait of {{student.name}}">
<h2>{{student.name}}</h2>
<h4>{{student.location}}</h4>
</a>
</li>
</ul>
Routing from angular: (the route with '/person/:itemId' is routing to a page specific to a specific user, where their index in the array determines their id)
app.config(function ($routeProvider, $httpProvider) {
$routeProvider
.when('/list', {
templateUrl: './js/templates/list.html',
controller: 'ListController'
})
.when('/person/:itemId', {
templateUrl: './js/templates/person.html',
controller: 'PersonController'
})
.otherwise('/list');
});
Here is the controller for the dynamic page. It works perfectly for the original array, but once I attempt to sort the array, the index no longer corresponds to the correct student.
app.controller('PersonController', function ($scope, $http, $routeParams) {
$scope.person = 'Someone\'s name';
$http.get('../js/students.json').success(function (data) {
$scope.allStudents = data;
$scope.studentId = $routeParams.itemId;
$scope.student = data[$scope.studentId];
});
So the functional problem is that the index applies to the first student in the large array of data. It appears to work perfectly, and the correct data populates the page, but when I use the html/text input to filter the list, the original indices are updated on the html side, and they do not correspond to the original array. So the routing sends them to the wrong page.
How can I make the routing work even for a filtered list?
One way you can do this is by using a function which returns you the index a student had in the original array for each student in your ng-repeat.
$scope.getIndex = function(student) {
return $scope.students.indexOf(student);
}
You can then call the function in your list like:
<a ng-href="/#person/{{getIndex(student)}}">
This though is not quite the most performant code you could imagine.
Another way would be to just temporarily store the index of the student as a property and use that one to reference it, again not quite the nicest solution:
$scope.students = $scope.students.map(function(student, index) {
student.index = index;
return student;
});
And in the list:
<a ng-href="/#person/{{student.index}}">
However, if you can somehow assign the students a unique id that would definitely be the preferred way. That way you also make sure that you always reference the same student. If your students.json somehow changes between the time you create the list and the time the user clicks on an item you may reference the wrong one again...
By the way always use ng-href when including placeholders in the link. Why you should do so is well described in the Angular API docs:
Using Angular markup like {{hash}} in an href attribute will make the link go to the wrong URL if the user clicks it before Angular has a chance to replace the {{hash}} markup with its value. Until Angular replaces the markup the link will be broken and will most likely return a 404 error. The ngHref directive solves this problem.
You are creating the ng-repeat using some object on $scope called students, correct? If this is being built from the same students.json as in your controller then their student ids should logically be equivalent. So just change the href from "/#person/{{$index}}" to "/#person/{{student.studentId}}".
If for some reason they aren't the same then when you create the students object you can add a new attribute, studentId, which holds the value of their index in the array and then use the previous suggestion.
Just remember that when using ng-repeat if you have identical objects it'll throw an error so you have to add the "track by $index" to it.
I am learning angularJS, went through few tutorials and sort of know my why around. It seems that the page never refreshes, therefore a value created in one view should be available in another view, right? I am testing this in a shop scenario. If we are at the main view, and we click on "add to cart" that should trigger a function in the background and add the item in an array. Then when we go to the cart view, we can see the item listed there. But this does not work.
I have a cart controller:
angular.module('shoppingCartApp')
.controller('CartCtrl', function ($scope) {
$scope.cart = [
'one item'
];
$scope.pushing = function(item){
this.cart.push(item);
};
});
In the main view (which doesn't have access to this controller) I have.
<div ng-controller="CartCtrl">
add to chart
</div>
And on the cart view I display the cart object
<div ng-repeat="item in cart">
{{item}}
</div>
We only see the one item. I have also added the ng-click attribute to this page as well, just to test, and it does work, however, if we go home and come back, the item is gone.
From the idea that the page never reloads, should the pushed items stay in the array? here is the simple example in action
Thanks
Controllers are not singletons, so when you change the view the $scope gets destroyed and a new controller will be initialized. If you want persistent data across different views then you want to look at using a service to store it, since they are singletons.
If you create something like
angular.module('app')
.service('cartService', [function() {
var cart = [];
var add = function(item) {
cart.push(item);
};
var get = function() {
return cart;
};
return {
add: add,
get: get
};
}]);
Then you can add that as a dependency in your controllers and use that for backing your data rather than using the $scope.
angular.module('app')
.controller('Ctrl', ['cartService', function(cartService) {
$scope.cart = cartService.get();
$scope.pushing = function(item) {
cartService.add(item);
};
}]);
I am following a todo list angular tutorial. Right now, I would like to loop through all of my todo list and clear all of the items where the attribute done is true.
Right now in my index.html file I have a button with the ng-click attribute of "clearCompleted()". That function looks like this in my js file:
$scope.clearCompleted = function () {
$scope.todos = $filter($scope.todos, function(todo){
return !todo.done;
});
};
What is wrong with this function because it is not clearing the todo items that are being set to done:true.
And in more of a general question, what is the typical way I could say something along the lines of "select all the items where the attribute done is true" because I am more used to ruby and not javascript.
$filter(name)gets the filter with that name. In order to actually call it you can write `$filter(name)(/arguments for the filter/)
There is a predefined filter called filter that can be used for filtering arrays. You pass in an object for comparisons:
$scope.clearCompleted = function () {
$scope.todos = $filter("filter")($scope.todos, {done:false});
};
Which returns all items that have the property donewith the value false.
Try this syntax:
$scope.todos = $filter("filter")($scope.todos, function(todo){
Example:
http://jsfiddle.net/cherniv/89Qqs/
And don't forget to inject the $filter service into controller first!