AngularJS Filter Best Practice - javascript

I have a filter I need to use two times (two pages/controllers), filtering on an object category. I can get this to work using this in each controller:
$scope.$on('category', function (event, arg) {
$scope.catSort = arg;
});
$scope.filterCat = function (item) {
return !$scope.catSort ?
item : (item.category == $scope.catSort);
};
The html i am filtering on:
<li dnd-draggable="item" ng-repeat="item in items| filter: filterCat">
</li>
I set the $scope.catSort which is what to filter on in a list of buttons the user can click:
<button class="btn btn-default" ng-click="$emit('category', 'all')">Show All</button>
<button class="btn btn-default" ng-click="$emit('category', 'Common')">Common</button>
The problem is, i have another html page with a different subset of categories that I need to filter on. Instead of just copying and pasting the $scope.filterCat function to filter again, I want to create a separate filter to inject into each controller (Thats best practice right?).
So what i have started is this for a separate filter:
angular.module("category-filter", [])
.filter('filterCat', function () {
return function (category, item) {
return !category ?
product : (item.category == $scope.catSort);
}
});
You can see i'm trying to get that category option as well. How do i reach into the scope and get the category? Should i use a service and on one of the button clicks set the category to filter on?
I'm still learning filters and what want to make sure its reusable and following best practices

Pass an object to the filter expression: filter:{category: '...', compareTo: '...'} and check it inside the filter function.

Related

AngularJS - Get printed value from scope inside an attribute?

I'm currently working on an AngularJS project and I got stuck in this specific requirement.
We have a service that has all the data, DataFactoryService. Then, I have a controller called DataFactoryController that is making the magic and then plot it in the view.
<div ng-repeat = "list in collection">
{{list.name}}
...
</div>
Now, we have a requirement that pass multiple data into one element. I thought an "ng-repeat" would do, but we need to have it inside an element attribute.
The scenarios are:
At one of the pages, we have multiple lists with multiple data.
Each data has a unique code or ID that should be passed when we do an execution or button click.
There are instances that we're passing multiple data.
Something like this (if we have 3 items in a list or lists, so we're passing the 3 item codes of the list):
<a href = "#" class = "btn btn-primary" data-factory = "code1;code2;code3;">
Submit
</a>
<a href = "#" class = "btn btn-default" data-factory = "code1;code2;code3;">
Cancel
</a>
In the example above, code1,code2,code3 came from the list data. I tried several approach like "ng-repeat", "angular.each", array, "ng-model" but I got no success.
From all I've tried, I knew that "ng-model" is the most possible way to resolve my problem but I didn't know where to start. the code below didn't work though.
<span ng-model = "dataFactorySet.code">{{list.code}}</span>
{{dataFactorySet.code}}
The data is coming from the service, then being called in the controller, and being plot on the HTML page.
// Controller
$scope.list = dataFactoryService.getAllServices();
The data on the list are being loaded upon initialization and hoping to have the data tags initialized as well together with the list data.
The unique code(s) is/are part of the $scope.list.
// Sample JSON structure
[
{ // list level
name: 'My Docs',
debug: false,
contents: [ // list contents level
{
code: 'AHDV3128',
text: 'Directory of documents',
...
},
{
code: 'AHDV3155',
text: 'Directory of pictures',
...
},
],
....
},
{ // list level
name: 'My Features',
debug: false,
contents: [ // list contents level
{
code: 'AHGE5161',
text: 'Directory of documents',
...
},
{
code: 'AHGE1727',
text: 'Directory of pictures',
...
},
],
....
}
]
How can I do this?
PLUNKER -> http://plnkr.co/edit/Hb6bNi7hHbcFa9RtoaMU?p=preview
The solution for this particular problem could be writing 2 functions which will return the baseId and code with respect to the list in loop.
I would suggest to do it like below
Submit
Cancel
//inside your controller write the methods -
$scope.getDataFactory = function(list){
var factory = list.map( (a) => a.code );
factory = factory.join(";");
return factory;
}
$scope.getDataBase= function(list){
var base= list.map( (a) => a.baseId);
base= base.join(";");
return base;
}
Let me know if you see any issue in doing this. This will definitely solve your problem.
You don't really have to pass multiple data from UI if you are using Angular.
Two-way data binding is like blessing which is provided by Angular.
check your updated plunker here [http://plnkr.co/edit/mTzAIiMmiVzQfSkHGgoU?p=preview]1
What I have done here :
I assumed that there must be some unique id (I added Id in the list) in the list.
Pass that Id on click (ng-click) of Submit button.
You already have list in your controller and got the Id which item has been clicked, so you can easily fetch all the data of that Id from the list.
Hope this will help you... cheers.
So basing from Ashvin777's post. I came up with this solution in the Controller.
$scope.getFactoryData = function(list) {
var listData = list.contents;
listData = listData.map(function(i,j) {
return i.code;
});
return listData.join(';');
}

How to maintain a constant scope in angular?

I have an angular service that fetch contacts data from the database, I then use these data so that the user can add one contact or more to a deal, so there is a many to many relationship between the deal and contact, the selected data are displayed in a grid (syncfusion grid).
I need to have a constant list of the data retrieved from database, and a varible containing the selected contacts that I pass to the syncfusion grid, when the user add from the dropdown I add this contact to the grid and remove it from the dropdown list, and if I deleted it from the grid I return it back to the drop down list, so here's my code:
this service get the contact list:
var contactsListDB = [];
contactService.getAll().then(function (contacts) {
contactsListDB = contacts.data;
$scope.contactsList = contactsListDB; // the scope used in the select input in the HTML
});
the method that add contact:
$scope.addContact = function (contact) {
var contactJson = JSON.parse(contact);
$scope.dealContacts.push(contactJson);
var index = $scope.contactsList.findIndex(x => x.id == contactJson.id);
SyncFusionDealContact($scope.dealContacts);
$scope.contactsList.splice(index, 1);
}
this function is invoked in HTML:
<ng-form name="ContactForm">
<md-input-container>
<label>{{"Contact" | translate}}</label>
<md-select ng-model="contact">
<md-option value="{{null}}">-- {{"selectContact" | translate}} --</md-option>
<md-option ng-repeat="contact in contactsList" value="{{contact}}">{{contact.name}}</md-option>
</md-select>
</md-input-container>
<md-input-container>
<div>
<button class="btn btn-primary" type="submit" ng-disabled="!ContactForm.$valid" ng-click="addContact(contact)" aria-label="submit">{{'add' | translate}}</button>
</div>
</md-input-container>
<div id="GridDealContacts">
<script id="columnTemplateContacts" type="text/x-jsrender">
<a class="btn btn-danger btn-xs contactDelete" data-id="{{:id}}" ng-click="DeleteContact{{:id}}"><i class="fa fa-trash-o "></i></a>
<!--add delete button-->
</script>
</div>
</ng-form>
When the page is loaded I check if the object is being edited and then I exclude the existing contacts from the list comming from contacts table:
$scope.dealContacts = deal.contacts;
SyncFusionDealContact($scope.dealContacts);
execludeContacts()
execludeContacts function:
function execludeContacts() {
var exIds = [];
if ($scope.dealContacts.length > 0) {
exIds = $scope.dealContacts.map(x=> x.id)
}
var conts = contactsListDB;
conts.forEach(function (item, index) {
if (exIds.includes(item.id)) {
conts.splice(index, 1);
}
})
$scope.contactsList = conts;
}
this function handles delete action:
$scope.DeleteContact = function (id, index) {
if (id <= 0) {
$scope.dealContacts.splice(index, 1)
SyncFusionDealContact($scope.dealContacts);
}
else {
if (confirm("Are You Sure?")) {
dealService.deleteContact(id, $routeParams.id).then(function (success) {
if (success.data.isSuccess) {
SyncFusionDealContact($scope.dealContacts);
var one = contactsListDB.filter(x => x.id == id)[0];
$scope.contactsList.push(one);
$scope.dealContacts.splice(index, 1);
}
else {
alert('Cannot delete');
}
SyncFusionDealContact($scope.dealContacts);
});
}
}
}
In the code above I tried to save a copy of the contacts list in a variable that can't be changed contactsListDB, so that when a record is deleted from the grid I can get it back from this array to add it in the drop-down list again, but what happens is that the array is changed:
Screenshot:
I'm not sure of what you're asking, but I think you want to 'save' the data somewhere? If that's the case, take a look at using a factory or service. I prefer using a factory because of its simplicity.
From the short period I've worked with Angular, I know that a factory will always stay alive (even while switching views in SPA) until you refresh the page.
In a simple example, the factory will hold a list of the contacts, and have several functions like your addContact and deleteContact. This makes your code less cluttered and you just have to call the factory and its functions in your code.
I currently can't give a specific example (as I prefer using my own factories and its usage), but here you can find a comparison between factory and service and how to use them.
If I misunderstood your question, please do tell. :D

Pass Angular typeahead object result to another function in controller

In this scenario I'm using the ui-bootstrap typeahead to capture an object from an external api. Using the select callback I'm getting that object and have the results set in a separate function within my controller.
The issue is that I want to take those results and send them off to a separate api with a click function I already have set up. My question is how do i get the results of the type-ahead into the click function to post? The user flow is as follows.
<div>
<input type="text" placeholder="Find A Game"
typeahead-on-select="setGames($item)"
ng-model="asyncSelected"
typeahead="test.name for test in getGames($viewValue)"
typeahead-loading="loadingLocations" typeahead-min-length="3"
typeahead-wait-ms="500" typeahead-select-on-blur="true"
typeahead-no-results="noResults">
</div>
<div ng-show="noResults">
No Results Found
</div>
<button ng-disabled="!asyncSelected.length"
ng-click="addtodb(asyncSelected)">Add To Database</button>
As you can see the label is set to the items name and this works fine. When the user selects the name I then use typeahead-on-select="setGames($item)" to send off the entire object to its own funtion. From there I want to take the object and pass it to another function that you can see within the button tags ng-click. I currently have it passing the model, but what I really want is to pass the entire object within $item from the select event. So far my controller looks like this:
angular.module('2o2pNgApp')
.controller('GiantCtrl', function ($scope, $http, TermFactory, $window, SaveFactory) {
$scope.getGames = function(val) {
return $http.jsonp('http://www.example.com/api/search/?resources=game&api_key=s&format=jsonp&limit=5&json_callback=JSON_CALLBACK', {
params: {
query: val
}
}).then(function(response){
return response.data.results.map(function(item){
return item;
});
});
};
$scope.setGames = function (site) {
var newsite = site;
};
$scope.addtodb = function (asyncSelected, newsite) {
TermFactory.get({name: asyncSelected}, function(data){
var results = data.list;
if (results === undefined || results.length === 0) {
SaveFactory.save({vocabulary:'5', name:newsite.name, field_game_id:newsite.id}, function(data) {
$window.alert('All Set, we saved '+asyncSelected+' into our database for you!')
});
} else {
// do stuff
});
}
});
No matter what I do I cant seem to pass the entire $item object into this click function to post all the info i need.
Via New Dev in Comments:
$item is only available locally for typeahead-on-select... you can
either assign it to some model within your controller, or, in fact,
make the model of typeahead to be the item: typeahead="test as
test.name for test in getGames($viewValue)" – New Dev

How to use ngRepeat on element returned from a function (a filter, actually) - AngularJS?

I need to use something like the following:
<ul>
<li ng-repeat='item in getShopItems(shop.id)'>{{item.name}}</li>
</ul>
I have to use a function since I need the current element of the ngRepeat to apply the filter.
The getShopItems(id) function is as follows:
$scope.getShopItems = function(id){
filteredItems = $filter('filter')($scope.items, {shop_id: shop_id});
return filteredItems;
}
Using ngInit can help but the official documentation discourages it.
If we cannot use functions for ngRepeat, how else should I apply the filter. Thanks in advance.
And here's a plunker: http://plnkr.co/ugOZ7QgQ3EHVQUGWTCII [not working]
EDIT: provider and shop are the same
EDIT: I don't want to filter in view like item in items | filter:{}
You could use filter as custom filter:
<ul>
<li ng-repeat='item in items | filter:getShopItems'>{{item.name}}</li>
</ul>
and modify filter function a little:
$scope.getShopItems = function(item) {
return item.provider_id == $scope.shop.id;
}
I think, you have two solution, either you init your array in your controller :
function getShopItems(id){
filteredItems = $filter('filter')($scope.items, {provider_id: provider_id});
return filteredItems;
}
$scope.filterItems = getShopItems($scope.shop.id)
And for the views :
{{item.name}}
But it is possible that this solution does not work in your case, for example if your id is suseptible to change.
A other way would be directly use your filter in the views :
<ul>
<li ng-repeat='item in items|filter:{"id":shop.id}'>{{item.name}}</li>
</ul>
I would also recommand you to read this thread: How to Loop through items returned by a function with ng-repeat?

Angular factory filter - Can't get data passed to the filter

I'm looking through the Angular docs and I can't better documented stuff.
My problem is as follows:
I have a filter:
.filter('filteringService', function () {
return function (photos, categsList) {
if (photos !== undefined) {
var filteredPosts = [];
console.log('Categories or Selected Categories:');
console.log(categsList);
//DEVEL Only, assign a SEARCH value, can't pass the data from the list now.
categsList = 'people';
if (categsList === undefined) {
for(i=0; i < photos.length; i++) {
filteredPosts.push(photos[i]);
}
} else {
console.log('Trying to push a category slug to search for.');
//TASK: Convert in multiple possible selections.
filteredPosts = [];
}
console.log('Filter returns ' + filteredPosts.length + ' posts');
return filteredPosts;
}
};
});
And I have the template
<div class="photos">
<div class="filters">
<ul>
<li><a>ALL</a></li>
<li ng-repeat="category in categsList">
<a ng-checked="category[0]" ng-model="category[0]">{{ category[1] }}</a>
</li>
</ul>
</div>
<ul class="photos-list">
<li ng-repeat="photo in photos|filteringService:category">
<h1>{{ photo.title }} click LINK</h1>
<ul class="categories">
<li ng-repeat="category in photo.categories">
{{ category.title }}
</li>
</ul>
</li>
</ul>
</div>
There's a huge object with posts called photos and then there's a category list called categsList.
The photos object has the items from the categs list in it. I WANT to be able to filter with the CLICKED element through that list, and maybe multiple filter, but first to pass on the actual filter value to the filter service, I can't seem to do that.
How should I do that?
Apparently I managed to pass the filter value, in a dirty way (I GUESS), like this
<a ng-bind="category.slug" ng-click="returnFilter(category.slug);" ng-model="category.slug">{{ category.title }}</a>
it goes here
$scope.returnFilter = function(theSlug) {
$scope.filterBy = theSlug;
};
and it comes out here
<li ng-repeat="photo in photos|filteringService:filterBy">
It's working, but Is this correct?
EDIT: Also with this way in mind, I could pass an array as theSlug so I can do multiple filtering, and when clicking two times on the same item it would get it out of the array. hmmm
EDIT 2:
Let's say the resulting array is under 15 items, could do I run some action in the controller knowing this?
Actually the other way around, could I query from the controller the resulting array that the filter outputs?
I can't $watch the first array, I guess the filter creates a new array and puts those results in page. How could I watch the resulting array for changes and do stuff in the controller?

Categories