Filtering an array of JSON - angularjs

Hey I am following another guide and really struggling to get it working for me. Somewhat new to Angular so I am sure this is a simple issue. Can anyone help me?
The front end shows all the JSON objects at the page load but when I type anything they all disappear.
_ninjaFilter:string
get ninjaFilter():string{
return this._ninjaFilter;
}
set ninjaFilter(value:string){
this._ninjaFilter = value
console.log(this.filteredNinjas, this.ninjaFilter)
this.filteredNinjas = this.ninjaFilter ? this.performFilter(this.ninjaFilter) : this.ninjas
}
performFilter(filterBy: string): any{
filterBy = filterBy.toLocaleLowerCase();
console.log(filterBy)
return this.ninjas.filter(ninja=>{
ninja.name.toLocaleLowerCase().includes(filterBy)
//tried a if statement here to console log match and it does log out match
//also have tried .indexOf(filterby) !== -1
})
}
filteredNinjas: any
ninjas=[{
'name':'yoshi',
'belt':'red'
},
{
'name':'luigi',
'belt':'black'
},
{
'name':'Ryu',
'belt':'green'
}]
constructor(private route: ActivatedRoute) {
this.filteredNinjas = this.ninjas //create new array to filter on
this.ninjaFilter='' //set initial filter string to null
}
and the view:
<h2>Ninja Listing</h2>
<input type='text' id="filter"
[(ngModel)]='ninjaFilter' />
<ul id="ninja-listing">
<li *ngFor="let ninja of filteredNinjas">
<div class='single-ninja'>
<span [ngStyle]="{background: ninja.belt}">{{ninja.belt}} belt</span>
<h3>{{ninja.name}}</h3>
</div>
</li>
</ul>
Here is console log (first page load and then me typing)
(3) [{…}, {…}, {…}] "r"
directory.component.ts:23 r
directory.component.ts:17 [] "ry"
directory.component.ts:23 ry
directory.component.ts:17 [] "ryu"
directory.component.ts:23 ryu

You don't return anything inside your filter function. You should return a condition there:
return this.ninjas.filter(ninja => {
return ninja.name.toLocaleLowerCase().includes(filterBy);
});

Related

Can't filter aync data on mounted vuejs

I'm working on a vuejs/(vuex) for state management/firebase project of posts.
So I have a firestore collection of posts (array of objects that have name id owner content and timestamp for creation ...)
I'm retrieving that data by using the onSnapshot methode and it's stored on blogPosts variable... and we show theme all, when a user want to visit a single post it will redirect him to the route of the single post (..../view-post/postid) and i filter that array using the id of the post to have an array of one element (which is the post he visited)
when the filter complete i got all the data and i fill theme on the template like these
<template>
<NavBarView />
<section class="preview" v-if="currentBlog">
<h2>{{ currentBlog[0].blogTitle }}</h2>
<p>Posted on: <span>{{ dateFormat() }}</span> <span v-if="currentBlog[0].editTime">(Last edit:
{{ editFormat() }})</span></p>
<img class="blogCover" :src="currentBlog[0].blogCoverFileURL" :alt="currentBlog[0].blogCoverPhoto">
<div class="html-blog" v-html="currentBlog[0].blogHTML"></div>
<hr />
</section>
</template>
<script>
export default {
data() {
return {
currentBlog: null
}
},
mounted: {
this.currentBlog = this.blogPosts.filter((post) => {
return post.blogID == this.$route.params.id
})
},
computed: {
...mapState(['blogPosts'])
}
//note that i have exported all the requirements such as mapState and firebase functions ... didn't write theme here
}
</script>
now the problem is that the filter is occurring before the data are fetched from firebase and i can't handle that so it's always returning can't read property of null (currentBlog[0])
i found a solution which is sending a getDoc request to firebase but it's a bad idea, (why i send request and i have all the posts here so i can filter it directly and get the specific post) which didn't work!!!!!
any solution??
Looking at your template, I'm under the impression currentBlog should not be an array, as you don't ever have more than 1 element in that array (you're filtering by blogID, which I'm guessing is a unique identifier). You need to .find() the blog entry, not .filter() it:
computed: {
blogID() {
return this.$route.params.id;
},
currentBlog() {
return this.$store.state.blogPosts.find((b) => b.blogID === this.blogID);
}
}
Note: if state.blogPosts does not start as an empty array (as it should), you might want to use:
currentBlog() {
return (this.$store.state.blogPosts || []).find(
(b) => b.blogID === this.blogID
);
}
And now replace all currentBlog[0]s with currentBlog:
<template>
<NavBarView />
<section class="preview" v-if="currentBlog">
<h2>{{ currentBlog.blogTitle }}</h2>
<p>
Posted on: <span>{{ dateFormat() }}</span>
<span v-if="currentBlog.editTime">(Last edit: {{ editFormat() }})</span>
</p>
<img
class="blogCover"
:src="currentBlog.blogCoverFileURL"
:alt="currentBlog.blogCoverPhoto"
/>
<div class="html-blog" v-html="currentBlog.blogHTML"></div>
<hr />
</section>
</template>
Important: make sure in every single method/computed/watch in the script part of the component, if using currentBlog, you're first checking if it's not falsy. Generic example:
computed: {
blogHTML() {
return this.currentBlog?.blogHTML
/* shorthand for:
* return this.currentBlog && currentBlog.blogHTML
*/
}
}
I would suggest that rather than trying to do this operation once on mounted that you instead re-run the filter every time that state.blogPosts is updated. You can easily do this through a computed property. See below:
export default {
data() {
return {
}
},
computed: {
...mapState(['blogPosts']),
currentBlog(){
const posts = this.$store.state.blogPosts;
if(Array.isArray(posts)) {
return posts.filter((post) => {
return post.blogID == this.$route.params.id
});
} else {
return null
}
}
}
}

Vue input tag element - how to fill it with tags properly?

I am using vue element <input-tag>, and I need to make edit page for one entity which contains tags. So the <input-tag> field should be populated based on the current values ​​in the entity.
I managed to do that, but the whole JSON object appears in <input-tag> field (example: {"id":2, "tagName": "vuejsproblem", "newsId": 1}), not only tagName as it should be.
Also, in console I got error, I don't know why, because this.tags is obviously not "":
[Vue warn]: Invalid prop: type check failed for prop "value". Expected Array, got String with value "".
found in
---> <InputTag>
This is code of my vue page:
<template>
<div class="pt-5">
<form #submit.prevent="editNews">
<div id="app">
<label for="tags">Tags</label>
<input-tag placeholder="Add Tag" v-model.trim="tags"></input-tag>
<br>
</div>
<button type="submit" class="btn btn-primary mt-2">Submit</button>
</form>
</div>
</template>
<script>
export default {
name: "EditNews",
data() {
return {
tags: '',
}
},
beforeMount () {
this.$axios.get(`/api/news/${this.$route.params.id}`,{
id: this.$route.params.id,
}).then((response) => {
this.tags = response.data.tags;
});
/* this.tags.forEach(element => element.tagName);
*/
},
methods: {
editNews() {
this.message = '';
if(this.tags.length > 1) {
console.log(this.tags)
this.$axios.post(`/api/news/${this.$route.params.id}`, {
tags: this.tags,
}).then(
data => {
this.message = data.message;
},
error => {
this.message = error.response.data
alert(this.message);
});
}
},
},
}
</script>
<style scoped>
</style>
As it is said here, tags must be Array. So,
define tags as Array in data() like:
data() {
return {
tags: [],
}
}
Also response.data.tags must be Array something like [ "Jerry", "Kramer", "Elaine", "George" ] as here.
You can convert response.data.tags to Array which contains only tagName by doing this: this.tags = response.data.tags.map(x => x.tagName)
Based on what I could understand from your question (if I got it right), you can do that using the render helper from vuejs, depending on which version you're trying to achieve this v2 or v3
Back in v2 you could do something like:
https://github.com/vubular/elements/blob/master/src/text/Plain.vue
using the this._v you can bind directly the content from the slot of the component or you could wrap with any html tag before passing the content in for example this._v('<span>'+this.content+'</span>'), you can also define tag as prop and then render prop instead of hardcoded tags.
Meanwhile in v3 you can return the h helper imported from vue:
render() { return h('span', {}, this.transformedContent); },. Again you can accept the span/tag as prop in child component context.
When doing so you don't need to define a <template> for your component so that you can render the component using vue helpers (where it then handles the DOM interactions).

AngularJs Auto Complete Search

So this works with static data, but when I push data with a $http this autocomplete does not work. The data pushes to the empty array of airport_list but something is happening when I try to use airport_list in for the autocomplete. Not sure what is is. I can only find answers which pertain to static data.
This is updated per everyones help.
Here is the controller
app.controller('selectCtrl', function($scope, $http) {
$scope.airport_list = null;
$http({
url: 'someUrl.com',
method: 'GET'
})
.then((response) => {
angular.forEach(response.data.airports, function(value, key) {
$scope.airport_list = response.data.airports;
})
$scope.airports = $scope.airport_list;
});
$scope.selectAirport = function(string) {
$scope.airport = string;
$scope.hidelist = true;
};
})
Here is the template
<div class="control">
<div>
<input
type="text"
name="airport"
id="airport"
ng-model="airport"
ng-change="searchFor(airport)"
placeholder="From..."
/>
<div class="airport-container-dropdown" ng-hide="hidelist">
<div
class="airport-list"
ng-repeat="airport in airports"
ng-click="selectAirport(airport)"
>
{{ airport.name }}
</div>
</div>
</div>
</div>
I really would like to do this without using bootstrap typeahead.
Thank you for looking at this.
I have made changes as recommended by below answers and the $http request is feeding into the autocomplete as a whole list but searching by name does not work and clicking on name sets [object, object]
this would be the code which is specific to that functionality.
$scope.searchFor = function(string) {
$scope.hidelist = false;
const output = [];
angular.forEach($scope.airport_list, function(airport) {
if (airport[0].toLowerCase().indexOf(string.toLowerCase(airport)) >=
0) {
output.push(airport);
}
});
$scope.airports = output;
};
$scope.selectAirport = function(string) {
$scope.airport = string;
$scope.hidelist = true;
};
Try this:
$scope.airport_list = response.data.airports;
What I am seeing is that you have an array: $scope.airport_list = [];
When you make your http request, you push what I would understand to be an array of airports into that array. So you end up with your airport array from the backend at the first position of $scope.airport_list, vs. $scope.airport_list being the actual list.
For your search method, you should change the following:
In your HTML:
ng-change="searchFor(airport.name)"
In your JS:
I've renamed your function and changed the input variable to be more clear. You were passing in a full airport, but treating it as a string. You need to compare your provided airport name to that of the airports in the array. So you iterate over the array, and compare each element's name property to what you pass in.
$scope.searchFor = function(airportName) {
$scope.hidelist = false;
const output = [];
angular.forEach($scope.airport_list, function(airport) {
if (airport.name.toLowerCase() === airportName) {
output.push(airport);
}
});
$scope.airports = output;
console.log($scope.airports);
};
I have provided minimal changes to your code to implement this, however I suggest you look at this SO post to filter drop down data more appropriately.
Angularjs Filter data with dropdown
If you want to simply filter out what is displayed in the UI, you can try this in your HTML template. It will provide a text field where you supply a partial of the airport name. If at least one character is entered in that box, the list will display on the page, with the appropriate filtering applied. This will avoid having to call functions on change, having a separate array, etc.
<input type="text" name="airport" id="airport" ng-model="airportSearch.name" placeholder="From..." />
<div class="airport-container-dropdown" ng-hide="!airportSearch.name">
<div class="airport-list"
ng-repeat="airport in airport_list | filter:airportSearch"
ng-click="selectAirport(airport)">
{{ airport.name }}
</div>
</div>

React - Not able to update list of product component

I made a list of product which contains name, price etc.. properties. Then I created a simple search box and I am searching my products based on product name . the search result returns the correct object but the UI is not getting updated with that result.Initially, I am able to see the list but after searching it is not getting updated. I am new to react SO need some help. here is my code
OnInputChange(term)
{
let result= this.products.filter(product=>{
return product.name==term;
});
console.log(result);
let list=result.map((product)=>
{
return <li key={product.price}>{product.name}</li>
});
console.log(list);
this.setState({listOfProducts:list});
}
render()
{
this.state.listOfProducts=this.products.map((product)=>
{
return <li key={product.price}>{product.name}</li>
});
return <div>
<input onChange={event=>{this.OnInputChange(event.target.value)}}/>
<ul>{this.state.listOfProducts}</ul>
</div>
}
}`
this.products.filter should probably be this.state.products.filter
When referring to a Components method you say this.onInputChange but when referencing state you must say this.state.products because this.products doesn't exist.
I would also move this:
let list=result.map((product)=>
{
return <li key={product.price}>{product.name}</li>
});
to your render statement. so your onchange handler could be:
OnInputChange(term)
{
let result= this.products.filter(product=>{
return product.name==term;
});
this.setState({listOfProducts:result});
}
and then you
render(){
return(
{this.state.listOfProducts.map(product => {
return <li key={product.price}>{product.name}</li>
})}
)
}
hope that helps! If your problem persists please share your entire code so I can better understand the issue.

AngularJS: list all form errors

Background:
I am currently working on an application with tabs; and I'd like to list the fields / sections that fail validation, to direct the user to look for errors in the right tab.
So I tried to leverage form.$error to do so; yet I don't fully get it working.
If validation errors occur inside a ng-repeat, e.g.:
<div ng-repeat="url in urls" ng-form="form">
<input name="inumber" required ng-model="url" />
<br />
</div>
Empty values result in form.$error containing the following:
{ "required": [
{
"inumber": {}
},
{
"inumber": {}
}
] }
On the other hand, if validation errors occur outside this ng-repeat:
<input ng-model="name" name="iname" required="true" />
The form.$error object contains the following:
{ "required": [ {} ] }
yet, I'd expect the following:
{ "required": [ {'iname': {} } ] }
Any ideas on why the name of the element is missing?
A running plunkr can be found here:
http://plnkr.co/x6wQMp
As #c0bra pointed out in the comments the form.$error object is populated, it just doesn't like being dumped out as JSON.
Looping through form.$errors and it's nested objects will get the desired result however.
<ul>
<li ng-repeat="(key, errors) in form.$error track by $index"> <strong>{{ key }}</strong> errors
<ul>
<li ng-repeat="e in errors">{{ e.$name }} has an error: <strong>{{ key }}</strong>.</li>
</ul>
</li>
</ul>
All the credit goes to c0bra on this.
Another option is to use one of the solutions from this question to assign unique names to the dynamically created inputs.
I made a function that you pass the form to. If there are form errors it will display them in the console. It shows the objects so you can take a look. I put this in my save function.
function formErrors(form){
var errors = [];
for(var key in form.$error){
errors.push(key + "=" + form.$error);
}
if(errors.length > 0){
console.log("Form Has Errors");
console.log(form.$error);
}
};
Brett DeWoody's answer is correct. I wanted to do the logic in my controller though. So I wrote the below, which is based off of the answer user5045936 gave. This may also help some of you who want to go the controller route. By the way Im using the toaster directive to show my users validation messages.
if (!vm.myForm.$valid) {
var errors = [];
for (var key in vm.myForm.$error) {
for (var index = 0; index < vm.myForm.$error[key].length; index++) {
errors.push(vm.myForm.$error[key][index].$name + ' is required.');
}
}
toaster.pop('warning', 'Information Missing', 'The ' + errors[0]);
return;
}
If you have nested forms then you will find this helpful:
function touchErrorFields(form) {
angular.forEach(form.$error, function (field) {
angular.forEach(field, function(errorField) {
if (!errorField.hasOwnProperty('$setTouched')) {
touchErrorFields(errorField);
} else {
errorField.$setTouched();
}
})
});
}

Resources