Filtering the data in angular 6 - javascript

I am in new in angular 6. I am currently working on angular 6 project. I am coming into a situation where i need tho filer the data, but i have problem to keep the original object. Here is code:
this.loginService.categoryType$.subscribe((data) => {
for(var i=0; i<this.listPosts.length; i++){
if(this.listPosts[i]['categoryName'] == data){
}else{
this.listPosts.splice(i,1);
}
}
});
Html:
<li class="nav-item" (click) = "selectedCategory('buy')" style="cursor: pointer;">
<a class="nav-link" [ngClass]="{'active':selectedCat == 'buy'}">
<i class="material-icons left-nav">
shopping_cart
</i>
Buy
</a>
</li>
<li class="nav-item" (click) = "selectedCategory('sell')"style="cursor: pointer;">
<a class="nav-link" [ngClass]="{'active':selectedCat == 'sell'}">
<i class="material-icons left-nav">
shopping_basket
</i>
Sell
</a>
</li>
The problem is how to keep the original object value, because data is already spliced .

If you need to keep the original list as well as a filtered one, I suggest you to save the original list in a variable, and then use rxjs methods to display the filtered
originalList$: BehaviorSubject<any> = new BehaviorSubject<any>([]);
filteredList = [];
this.loginService.categoryType$.subscribe((data) => {
this.originalList$.next(data);
});
this.originalList$.pipe(
map(data => yourLogicToFilter(data))
).subscribe(list => filteredList = list);
filteredList can be an Observable so you can use an async pipe in your code as well.
Probably there are even cleaner solutions embracing rxjs, but at least this should get you going on the logic

From my understanding, you want to get the data and you do not want to mutate the original object stored in your component as a property. In which case, using splice defeats the purpose of what you are trying to do since it mutates the object.
There are a number of ways you could do this but I'll try to keep my answer as close to your code as possible. Correct me if I'm wrong but in my understanding, this.loginService.categoryType$ is an observable or a subject for determining the category type. Since you already have the category type as an observable, you could use map on it and filter this.listPosts based on the category name.
const listPosts$ = this.loginService.categoryType$.pipe(
map(categoryType => this.listPosts.filter(
listPost => listPost.categoryName === categoryType
)),
);
You could use listPosts$ on your template and pipe async on it.
listPosts$ | async
Or you could tap or subscribe against it like what you did to get the value and assign it to your component property or variable. I personally don't like this approach since you're storing the values from your stream in a variable.
const listPosts$ = this.loginService.categoryType$.pipe(
map(categoryType => this.listPosts.filter(
listPost => listPost.categoryName === categoryType
)),
);
listPosts$.subscribe(currentList => this.currentListPosts = currentList);
Hope this helps!

Related

How can I achieve the same outcome without using map() array method? What exactly is the significance of using map() array method here?

Source content can be found here: https://github.com/LinkedInLearning/javascript-essential-training-2832077/tree/main/08_17. Code in question is this block right here:
import backpackObjectArray from "./components/data.js";
const content = backpackObjectArray.map((backpack)=>{
let backpackArticle = document.createElement("article");
backpackArticle.classList.add("backpack");
// Set article ID to the backpack.id property
backpackArticle.setAttribute("id", backpack.id);
backpackArticle.innerHTML=`
<figure class="backpack__image">
<img src=${backpack.image} alt="" />
</figure>
<h1 class="backpack__name">${backpack.name}</h1>
<ul class="backpack__features">
<li class="packprop backpack__volume">Volume:<span> ${
backpack.volume
}l</span></li>
<li class="packprop backpack__color">Color:<span> ${
backpack.color
}</span></li>
<li class="backpack__age">Age:<span> ${backpack.backpackAge()} days old</span></li>
<li class="packprop backpack__pockets">Number of pockets:<span> ${
backpack.pocketNum
}</span></li>
<li class="packprop backpack__strap">Left strap length:<span> ${
backpack.strapLength.left
} inches</span></li>
<li class="packprop backpack__strap">Right strap length:<span> ${
backpack.strapLength.right
} inches</span></li>
<li class="feature backpack__lid">Lid status:<span> ${
backpack.lidOpen ? "open" : "closed"
}</span></li>
</ul>
`;
return backpackArticle;
})
const main = document.querySelector(".maincontent");
content.forEach((backpack)=>{
main.append(backpack);
}
)
In essence, is it possible to just use forEach loop to output the same result as when we use map() array method, which is to output a HTML article for each object?
You can definitely use a forEach loop if you wanted to, the code will just be a little different. Using map is not necessary if you like how the new code looks.
import backpackObjectArray from "./components/data.js";
const main = document.querySelector(".maincontent");
backpackObjectArray.forEach((backpack) => {
let backpackArticle = document.createElement("article");
backpackArticle.classList.add("backpack");
// Set article ID to the backpack.id property
backpackArticle.setAttribute("id", backpack.id);
backpackArticle.innerHTML = `
<figure class="backpack__image">
<img src=${backpack.image} alt="" />
</figure>
<h1 class="backpack__name">${backpack.name}</h1>
<ul class="backpack__features">
<li class="packprop backpack__volume">Volume:<span> ${
backpack.volume
}l</span></li>
<li class="packprop backpack__color">Color:<span> ${
backpack.color
}</span></li>
<li class="backpack__age">Age:<span> ${backpack.backpackAge()} days old</span></li>
<li class="packprop backpack__pockets">Number of pockets:<span> ${
backpack.pocketNum
}</span></li>
<li class="packprop backpack__strap">Left strap length:<span> ${
backpack.strapLength.left
} inches</span></li>
<li class="packprop backpack__strap">Right strap length:<span> ${
backpack.strapLength.right
} inches</span></li>
<li class="feature backpack__lid">Lid status:<span> ${
backpack.lidOpen ? "open" : "closed"
}</span></li>
</ul>
`;
main.append(backpackArticle);
});
*Pseudo-code* Array.prototype.map(function(ea){return backpackArticle}) will simply create a new array and include whatever you return in each iteration.
You can achieve the same thing in other ways, but, as was already said, with more code. Really, the 'significance' in this case is simply achieving the intended result with less code.
I think in your example, it's to explicitly create a new array and then explicitly append each backpackArticle to the <main> using a forEach. You can skip the creating of the new array with map.
EDIT
Sorry, I havn't read your question carefully enough it seems. In your case, yes, you can interchange .map with .forEach and append the created elements directly without storing them in an intermediate array.
However, I'll leave the rest as it is, maybe someone finds it useful.
As a somewhat general answer, "yes, you could use .forEach and get the same results". And you can also do it with .reduce. However, if you want to have a list of results from a list of sources (as in your example), .map is the way to go.
I am going to answer your question in a more general way, because it
appears to me you are asking about the difference between .forEach
and .map in general.
Every method on Array.prototype is there for a purpose. The purpose of .map is to project (or "map", hence the name) a function over all items of a list. So if you want to go from a list of values to a list of other values, you can use .map.
const sources = [1, 2, 3, 4, 5];
const results = sources.map(n => n + 1);
console.log(results); // logs [2, 3, 4, 5, 6]
To get the same with .forEach, you'd have the same number of variables but two steps more you have to program: One for creating the results array, one for adding the items manually to it.
const sources = [1, 2, 3, 4, 5];
const results = [];
sources.forEach(n => {
results.push(n);
});
console.log(results);
By direct comparison you can easily see that using .forEach results slightly more code which is less declarative and more imperative in style.
As mentioned earlier, you could as well use .reduce to map a function over a value and accumulate that into a resulting list. In fact, a vast amount of operations can be written by utilizing .reduce. If you're curious, search for transducers, there are several libraries in various languages available.
But back to the example using reduce:
const sources = [1, 2, 3, 4, 5];
const results = sources.reduce((acc, n) => acc.concat(n + 1), []);
console.log(results); // logs [2, 3, 4, 5, 6]

removing value from array not working NEXT.JS

I have a filter options, which shows checkbox. So when click on each checkbox the value should be added to array if not exists and remove the value from array if already exists and the state should be updated. I have tried using below code and it is not working.
const [showFilter, setFilter] = useState([]);
useEffect(() => {
dispatch(fetchproducts(slug, sort, pageInitial+1, showFilter));
console.log(showFilter);
}, [showFilter]);
function filterClick (id, title) {
const index = showFilter.indexOf(id);
if (index > -1)
setFilter(showFilter.splice(index, 1));
else
setFilter(showFilter.concat(id));
}
return (
<ul style={{display: showMe.includes(index) ? "block" : "none"}}>
{item.items.map((single, index1) => (
<li key={index1}>
<label><input type="checkbox" name="checkbox" onClick={(e) => filterClick(e.target.value, item.title)} value={single.items_id}/> {single.items_value}</label>
</li>
))}
</ul>
)
In the above code, array insertion is working, but the splice is not working and the state is not updating.
How to alter my code to get the expected result.
You use useEffect. The useEffect's callback will be triggered when one of dependency is changed.
splice function changes array in place (ie mutates the array). In this case your array variable (showFilter) is not changed, therefore useEffect's callback will not be triggered.
Try using filter function instead:
setFilter(showFilter.filter(el=> el !== id));
Splice modifies the original array which is not considered a good practice in React. Please use slice or`filter.
Using slice your code would look like:
setFilter(showFilter.slice(index, index + 1))

Vue filter function to return always 5 more elements

actually no biggie but how would a computed property filter function look like that always returns the current array + 5 more elements?
more in detail:
Template:
<span class="box-content" v-for="item in activeItems" :key="item.id">
<img class="item" :src="item.filename" />
</span>
Script
data: function() {
return {
items: [],
limit: 1,
};
},
computed: {
activeItems: function() {
return this.items.filter( function(s) {
if(s.length > this.limit) {
return s;
}
});
// return this.limit ? this.items : this.items;
}
},
on page load , an axios post request gets an object of items, whose response is pushed into the items array which is empty upon component declaration.
so axios -> get object with items -> push into empty array.
now i want to display ,like, 5 items and make a show more button.
The problem now is, my activeItems function is invalid, it does not know "this.limit" and i doubt anyway that it returns the correct result as i just made it return itself and not a set of objects / arrays.
What I would do next is trying around with splice and slice, array copies and pushing elements into it until a certain condition is met but.. is there a better way ?
Thanks in advance
The filter function should be used to filter based on the internal values of an array. Say you have an array of objects with persons, and each Person as an age, then you could use the Array.prototype.filter function to filter based on that age of each entry.
The filter function therefore goes through every entry in your array and determines whether an item should be included or excluded.
If you, on the other hand, want to limit the amount of entries based on a maximum number of entries, I would suggest you use Array.prototype.slice, as you mentioned already.
Your computed function could be rewritten to:
activeItems: function() {
return this.items.slice(0, this.limit)
}
First, in your code, this.limit is undefined because this is referencing the anonymous function. If you want to access the component, you will better use arrow functions syntax.
Also, s references an element of your array, so s.length will be undefined too I guess...
Now, filter does not seem to be the best choice for your need. I'll go with slice instead. Somthing like:
computed: {
activeItems() {
return this.items.splice(0, this.limit)
}
}
Where limit is increased by 5 when you click the show more button.
Of course you could do it. You just missed some code on it. Here how you fix it
activeItems: function() {
let limit = this.limit
return this.items.filter( function(item, s) {
return s <= limit
});
}
If you don't mind using filter, here are some way to do it.
First : put condition in your for loop, this one
<span class="box-content" v-for="(item, index) in items" :key="item.id" v-if="index <= limit">
<img class="item" :src="item.filename" />
</span>
Second is to slice your array on you desired length, this one
<span class="box-content" v-for="(item, index) in items.slice(0, limit)" :key="item.id">
<img class="item" :src="item.filename" />
</span>

Joining 2 nodes with angularfire2

Image of my firebase
Im trying to map the categories that the user have favourited to the category node to retrieve its details.
Code for my typescript :
ionViewDidLoad(categorykey:string){
console.log('Hello Manage Favourite Page');
let userid = this.authData.getID();
this.favouritecategories = this.af.database.list(`/users/${userid}/favourites`)
.map(favourites => {
favourites.map(category => {
let categorykey = category.$key
console.log(categorykey);
category.favname = this.af.database.object(`/test/${categorykey}`)
});
return favourites;
});
Code for my Html:
<div *ngFor="let region of favouritecategories | async; let i = index;">
<ion-item (click)=" category.hidden = !category.hidden; category.visible = !category.visible" detail-none><b>{{i+1}}.{{ (category.favname| async)?.name }}</b>
<ion-icon name="arrow-down" [hidden]="category.visible" item-right></ion-icon>
<ion-icon name="arrow-up" [hidden]="!category.visible" item-right></ion-icon></ion-item>
</div>
With the above code , i only managed to retrieve the category's name but not the regions name. Anyone have any idea on how to do?
You haven't actually returned the modified map. You want to do something like the following (modified for latest AngularFire versions):
let listObservable = this.af.database.list(`/users/${userid}/favourites`);
// use snapshotChanges() as this contains the keys
this.favouritecategories = listObservable.snapshotChanges()
.map(snapshots => {
// note that we return the inner .map()!
return snapshots.map(snap => {
let data = snap.val();
data.favname = this.af.database.object(`/test/${snap.key}`).valueChanges();
// note also that we return the transformed data
return data;
});
});
Differences from the original poster's question:
Should use .valueChanges() to get a useful observable for ngFor ops
$key is no longer included in data by default
But can be obtained using .snapshotChanges()
Some other useful tips:
Avoid sequential numeric ids (i.e. arrays) in distributed data
Write the data how you'll read it later
Avoid trying to make NoSQL look like SQL with lots of fkey references and artificial normalizing. Just store the names where you'll read them as a general rule.
It's okay to denormalize data.

Use of "for...of" in ng-repeat

Looking through ng-repeats source, it doesn't look like theres any instance of it using for-of. Is there any custom directive that does this or some other way of achieving this loop in templates to make use of iterator functions?
Class with iterator
class Cache{
constructor(items){
this.cache = {
"one" : 1,
"two" : 2
};
};
// custom iterator that turns our cache into an array
// for use in "for...of" loops
[Symbol.iterator](){
var index = 0;
// turn cache object into array of its values (underscore method)
var data = _.values(this.cache);
return {
next: function(){
if(index < data.length){
return {
value: data[index++],
done: false
};
}else{
return { done:true };
}
}
};
};
};
var myCache = new Cache();
// looping my cache in simple js would look like
for(let val of myCache){
console.log(val);
}
// 1, 2
proposed angularjs ng-repeat directive
<ul>
<li ng-repeat="item in myCache track by $index"></li>
</ul>
However that does not work as ng-repeat does not implement for...of. My question is: is there a way to get the ng-repeat directive to work nicely with iterators with minimal interface changes, or better yet, a custom directive identical to ng-repeat that is made for for...of loops?
You could just use Array.from to convert your iterable source to an array, which ngRepeat will be able to iterate:
<ul>
<li ng-repeat="item in Array.from(myCache) track by $index"></li>
</ul>
Ideally this would happen in your javascript directive/controller:
scope.myCache = Array.from(new Cache());
View:
<ul>
<li ng-repeat="item in myCache track by $index"></li>
</ul>

Categories