Filtering an observable in Angular/Nativescript - json

Currently, I'm trying to build an app which retrieves an obserable, which you can then sort and/or filter in some predefined ways.
Retrieving and sorting the data works fine:
sort.service.ts
import { Injectable } from "#angular/core"
import { HttpClient, HttpErrorResponse } from "#angular/common/http"
import { Observable } from "rxjs/Observable";
import { Subscriber } from "rxjs";
import "rxjs/add/operator/catch";
import "rxjs/add/operator/do";
import "rxjs/add/operator/map";
import { Property } from "../property/property.model";
import { UserSettings } from "../../data/usersettings/usersettings.service"
export class SortService {
url = "/path/to/file.json";
constructor(private http:HttpClient) {}
getProperties(): Observable<Property[]> {
return this.http.get<Property[]>(this.url);
}
sortAllProperties() {
let count = 0;
return this.getProperties()
.map((data) => {
data.sort((a: Property, b: Property) => {
const aP = a.price;
const bP = b.price;
const aS = a.areaSize;
const bS = b.areaSize;
const aR = a.numberOfRooms;
const bR = b.numberOfRooms;
const aB = a.numberOfBedrooms;
const bB = b.numberOfBedrooms;
/*if(this.userSettings.getAppSetting("filterMinPrice", "number") >= a.price)
console.log(a.price + " is smaller than " + this.userSettings.getAppSetting("filterMinPrice", "number"));*/
const aID = a.ID;
const bID = b.ID;
//Price sort (primary)
const priceSort = this.userSettings.getAppSetting("sortByPrice", "string");
if(priceSort == "asc") {
if (aP > bP) return 1;
if (aP < bP) return -1;
} else if (priceSort == "desc") {
if (aP < bP) return 1;
if (aP > bP) return -1;
} else {
count++;
}
//Areasize sort (secondary)
const sizeSort = this.userSettings.getAppSetting("sortBySize", "string");
if(sizeSort == "asc") {
if (aS > bS) return 1;
if (aS < bS) return -1;
} else if (sizeSort == "desc") {
if (aS < bS) return 1;
if (aS > bS) return -1;
} else {
count++;
}
//Rooms sort (tertiary)
const roomSort = this.userSettings.getAppSetting("sortByRooms", "string");
if(roomSort == "asc") {
if (aR > bR) return 1;
if (aR < bR) return -1;
} else if (roomSort == "desc") {
if (aR < bR) return 1;
if (aR > bR) return -1;
} else {
count++;
}
//Bedrooms sort (quaternary)
const bedroomSort = this.userSettings.getAppSetting("sortByBedrooms", "string");
if(bedroomSort == "asc") {
if (aB > bB) return 1;
if (aB < bB) return -1;
} else if (bedroomSort == "desc") {
if (aB < bB) return 1;
if (aB > bB) return -1;
} else {
count++;
}
if(count = 4) {
return aID > bID ? 1 : -1;
}
})
return data;
})
}
}
The data being retrieved here, looks like this:
file.json
[
{
"ID": 1,
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing ...",
"price": 800.25,
"agreementType": "unknown",
"streetName": "street1",
"houseNumber": 249,
"postCode": "postcode",
"place": "New Orlands",
"status": "status",
"constructionYear": 1999,
"areaSize": 5540,
"numberOfRooms": 545,
"numberOfBedrooms": 21,
"garageType": "",
"garageCapacity": 0
},
{
//...
}
]
and the property model, which the JSON format "adheres" to, looks as follows...
property.model.ts
export class Property {
ID: number;
description: string;
price: number;
agreementType: string;
streetName: string;
houseNumber: number;
postCode: string;
place: string;
status: string;
constructionYear: number;
areaSize: number;
numberOfRooms: number;
numberOfBedrooms: number;
garageType: string;
garageCapacity: number;
}
I'm displaying my data in the property component simply using an async pipe, which works just fine: *ngFor="let item of propertyData | async". The sorting works as well. It's the filtering I have an issue with.
For now, I'm simply trying to apply a static filter inside the sortAllProperties() method. Making it dynamic and giving it its own class, methods etc can come later.
It's also hard finding the exact right information for this, because most of it is outdated and uses http rather than httpClient, which is of course slighty different.
Every attempt I've made so far (all copied from internet examples and slightly adjusted to fit my use-case) resulted in an error. The closest I got so far is .filter((property) => property.price > 800) which I tried placing in front of, and later after the .map() function, both resulting in the same error:
[ts] Property 'price' does not exist on type 'Property[]'.
Could it be that I'm missing some functions I should use on the observable before filtering? I'm really at a loss right now.
Thank you in advance.

With the help of another programmer, I finally managed to get the solution. As usual, it turned out to be pretty simple:
return this.getProperties()
.map(properties => properties.filter(property=> property.price > 810)
.sort((a: Property, b: Property) => {
//sorting stuff
})
That's for one filter. If you want to apply multiple filters, you could probably do something like
return this.getProperties()
.map(properties => properties.filter((property) => {
//filter coniditions, arrays etc
return property;
})
.sort((a: Property, b: Property) => {
//sorting stuff
})

Related

How do I do a recursion over objects of unknown depth in Typescript?

I have a JSON file with a category structure of unknown depth. I want to make sure all pages can be accessed. I established three nested calls, but I think it would be better to recursion here. Unfortunately, I have no experience with Typescript regarding recursion. Can someone be so kind as to help me put the logic into a function I can call?
test.setTimeout(28800000); // 8 hours max.
// console.log(ofcJSON)
for (let i = 0; i < ofcJSON.items.length; i++) {
let currentPage = ofcJSON.items[i].link
console.log(currentPage)
if (!currentPage.startsWith("http")) await page.goto(currentPage)
if (ofcJSON.items[i].items != null) {
for (let j = 0; j < ofcJSON.items[i].items!.length; j++) {
let currentPage1 = ofcJSON.items[i].items![j].link
console.log(currentPage1)
if (!currentPage1.startsWith("http")) await page.goto(currentPage1)
if (ofcJSON.items[i].items![j].items != null) {
for(let k = 0; k < ofcJSON.items[i].items![j].items!.length; k++) {
let currentPage2 = ofcJSON.items[i].items![j].items![k].link
console.log(currentPage2)
if (!currentPage2.startsWith("http")) await page.goto(currentPage2)
if (ofcJSON.items![i].items![j].items![k].items != null) {
for(let l = 0; l < ofcJSON.items[i].items![j].items![k].items!.length; l++) {
let currentPage3 = ofcJSON.items[i].items![j].items![k].items![l].link
console.log(currentPage3)
if (!currentPage3.startsWith("http")) await page.goto(currentPage3)
}
}
}
}
}
}
}
});
The JSON has 1 items object, which in turn can have 1 items object. This is optional. I don't know the depth.
I sketched an implementation which compiles and runs in the typescript playground as below (click on Run top left in the playground)...
type HttpLink = `http{'s'|''}://${string}`;
function isHttpLink(link: string): link is HttpLink {
return !!link.match(/^https?:\/\//);
}
type Link = HttpLink | string;
interface Item {
link: Link;
items?: Item[];
}
async function goto(link: HttpLink) {
console.log(`Ran goto on ${link}`);
}
async function visitItemAndDescendants(ancestor: Item) {
const { link, items } = ancestor;
if (isHttpLink(link)) {
await goto(link);
}
if (items) {
for (const item of items) {
visitItemAndDescendants(item);
}
}
}
{
const exampleItem: Item = {
link: "https://my.url",
items: [
{
link: "not http",
items: [
{
link:"http://insecure.url"
},
{
link:"https://another.url"
}
],
},
],
};
visitItemAndDescendants(exampleItem)
}
Thanks to your help and the help of a colleague I have solved the problem as follows:
import { Page, test } from '#playwright/test';
import fetch from "node-fetch";
test.use({
baseURL: "https://www.myUrl.de/"
})
const links: string[] = [];
interface Item {
link: string;
items?: Item[];
}
async function getLinks(item: Item): Promise<void> {
if (item.items && item.items.length > 0) {
for (let i = 0; i < item.items.length; i++) {
let currentItem = item.items[i];
if (currentItem.link && currentItem.link.length > 0) {
links.push(currentItem.link);
if (currentItem.items && currentItem.items.length > 0)
getLinks(currentItem);
}
}
}
}
test('test', async ({ page }) => {
test.setTimeout(1560000); // 26 minutes max.
const ofcJSON = await fetch('https://www.myUrl.de/ofcJSON')
.then((response) => response.json())
.then((item) => {
return item.items
})
// console.log(ofcJSON);
ofcJSON.forEach(element => {
getLinks(element);
});
var maximumNumberOfLinksToCheck = 10;
var delta = Math.floor(links.length / maximumNumberOfLinksToCheck);
for (let i = 0; i < links.length; i = i + delta) {
console.log("Checking page: " + links[i])
await (page.goto(links[i]));
}
});

I'm trying to create a memory game where an expanding list of numbers is shown in ionic and angular and the user has to type in the answer

The way that I am doing it is that I want each of the numbers to appear then disappear. I have tried a lot of options but only the last number ends up showing when there are two or more numbers in the array. I suspect it has something to do with the for loop, but there does not seem to be a way around it.
Here is my typescript code for the generate numbers function:
generateNumbers() {
let numbersArray = new Promise<number[]>((resolve, reject) => {
let numberArray: number[] = []
for (let i = 0; i < this.level; i++) {
this.animationCtrl.create()
.addElement(this.currentNum.nativeElement)
.duration(500)
.iterations(1)
.fromTo('opacity', '1', '0.05').play()
.then(func => {
let randomnum = Math.floor(Math.random() * 9)
numberArray.push(randomnum)
this.currentIndex = i
this.currentNumber = randomnum
this.parsedCurrentNumber = JSON.parse(JSON.stringify(this.currentNumber))
}).then(func => {
this.animationCtrl.create()
.addElement(this.currentNum.nativeElement)
.duration(500)
.iterations(1)
.fromTo('opacity', '0.05', '1').play()
}).then(func => {
if (i === this.level - 1) {
resolve(numberArray)
}
})
}
})
return numbersArray
}
Here are my variable declarations and injections:
#ViewChild('currentNumber', { read: ElementRef, static: true}) currentNum: ElementRef;
level: number = 1;
levelExp: number = 1;
gameHasBegun = false;
paused = false;
numbersArray: number[] = [];
answer: string;
wrongcount: number = 0;
wrong = false;
lost = false;
currentIndex: number = 0
currentNumber: number;
parsedCurrentNumber: string;
constructor(
private router: Router,
private menu: MenuController,
private animationCtrl: AnimationController ) { }
Here is how I call my generate function:
this.generateNumbers().then(
(val) => this.numbersArray = val
)
Here is my HTML Code for the part where the numbers should be shown, but instead only one number is shown when I have two or more numbers in my array:
<ion-content #currentNumber>
<ion-label class="ion-text-center" >
<h1>{{ parsedCurrentNumber }}</h1>
</ion-label>
</ion-content>
Look at the following stackblitz.
https://stackblitz.com/edit/ionic-79e1rn
You basically need to loop through your array with a timeout.
ionViewDidEnter(){
this.runSeries(0);
}
runSeries(i){
if(i < this.nums.length){
setTimeout(() => {
this.lastNum = this.nums[i];
i++;
this.runSeries(i);
}, 1000)
}
}
and bind lastNum in your template.

Angular, cannot access members of object in the array in custom pipes

Below is my custom pipe where I am unable to access the members of the customfilter array which is of type Item.
import { Pipe, PipeTransform } from '#angular/core';
import {Bus} from '/home/pavan/Desktop/Pavan/apstrtcAngular/src/app/Bus';
import { Item } from './Item';
#Pipe({
name: 'busFilter'
})
export class BusFilterPipe implements PipeTransform {
transform(items: Bus[], customfilter: Item): Bus[] {
if(!items || !customfilter)
{
return items;
}
return items.filter((item: Bus)=>
this.applyFilter(item, customfilter));
}
applyFilter(bus:Bus, customfilter: Item):
boolean{
if( customfilter[0].item_id){
if(typeof customfilter[0].item_id==='string'){
if(typeof bus.bustype==='string')
{
if(customfilter[0].item_id===bus.bustype)
{
return false;
}
} }
}
return true;
}
}
Below is my Item.ts and ng multiselect.
export class Item {
/**
* #type {number} id Unique numeric identifier.
*/
item_id: string;
item_text:string;
}
<ng-multiselect-dropdown class="ngfilter"
[placeholder]="'Select BusType'"
[data]="BusTypes"
[(ngModel)]="customfilter"
[settings]="dropdownSettings"
(onSelect)="onItemSelect($event)"
(onSelectAll)="onSelectAll($event)"></ng-multiselect-dropdown>
I am unable to find the issue here, I cannot look at the value of item_id during debugging too. please help me to know where the issue is. Thank you.
import { Pipe, PipeTransform } from '#angular/core';
import {Bus} from '/home/pavan/Desktop/Pavan/apstrtcAngular/src/app/Bus';
import { Item } from './Item';
import { forEach } from '#angular/router/src/utils/collection';
#Pipe({
name: 'busFilter'
})
export class BusFilterPipe implements PipeTransform
{
transform(items: Bus[], customfilter: Item[]): Bus[] {
let ResultSet: Bus[] = [];
if (!items || !customfilter) {
return items;
}
else if (customfilter.length == 0) {
return items;
}
else{
for (let i = 0; i < items.length; i++) {
for (let j = 0; j < customfilter.length; j++) {
if (customfilter[j].item_text === items[i].bustype) {
ResultSet.push(items[i]);
console.log("Result Set =" + ResultSet);
}
}
}
return ResultSet;
}
}
}
Based on your comments and my understanding of your code written in the pipe, modify your pipe like this (please read through the comments in the code):
transform(items: Bus[], customfilter: Item[]): Bus[] {
if(!items || !customfilter)
{
return items;
}
// making custom filter an Array if it isn't already
customFilter = customFilter instanceof Array ? customFilter : [customFilter];
// you seem to ignore the custom filters which don't have item_id
customFilter = customFilter.filter((eachCustom) => eachCustom.item_id);
// create an array of new items which satisfy your criteria
return items.reduce((acc, eachBus) => {
// if bus's bustype is not string then no need to filter
if (typeof eachBus.bustype != 'string') {
acc.push(eachBus)
}
else {
// if the bustype is a string
// then you have to see if this bus's bustype matches any of the custom filters and it's id type
// if not found then that bus should be present in the final bus list
let filterFound = customFilter.findIndex((eachFilter) => {
return (typeof eachFilter.item_id === 'string') && (typeof eachBus.bustype === 'string') && (eachFilter.item_id === eachBus.bustype);
});
if (filterFound === -1) {
// this bus is not found in the filter
acc.push(eachBus)
}
}
return acc;
}, [])
}
Below is a script in javascript to verify the result
function transform(items, customfilter) {
if(!items || !customfilter)
{
return items;
}
// making custom filter an Array if it isn't already
customFilter = customFilter instanceof Array ? customFilter : [customFilter];
// you seem to ignore the custom filters which don't have item_id
customFilter = customFilter.filter((eachCustom) => eachCustom.item_id);
// create an array of new items which satisfy your criteria
return items.reduce((acc, eachBus) => {
// if bus's bustype is not string then no need to filter
if (typeof eachBus.bustype != 'string') {
acc.push(eachBus)
}
else {
// if the bustype is a string
// then you have to see if this bus's bustype matches any of the custom filters and it's id type
// if not found then that bus should be present in the final bus list
let filterFound = customFilter.findIndex((eachFilter) => {
return (typeof eachFilter.item_id === 'string') && (typeof eachBus.bustype === 'string') && (eachFilter.item_id === eachBus.bustype);
});
if (filterFound === -1) {
// this bus is not found in the filter
acc.push(eachBus)
}
}
return acc;
}, [])
}
let buses = [{bustype: 1}, {bustype: "volvo-ac"}, {bustype: "volvo-non-ac"}, {bustype: "non-volvo-ac"}, {bustype: "non-volvo-non-ac"}]
let customFilter = [{item_id: "volvo-ac"}, {item_id: "non-volvo-ac"}]
console.log(transform(buses, customFilter))
// expected output won't contain the buses present in the filter

Object from Observable then Array from Observable inside a foreach. how to order it?Asynchronous Angular 4/5

Here is my problem.
I'm running a method that sends me a json (method = myTableService.getAllTables ()), to create an object (object = this.myTables).
Then I execute the method for each, for each element of this.myTables I execute a new request (request = this.myTableService.getTableStatut (element.theId)).
I retrieve data from a new json to create an object (object = myTableModel).
Each result will be added to this.myTableListProvisory.
The problem is the order of execution.
It execute the console.log before the end of the for each...
This.myTableListProvisory.length and this.myTableList.length return 0.
How to wait for the end of the for each run before running the console.log?
Thank you
ngOnInit() {
this.myTableService.getAllTables()
.subscribe(data => {
this.myTables = data;
this.myTableList = this.getAllTableStatut(this.myTables);
console.log("this.myTableList.length : " + this.myTableList.length);
}, err => {
console.log(err);
})
}
getAllTableStatut(myTables: any) {
this.myTableListProvisoire = [];
myTables.forEach(element => {
this.myTableService.getTableStatut(element.theId)
.subscribe(data => {
this.statut = data;
this.myTableModel = new MyTableModel(element.tableNumber, this.statut.name, element.theId);
this.myTableListProvisoire.push(this.myTableModel);
})
console.log("this.myTableListProvisoire.length : " + this.myTableListProvisoire.length);
})
return this.myTableListProvisoire;
}
Result of console.log
this.myTableListProvisoire.length : 0
this.myTableList.length : 0
UPDATE
I have simplified the code ... I put it in its entirety for the understanding. What I need is to sort the array after it is done. The problem is that I don't know how to use a flatMap method in a query inside a foreach ... I have temporarily placed the sort method inside the subscribe which is a bad solution for the performance. That's why I want to do my sort after the creation of the array. Thank you
export class MyTableComponent implements OnInit {
myTables: any;
statut: any;
myTableModel: MyTableModel;
myTableList: Array<MyTableModel>;
myTableListProvisoire: Array<MyTableModel>;
i: number;
j: number;
myTableModelProvisoire: MyTableModel = null;
constructor(public myTableService: MyTableService) { }
ngOnInit() {
this.myTableService.getAllTables()
.subscribe(data => {
this.myTables = data;
this.myTableList = this.getAllTableStatut(this.myTables);
}, err => {
console.log(err);
})
}
getAllTableStatut(myTables: any) {
this.myTableListProvisoire = [];
myTables.forEach(element => {
this.myTableService.getTableStatut(element.theId)
.subscribe(data => {
this.statut = data;
this.myTableModel = new MyTableModel(element.tableNumber, this.statut.name, element.theId);
this.myTableListProvisoire.push(this.myTableModel);
for (this.j = 0; this.j < this.myTableListProvisoire.length; this.j++) {
for (this.i = 0; this.i < this.myTableListProvisoire.length - 1; this.i++) {
if (this.myTableListProvisoire[this.i].getTableNumber() > this.myTableListProvisoire[(this.i + 1)].getTableNumber()) {
this.myTableModelProvisoire = this.myTableListProvisoire[this.i];
this.myTableListProvisoire[this.i] = this.myTableListProvisoire[(this.i + 1)];
this.myTableListProvisoire[(this.i + 1)] = this.myTableModelProvisoire;
}
}
}
}, err => {
console.log(err);
})
}, err => {
console.log(err);
})
return this.myTableListProvisoire;
}
}
Well Observables are asynchronous actions and will be executed after finishing the current execution block. So when the js engine comes to your
this.myTableService.getTableStatut(element.theId)
.subscribe(data => {
this.statut = data;
this.myTableModel = new MyTableModel(element.tableNumber, this.statut.name, element.theId);
this.myTableListProvisoire.push(this.myTableModel);
})
it will only create a subscription, but the code inside of it will be executed after all the other code in the block. So that's why your console.log is being executed before you get any data. So you need to place it inside the .subscribe block to see the. I think there can be a better solution to get the data, but I don't know the structure of the app, so I can't advice. If you create an example on https://stackblitz.com/ I could probably help you out with a better solution.

Display only the first elements of Object.keys

I have made a JSON Request so i can bring the Objects in Angular2. But I want to display only the first 15 elements and then if it works repeat the same process on InfiniteScroll. So this is one of my code.
setList(informes) {
if (informes) {
for (let id of Object.keys(informes)){
this.count = 0;
for (let i = 0; i < 15; i++) {
let node = informes[id];
this.informes.push(node[this.count]);
console.log (id);
this.count++;
}
}
}
}
Obviously It doesn't work, it keeps giving me all elements like 15 times each. I know that but on the other hand if i make the opposite.
setList(informes) {
if (informes) {
for (let i = 0; i < 15; i++) {
for (let id of Object.keys(informes)){
let node = informes[id];
this.informes.push(node[this.count]);
console.log (id);
}
this.count++
}
}
}
It counts the number of nodes in total.
What i want is to display only the first 15 elements. And then repeat the code in my other function infiniteScroll (I will do that by myself, it works).
Any suggestion will be appreciated.
UPDATE:
Here's the constructor:
constructor(public navCtrl: NavController, public navParams: NavParams, public nav: NavController, public http: Http, public sanitizer: DomSanitizer) {
this.dataUrl = 'https://myurl.com/ionic/'; //example
if (this.dataUrl) {
this.http.get(this.dataUrl)
.map(res => res.json())
.subscribe(informes => this.setList(informes));
}
}
UPDATE 2:
The code works well.
I had to modify some things to make it work. I will update the script so if it could help someone.
setList(informes) {
if (informes) {
let ids = Object.keys(informes);
ids.forEach((id, index) => {
if(index < 15){
let node = informes[id];
this.informes.push(node);
this.count++;
console.log(this.count);
}
});
}
}
goToNodeInformes(node){
this.navCtrl.push(NodeInformesPage, {'node':node.nid});
}
doInfinite(infiniteScroll, informes) {
informes = this.informes;
setTimeout(() => {
let ids = Object.keys(informes);
ids.forEach((id, index) => {
if(index < 15){
let node = informes[id];
this.informes.push(node);
this.count++;
console.log(this.count);
}
});
infiniteScroll.complete();
}, 500);
}
}
I will figure what i have to do for not repeating the same nodes (will update) but the counter works!!!
I think you are looking for something like this :
let keys = Object.keys(informes);
keys.foreach((key, index) => {
if(index < 15){
let node = informes[key];
this.informes.push(node);
console.log(informes[key]);
}
});