I have included dir paginate into my angularjs project to handle pagination easily, it works well up until the point where I want to filter all the pages results.
The controller that contains the data and filter looks like so:
app.controller('listCtrl', function ($scope, services) {
$scope.sort = function(keyname){
$scope.sortKey = keyname; //set the sortKey to the param passed
$scope.reverse = !$scope.reverse; //if true make it false and vice versa
}
$scope.currentPage = 1;
$scope.pageSize = 10;
services.getPosts().then(function(data){
$scope.posts = data.data;
});
});
The dir-paginate looks like so
<div dir-paginate="data in posts | itemsPerPage:5 | orderBy:sortKey:reverse" class="col-xs-12 post">
<div id="title" class="col-xs-3"></div>
<div id="title" class="col-xs-9"></div>
<div id="poster" class="col-xs-3">
<img src="pics/house.jpg" id="avatar">
<a ng-if="data.user_name == '<?php echo $currentUser?>'" href="edit-post/{{data.post_id}}"> Edit </a>
</div>
<div class="col-xs-9">
<div class="col-xs-12">
<p class="timestamp">{{ data.post_datetime }}</p>
</div>
<div id="rant">
<span style="word-wrap: break-word;">{{ data.post_content }}</span>
</div>
<div id="stats" class="col-xs-12"> <img src="pics/comment.png"><span>3</span>
</div>
</div>
</div>
<dir-pagination-controls
max-size="5"
direction-links="true"
boundary-links="true" >
</dir-pagination-controls>
I call the sort function like so:
<button id="changeLikes" ng-click="sort('post_id')">ID</button>
It only sorts the current page of Data, if i use the pagination and click page 2 it will then only filter page 2 and so on, any ideas here would be of great help getPosts() returns post data via php with no group or order by clauses in the sql that would affect the angular.
Have you tried flipping the order of the filters, i.e.
<div dir-paginate="data in posts | orderBy:sortKey:reverse | itemsPerPage:5" class="col-xs-12 post">
The dir-paginate docs state:
itemsPerPage: The expression must include this filter. It is required
by the pagination logic. The syntax is the same as any filter:
itemsPerPage: 10, or you can also bind it to a property of the $scope:
itemsPerPage: pageSize. Note: This filter should come after any other
filters in order to work as expected. A safe rule is to always put it
at the end of the expression.
Related
I am making a simple sports goods shopping app in AngularJs.
I am in a situation where I have three nested ng-repeats.
First loop: Get the brand name. I have written angularjs service that calls the rest endpoint to fetch the lists of brands (Adidas, Yonex, Stiga, etc). I am calling this service as soon as the page(controller) gets loaded.
Second loop: For each brand, I want to display the category of products they are offering. Inside this loop, I want to execute a function/service that will take the brand name as input and get all the categories for the brand. For this, I also have an angularjs service that calls the rest endpoint to fetch the list of categories for a given brand name.
Third loop: For each brand and category, I want to display the products in that category. Inside this loop, I want to execute a function that will take the brand name and category as input and get all the products in that category. I an angularjs service call which will call the rest endpoint to fetch the products given the brand name and category.
Sample data set:
Adidas
-----T-Shirts
----------V-Neck
----------RoundNeck
-----Shoes
----------Sports Shoes
----------LifeStyle Shoes
Yonex
-----Badminton Racquet
----------Cabonex
----------Nanospeed
-----Shuttlecocks
----------Plastic
----------Feather
Stiga
-----Paddle
----------Procarbon
----------Semi-carbon
-----Ping Pong Balls
----------Light Weight
----------Heavy Weight
Please note that because of some constraints I cannot have a domain object on the REST side to mimic the data structure shown above.
I want to display the above data in a tree-like fashion (something on the same lines as shown above possibly with expand/collapse options).
Below are the code snippets.
CONTROLLER:
(function () {
'use strict';
angular.module('SportsShoppingApp.controllers').controller('sportsController', ['sportsService', '$scope', function (sportsService, $scope) {
$scope.brands = [];
$scope.categories = [];
$scope.products = {};
$scope.getBrands = function () {
sportsService.getBrands()
.then(loadBrands, serviceError);
};
var loadBrands = function(response) {
$scope.brands= response.data;
};
$scope.getCategories = function(brand) {
sportsService.getCategories(brand)
.then(loadCategories, serviceError);
};
var loadCategories = function (response) {
$scope.categories = response.data;
};
$scope.getProducts = function(brand, category) {
sportsService.getProducts(brand, category)
.then(loadProducts, serviceError);
};
var loadProducts = function (response) {
$scope.products = response.data;
};
var serviceError = function (errorMsg) {
console.log(errorMsg);
};
$scope.getBrands();
}]);
}());
HTML:
<div class="well">
<div class="row">
<div id="sportsHeader" class="col-md-3">
<div ng-repeat="brand in brands.data">
<div class="row">
<div class="col-md-9">{{brand}}</div>
</div>
<div ng-repeat="category in categories.data" ng-init="getCategories(brand)">
<div class="row">
<div class="col-md-9">{{category}}</div>
</div>
<div ng-repeat="product in products.data" ng-init="getProducts(brand, category)">
<div class="row">
<div class="col-md-9">{{product}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
When I use the above HTML, only the brand names are displayed on the UI. The categories and their corresponding products are not displayed. I know that there is some overlapping that is happening. I am not sure if I am doing it the right way. I might be completely wrong with my approach. I am new to AngularJS. I want to know how to loop in nested ng-repeat so that each ng-repeat could call an angularjs service and also I want to display the data in the tree fashion as shown above. Can someone help me here?
I think that the ng-inits have to be placed on separate tags to the ng-repeats:
<div class="well">
<div class="row">
<div id="sportsHeader" class="col-md-3">
<div ng-repeat="brand in brands.data">
<div class="row">
<div class="col-md-9">{{brand}}</div>
</div>
<div ng-init="getCategories(brand)">
<div ng-repeat="category in categories.data">
<div class="row">
<div class="col-md-9">{{category}}</div>
</div>
<div ng-init="getProducts(brand, category)">
<div ng-repeat="product in products.data">
<div class="row">
<div class="col-md-9">{{product}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
You might have to juggle your bootstrap classes around also, moving ng-init is only to fix the angular part.
Move the ng-init directives outside of the ng-repeat to which they provide data.
<div class="well">
<div class="row">
<div id="sportsHeader" class="col-md-3">
<!-- MOVE init of categories here -->
<div ng-repeat="brand in brands.data" ng-init="getCategories(brand)">
<div class="row">
<div class="col-md-9">{{brand}}</div>
</div>
<!-- MOVE init of products here -->
<div ng-repeat="category in categories.data" ng-init="getProducts(brand, category)">
<div class="row">
<div class="col-md-9">{{category}}</div>
</div>
<div ng-repeat="product in products.data">
<div class="row">
<div class="col-md-9">{{product}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
The ng-init directive has a priority of 450; the ng-repeat, priority 1000. This means that when they are on the same element ng-init executes after the ng-repeat directive. The ng-repeat for categories.data won't execute its ng-init until it has a category. Thus its ng-init can't be used to populate the categories array.
Quick question. Is my approach correct ?
The approach works but it violates the Zen of Angular and the principles of an MV* Model View Whatever framework.
The model is the Single Source of Truth
Because the view is just a projection of the model, the controller is completely separated from the view and unaware of it. This makes testing a snap because it is easy to test your controller in isolation without the view and the related DOM/browser dependency.
--AngularJS Developer Guide -- Data-Binding
Having the ng-repeat and ng-init directives build the model creates a dependency that makes testing and debugging difficult. (As witnessed by this question.)
My advice is to learn how to build the model by chaining promises and using $q.all.
Edit: Thanks to Simon Schüpbach, I was able to resolve the issue by changing the template. See the end for the solution.
Let's preface this by saying that we are beginner to soft-intermediate in Angular.
On one of our project, we are using angularjs 1.4.x and also ng-cart (https://github.com/snapjay/ngCart). It worked great but then we were confronted with a demand from our client that created new weird issues.
We added fsCounter, as a directive, to the cart page so user can add or remove items. This all work great but the users also have the option to delete an item from the cart view. Deletion works as expected BUT it seems to affect the scope to the item that takes it place.
Let me make it clearer :
Let's say we have 2 products in our cart page, it displays something like that
Product_1 {price} {counter} {total} delete_btn
Product_2 {price} {counter} {total} delete_btn
Each fsCounter is its own scope
return {
restrict: "A",
scope: {
value: "=value",
index: "=index"
},
link: //other logic
However when we delete the first item, visually and in the directives, the data seems to shift. So our second row will now inherit the first row's counter.
Directive's data looks like this:
Product_1:
itemId:3,
quantity:2,
{other data}
Product_2:
itemId:8,
quantity:5,
{other data}
But once we delete the first directive (We get the scope, remove the DOM element, destroy the scope) the second directive will now have this data:
Product_2:
itemId:3,
quantity:2,
{other data}
Here is the template code :
<div class="unItem" ng-repeat="item in ngCart.getCart().items track by $index">
<div class="photo"><img src="{[{ item.getImage() }]}" alt=""></div>
<div class="details">
<h3>{[{ item.getName() }]} <span>{[{ item.getPrice() | currency:$}]}</span></h3>
<md-select ng-model="attributes" placeholder="Attribut" class="select-attribut" ng-show="item.hasAttributes()" ng-change="item.updateSelected(attributes)">
<md-option ng-repeat="attr in item.getAttributes()" ng-selected="attr == item.getSelected()" ng-value="attr">{[{ attr }]}</md-option>
</md-select>
</div>
<div class="quantity">
<div fs-counter-dynamic value="itemQuantity"
data-min="1"
data-max="999"
data-step="1"
data-addclass="add-quantity"
data-width="130px"
data-itemid="{[{ item.getId() }]}"
data-editable
ng-model="itemQuantity"
name="quantity" id="quantity-{[{ item.getId() }]}",
index="{[{ item.getId() }]}"
></div>
</div>
<div class="total">Total : {[{ item.getTotal() | currency }]}</div>
<div class="delete"><a ng-click="ngCart.removeItemById(item.getId());"></a></div>
</div>
Is this normal behavior? Is there any way to force the directive to keeps its own data? From what I've understood, each directive has its own scope, so what I think happens is that, when we remove the first one, it keeps the data stored in some kind of array that says "directive 1 data is : " and when we delete the first directive, the second one becomes the first.
So basically, are we doing anything wrong or is there anyway to remap the data?
Hope it was clear enough,
Thanks!
Edit: added html code
Edit2: Answer :
New FsCounter template looks like this:
<div fs-counter-dynamic value="item._quantity"
data-min="1"
data-max="999"
data-step="1"
data-addclass="add-quantity"
data-width="130px"
data-itemid="{[{ item.getId() }]}"
data-editable
ng-model="item._quantity"
name="quantity" id="quantity{[{ item.getId() }]}"
></div>
Do you know ng-repeat, then you don't have such problems
<div ng-repeat="product in products">
<fs-counter index="product.index" value="product.value"></fs-counter>
</div>
and in your controller
$scope.products = [
{index:1, value:"Cola"},
{index:2,,value:"Fanta"}
]
to remove an element you just have to do
$scope.products.splice(0,1);
Edit:
I suggest to save all necessary data inside the item you use inside ng-repeat. Your problem is, that you mix data from array with other data from your $scope. It is possible to $watch changes in your directive, but if you set them with ng-repeat everything is done automatically.
$scope.products = [
{index:1, name:"Cola", price:1.50, image:"your/path.png", attributes:{...}},
{index:2, name:"Fanta", price:1.40, image:"your/path.png"}
]
And then in your html
<div class="unItem" ng-repeat="item in ngCart.products track by $index">
<div class="photo"><img ng-src="item.image" alt=""></div>
<div class="details">
<h3>{{item.name}} <span>{{item.price | currency}}</span></h3>
</div>
<div class="quantity">
<div fs-counter-dynamic value="item.quantity"
data-min="1"
data-max="999"
data-step="1"
data-addclass="add-quantity"
data-width="130px"
data-itemid="item.index"
data-editable
ng-model="item.quantity"
name="quantity" id="{{'quantity-' + $index}}",
index="item.index"
></div>
</div>
<div class="total">Total : {{ item.price * item.quantity | currency }}</div>
<div class="delete"><a ng-click="ngCart.removeItemById(item.index);"></a></div>
</div>
I am working on an Ionic app, screen has lists with checkboxes and option to select all. I am new to AngularJS and Ionic.
"Select All" works fine when using controller in parent element where Select all and other lists reside.
I want to move the Select All to sub-header so that "Select All" will be always visible when we scroll through.
I tried to use the same controller in both places but Select All didn't work, I just read the scope gets changed and the value won't get passed.
Is there any way to pass the changes or any other way to fix this?
And data will be populated from the services.
HTML
<ion-header-bar class="bar-light bar-subheader">
<div ng-controller="MainCtrl">
<ion-checkbox ng-model="selectAll" ng-click="checkAll()" >
<p>Select All</p>
</ion-checkbox>
</div>
</ion-header-bar>
<ion-content class="has-header">
<div class="list" ng-controller="MainCtrl">
<ion-checkbox ng-repeat="item in devList" ng-model="item.checked">
<div class="row">
<div class="col">
<p>{{ item.text }} - 99</p>
<p>{{ item.text }} - 99</p>
</div>
<div class="col right">
<p>{{ item.text }} - 99</p>
<p>{{ item.text }} - 99</p>
</div>
</div>
</ion-checkbox>
</div>
</ion-content>
JS
.controller('MainCtrl', function($scope) {
$scope.devList = [
{ text: "HTML5", checked: true },
{ text: "CSS3", checked: false },
{ text: "JavaScript", checked: false }
];
$scope.checkAll = function() {
if ($scope.selectAll) {
$scope.selectAll = true;
} else {
$scope.selectAll = false;
}
angular.forEach($scope.devList, function (item) {
item.checked = $scope.selectAll;
});
};
});
CodePen link
Each controller will have it's own $scope. So, two different instances of controllers might have the same code, but they will still have different scopes.
So, you want to pass the changes from one controller to another.
In that case, there are a few solutions:
Using events:
$scope has a few methods which can help you to handle these cases.
These methods:
$on(name, listener) - Listens on events of a given type/name.
$emit(name, args) - Dispatches an event name upwards through the scope hierarchy notifying the registered $rootScope.Scope listeners.
$broadcast(name, args) - Dispatches an event name downwards to all child scopes (and their children) notifying the registered $rootScope.Scope listeners.
These methods will allow you to rise events from one controller and handle them inside the others.
Shared services
Also, you can create the service which will be injected into different controllers and, lets say, first controller will read this shared data and the second will write the data to this shared service.
Here is an article with some examples - link.
You can chose the approach that you like more, but I prefer shared services. This approach keeps my controllers more clear and I can manage cross-controllers dependencies by injection this shared services.
Hope it will help you.
You cannot use 2 'ng-controllers' with same controllers because the scopes created from those 2 controllers will be different, as controller scopes are created using the constructor pattern.
Ideally you should use $stateProvider to define your routes and their corresonding template and controller like below:
But for simplicity sake I have forked your codepen and used a single controller at the parent level of the view and it is working fine: http://codepen.io/anon/pen/vGQJNj
<body ng-controller="MainCtrl">
<ion-header-bar class="bar-positive">
<h1 class="title">Checkboxes</h1>
</ion-header-bar>
<ion-header-bar class="bar-light bar-subheader">
<div>
<ion-checkbox ng-model="selectAll" ng-click="checkAll()" >
<p>Select All</p>
</ion-checkbox>
</div>
</ion-header-bar>
<ion-content class="has-header">
<div class="list">
<ion-checkbox ng-repeat="item in devList"
ng-model="item.checked"
ng-checked="item.checked">
<div class="row">
<div class="col">
<p>{{ item.text }} - 99</p>
<p>{{ item.text }} - 99</p>
</div>
<div class="col right">
<p>{{ item.text }} - 99</p>
<p>{{ item.text }} - 99</p>
</div>
</div>
</ion-checkbox>
<div class="item">
<pre ng-bind="devList | json"></pre>
</div>
<div class="item item-divider">
Notifications
</div>
<ion-checkbox ng-model="pushNotification.checked"
ng-change="pushNotificationChange()">
Push Notifications
</ion-checkbox>
<div class="item">
<pre ng-bind="pushNotification | json"></pre>
</div>
<ion-checkbox ng-model="emailNotification"
ng-true-value="'Subscribed'"
ng-false-value="'Unubscribed'">
Newsletter
</ion-checkbox>
<div class="item">
<pre ng-bind="emailNotification | json"></pre>
</div>
</div>
</ion-content>
</body>
I would like a directive that dynamically knows if I'm following the user in my App.
I have a resource to get the currentUser
this.following = function () {
var defer = $q.defer();
var user = $cookies.getObject('currentUser');
UserResource.get({id: user.id}).$promise.then(
function (data) {
defer.resolve(data.following);
});
return defer.promise;
};
This is in one of my services. It returns all users that I'm following.
When instantiating my controller I fetch the users I follow within my app:
UserService.following().then(
function (data) {
$scope.following = data;
});
I would like to move that into a directive so that I can easily reuse it somewhere else in my app.
This is the HTML I am using right now (and it's not really beautiful) :
<div class="item" ng-repeat="user in users">
<div class="right floated content">
<div ng-show="isFollowing(user)" class="ui animated flip button" tabindex="0"
ng-click='unFollow(user)'>
<div class='visible content'>
Following
</div>
<div class="hidden content">
Unfollow
</div>
</div>
<div ng-hide="isFollowing(user)" ng-click="follow(user)" class="ui button">Follow</div>
</div>
</div>
But instead something like :
<div class="item" ng-repeat="user in users">
<um-follow-button um-user="user"></um-follow-button>
</div>
then depending if I'm following the user or not then render one of the two options.
I don't know if I will have to use a controller in my directive.
I have looked at : https://gist.github.com/jhdavids8/6265398
But it looks like a mess.
There are quite a few questions on how to implement item removal inside ngRepeat directive, and as I figured out, it comes down to using ngClick and triggering some remove function passing it item's $index.
However, I couldn't find anywhere an example where I have multiple ngRepeats:
<div ng-controller="MyController">
<div ng-repeat="email in user.emails">
{{ email }} <a href>Remove</a>
</div>
<div ng-repeat="phone in user.phones">
{{ phone }} <a href>Remove</a>
</div>
</div>
For this, I would need to create $scope.removePhone and $scope.removeEmail which would be called using ngClick on Remove anchor. But I'm looking for a more generic solution. Especially since I have many pages with many ngRepeats .
I was thinking about writing a directive which would be placed on Remove anchor and would do something like this:
Find ngRepeat among parent elements.
Read what it's iterating over ('user.emails' in first case, 'user.phones' in second)
Remove $index element from THAT model.
So the markup would look something like this:
<div ng-controller="MyController">
<div ng-repeat="email in user.emails">
{{ email }} <a href remove-directive="$index">Remove</a>
</div>
<div ng-repeat="phone in user.phones">
{{ phone }} <a href remove-directive="$index">Remove</a>
</div>
</div>
Is what I'm looking for possible to achieve and what would be the preferred way to do this?
Current hacky solution
Here is how I do it currently. It's hacky and ugly but gets the job done until I figure out a prettier way.
myAppModule.controller('MyController', function ($scope, $parse, $routeParams, User) {
$scope.user = User.get({id: $routeParams.id});
$scope.remove = function ($index, $event) {
// TODO: Find a way to make a directive that does this. This is ugly. And probably very wrong.
var repeatExpr = $($event.currentTarget).closest('[ng-repeat]').attr('ng-repeat');
var modelPath = $parse(repeatExpr.split('in')[1].replace(/^\s+|\s+$/g, ''));
$scope.$eval(modelPath).splice($index, 1);
};
});
And in DOM:
<div ng-repeat="email in user.email" class="control-group">
<label class="control-label">
{{ "Email Address"|_trans }}
</label>
<div class="controls">
<input type="text" ng-model="email.address">
<span class="help-inline"><a href ng-click="remove($index, $event)">{{ "Delete"|_trans }}</a></span>
</div>
</div>
You could create a generic remove method that would take in the array and the item to remove.
<div ng-app="" ng-controller="MyController">
<div ng-repeat="email in emails">{{ email }} <a ng-click="remove(emails, $index)">Remove</a>
</div>
<div ng-repeat="phone in phones">{{ phone }} <a ng-click="remove(phones, $index)">Remove</a>
</div>
</div>
$scope.remove = function(array, index){
array.splice(index, 1);
}
No JS
<div ng-repeat="option in options" ng-init=options=[1,2,3,4,5]>
<button ng-click="options.splice($index,1)">Remove me</button>
</div>
<div ng-app="" ng-controller="MyController">
<div ng-repeat="email in emails as datasource">{{ email }}
<a ng-click="datasource.splice($index,1)">Remove</a>
</div>
<div ng-repeat="phone in phones as datasource">{{ phone }}
<a ng-click="datasource.splice($index,1)">Remove</a>
</div>
</div>
A very simple and convenient way that works cross-browser is to use the 'remove' utility method from the library lodash.
<div ng-repeat="phone in phones">{{ phone }}
<a ng-click="removeItem(phones, phone)">Remove</a>
</div>
In your controller you declare then
//inject lodash dependency
//declare method in scope
$scope.removeItem = function(list, item){
lodash.remove(list,function(someItem) { return item === someItem});
}
You may of course use indexes if you like. See https://lodash.com/docs#remove
If you have used ng-repeat on an object instead of an array, do the following.
<div ng-app="" ng-controller="MyController">
<div ng-repeat="email in emails">{{ email }}
<a ng-click="remove(emails, email)">Remove</a>
</div>
<div ng-repeat="phone in phones">{{ phone }}
<a ng-click="remove(phones, phone)">Remove</a>
</div>
</div>
$scope.remove = function(objects, o){
delete object[o.id];
}
or the more terse
<div ng-app="" ng-controller="MyController">
<div ng-repeat="email in emails">{{ email }}
<a ng-click="delete emails[email.id]">Remove</a>
</div>
<div ng-repeat="phone in phones">{{ phone }}
<a ng-click="delete phones[phone.id]">Remove</a>
</div>
</div>
presumes that the objects look like this
var emails = { '123' : { id : '123', .... } };
var phones = { '123' : { id : '123', .... } };