I have an observable array and would like to get the sum of a property values in that array. My array is defined as:
public bookStores$: Observable;
I was going to do a simple for loop and calculate the sum, but I get a syntax error when trying to use the count property of my array:
Operator '<' cannot be applied to types 'number' and '<T>(this: Observable<T>, predicate?: (value: T, index: number, source: Observable<T>)=>boolean)...
This occurs when I do:
for (let i = 0; i < this.bookStores$.count; i++){ }
Every item in my array of BookStore objects has a property called numberOfBooks. What is the proper way to get the sum of those values contained on each BookStore object in my BookStore array?
This is why you're getting unexpected results for Observable.count
To get the array lenght of the results, you need to do, something like this:
BookStoreService.ts
...
getBookStore() : Observable<Bookstore> {
this.bookstore$ = this.http.get(...)
.map( (response:Response) => response.json() )
.map( response => response.bookstore); // optional depends if JSON payload you want is wrapped inside some other container
return this.bookstore$;
}
Component.ts
...
bookstore$ : Observable<Bookstore>;
bookstores: Bookstore[];
numberOfBookStores:number;
constructor(private bookService:BookService) {}
..
this.bookService.getJobs()
.subscribe(
bookstores => {this.bookstores = bookstores;
this.numberOfBookstores = this.bookstores.length;
},
err => { this.bookstores = [] as BookStore[];
// Caters for 404 etc error - be sure to be more robust in final code as you may want to indicate types of error back to user.
},
() => {
}
);
Update:
If you only need to loop through the list in yourHTML template, then
then defining the bookstores array as a property would not be necessary. I did this to illustrate how to get the size of the returned collection of bookstores.
You can use this type of syntax:
<tr *ngFor="let bookstore of (bookstore$ |async) as bookstores;
trackBy bookstore?.id; let i = index">
<td>{{bookstore.numberofBooks}}
<tr/>
You can find out more about:
*ngFor trackBy, even, odd, first, last here.
Using Async pipe for entire block of html template with AS here
Furthermore have a look at libraries like Lodash and Underscore for summing count of number of books. I've not used Underscore myself.
Here's a simple example to get you started.
If you want to get more adventurous have a look at this Functional Programming in Javascript Tutorial
Related
Overview
Im trying to create an initialise an object with known keys, however the initial empty object {} that I need breaks my typing.
The problem:
I have a list of value, that I want to use as keys, and I want to initialise an object with those keys. In python i would do something like:
myDict = {key: value for (key, value) in zip(myKeys, [[]]*len(myKeys))}
This should give the myDict the type Record<myKeys, string[]>
In TS, i dont seem to be able to have the same luck, as i need to initialise the object to an empty list before i can populate it with my keys/values. I would rather not need to call it a partial, then coerce it into the correct type.
what ive tried:
I have tried:
const allPlanFeatures: Partial<Record<ExtensivePlanHandle, string[]>> = {};
for (const planId of extensivePlanList) {
const features = getFeaturesForPlan(planHandleToMonthly[planId], is4kEnabled, exportFlow);
allPlanFeatures[planId] = features;
}
return allPlanFeatures as Record<ExtensivePlanHandle, string[]>;
and
const allPlanFeatures: Partial<Record<ExtensivePlanHandle, string[]>> =
extensivePlanList.reduce(
(a, planHandle) => ({
...a,
[planHandle]: getFeaturesForPlan(
planHandleToMonthly[planHandle],
is4kEnabled,
exportFlow
),
}),
{}
);
return allPlanFeatures as Record<ExtensivePlanHandle, string[]>;
These do end up with the desired effect at the end of the day, but I'd rather be able to not have this partially working middle
You can try this:
const allPlanFeatures: Record<ExtensivePlanHandle, string[]> = {} as Record<ExtensivePlanHandle, string[]>;
this is not perfect, but sometimes you need to tell typescript compiler what is right.
In my ReactJS application I am getting the mobile numbers as a string which I need to break and generate a link for them to be clickable on the mobile devices. But, instead I am getting [object Object], [object Object] as an output, whereas it should be xxxxx, xxxxx, ....
Also, I need to move this mobileNumbers function to a separate location where it can be accessed via multiple components.
For example: Currently this code is located in the Footer component and this code is also need on the Contact Us component.
...
function isEmpty(value) {
return ((value === undefined) || (value === null))
? ''
: value;
};
function mobileNumbers(value) {
const returning = [];
if(isEmpty(value))
{
var data = value.split(',');
data.map((number, index) => {
var trimed = number.trim();
returning.push(<NavLink to={`tel:${trimed}`} key={index}>{trimed}</NavLink>);
});
return returning.join(', ');
}
return '';
};
...
What am I doing wrong here?
Is there any way to create a separate file for the common constants / functions like this to be accessed when needed?
First question:
What am I doing wrong here?
The issue what you have is happening because of Array.prototype.join(). If creates a string at the end of the day. From the documentation:
The join() method creates and returns a new string by concatenating all of the elements in an array (or an array-like object), separated by commas or a specified separator string. If the array has only one item, then that item will be returned without using the separator.
Think about the following:
const navLinks = [{link:'randomlink'}, {link:'randomlink2'}];
console.log(navLinks.join(','))
If you would like to use concatenate with , then you can do similarly like this:
function mobileNumbers(value) {
if(isEmpty(value)) {
const data = value.split(',');
return data.map((number, index) => {
const trimed = number.trim();
return <NavLink to={`tel:${trimed}`} key={index}>{trimed}</NavLink>;
}).reduce((prev, curr) => [prev, ', ', curr]);
}
return [];
};
Then you need to use map() in JSX to make it work.
Second question:
Is there any way to create a separate file for the common constants / functions like this to be accessed when needed?
Usually what I do for constants is that I create in the src folder a file called Consts.js and put there as the following:
export default {
AppLogo: 'assets/logo_large.jpg',
AppTitle: 'Some app name',
RunFunction: function() { console.log(`I'm running`) }
}
Then simply import in a component when something is needed like:
import Consts from './Consts';
And using in render for example:
return <>
<h1>{Consts.AppTitle}</h1>
</>
Similarly you can call functions as well.
+1 suggestion:
Array.prototype.map() returns an array so you don't need to create one as you did earlier. From the documentation:
The map() method creates a new array populated with the results of calling a provided function on every element in the calling array.
I hope this helps!
I've done some research on this issue. I am trying to manipulate an array of calculated values that looks like this in the console:
{nodeVoltages: Array(11), totalPower: Array(1), xlength: Array(11)}
nodeVoltages: Array(11)
0:48
1:47.71306060387108
2:47.250273223993105
3:46.59686907269243
4:45.71876416434013
5:44.53304242029258
6:42.745236969423615
7:Complex {re: 40.38334500994142, im:1.919295696316476, __ember1513267958317: "ember368"}
8:Complex { re:39.55961661806138, im:3.8933604519196416, __ember1513267958317: "ember369"}
This array is created dynamically through some math that I've come up with so there is no input data that I can give you. I'm trying to make the above array look like this:
{nodeVoltages: Array(11), totalPower: Array(1), xlength: Array(11)}
nodeVoltages: Array(11)
0:48
1:47.71306060387108
2:47.250273223993105
3:46.59686907269243
4:45.71876416434013
5:44.53304242029258
6:42.745236969423615
7:40.38334500994142
8:39.55961661806138
Using mathjs, I was able to evaluate my expressions and dynamically add the values into an array with the array.push command and display them. However, my code breaks once the imaginary values pop up in the results of my array.
How can I remove these imaginary numbers from my array? In other words, I need to remove the "im:" parts of the values when they begin to appear before I push them to the displayed array.
I tried to do this with some code I found from a previous answer to someone else's question (How do I remove a particular element from an array in JavaScript?) splice command like this:
var nodeVoltage2 = parser.eval(expression2);
//checks if there are imaginary values and removes them
if ("im" in nodeVoltage2) {
nodeVoltage2.splice(2,1)
}
//adds value to result array for analysis
nodeVoltages.push(nodeVoltage2);
but it returns in the console that "im is not defined".
Any help is greatly appreciated!
You can use the array map function.
Basically, we loop through the array. If the item has a .re property, we take that value only. If there is no .re property, we keep the value as is.
We can either write that in shorthand, as with result using the ternary operator and arrow function, or we can write it in a slightly more verbose but traditional way, as with resultTwo
let data = [
48
,47.71306060387108
,47.250273223993105
,46.59686907269243
,45.71876416434013
,44.53304242029258
,42.745236969423615
,{re: 40.38334500994142, im:1.919295696316476, __ember1513267958317: "ember368"}
,{ re:39.55961661806138, im:3.8933604519196416, __ember1513267958317: "ember369"}
]
let result = data.map((x) => x && x.re ? x.re : x);
let resultTwo = data.map(function(elem) {
// First, we need to check that the array element is not null / undefined
// We then need to check that it has a property called re that is also not null / undefined
if (elem != null && elem.re != null) {
// Just return the property we're interested in
return elem.re;
} else {
// Return the element as is
return elem;
}
});
console.log(result);
console.log(resultTwo);
In my aurelia app, I need to display a list of elements. For performance reasons, these elements are stored as a javascript object instead of an array. That is what the object looks like :
var x = {
0 : {...},
3 : {...},
5 : {...},
}
Here is the part of the template where I display these elements :
<template>
<ul>
<li repeat.for="property of object | ObjectToArray">
${ property.text }
</li>
</ul>
</template>
As you can see, I'm currently using a value converter to be able to iterate over my object properties. The value converter simply converts the object to an array :
export class ObjectToArrayValueConverter {
toView(data : {[key : string] : any}) : any[] {
var array = [];
for (var key in data) {
array.push(data[key]);
}
return array;
}
}
This solution works very well as long as properties do not get removed or added to the object after the list has been rendered for the first time. The reason is that the value converter only gets called once.
In my case, however, I need my list to stay up to date whatever happens.
I know I could create a function that could be manually called every time the object is modified, but that would add some unwanted complexity in business logic.
Is there an aurelia functionality that coud help me achieve what I want ? I could not find any help in the docs. Thank you !
You can get the keys using Object.prototype.keys and call Array.prototype.map on it to get an array every time you want to list it out.
var obj={...}; //Object with many keys
var genArray = Object.keys(obj).map(function(key){return obj[key];})
//Using as a function
function getKeysAsArray(obj){
if(!obj){
return [];
}
return Object.keys(obj).map(function(key){return obj[key]});
}
There's an easier strategy, as shown in the docs:
export class KeysValueConverter {
toView(obj) {
return Reflect.ownKeys(obj);
}
}
Usage:
<p repeat.for="greeting of friends | keys">${greeting}, ${friends[greeting].name}!</p>
I have this piece of code:
ngOnInit(): void
{
this.categories = this.categoryService.getCategories();
var example = this.categories.flatMap((categor) => categor.map((categories) => {
var links = this.categoryService.countCategoryLinks(categories.id)
.subscribe(valeur => console.log(valeur));
return categories.id
}));
}
The result are two observables.
One consists in a list of categories.
The second one is the number of items for a particular categories.id.
My question is as follow:
How could I get all this information structured in a particular data structure?
I would like to store categories and the number of items per category in the same data structure to be able to show them up in my TS component.
I went step by step trying to fix my issues and I went to have the following code that is almost the solution:
this.categories = this.categoryService.getCategories();
var example = this.categories.mergeMap((categor) => categor.map((myCateg) =>
{
this.categoryService.countCategoryLinks(myCateg.id)
.map(numlinks => Object.assign(myCateg,{numLinks: numlinks}))
.subscribe(valeur => console.log(valeur));
return myCateg.id
}));
It gives the following output:
Where numLinks is still an object... (containing my count value) Any idea on how to transform it to a json property like categoryName or id??
Thanks in advance and Regards,
Here is the solution to the problem:
ngOnInit(): void
{
this.categories = this.categoryService.getCategories();
const example = this.categories
.mergeMap((categor) => categor
.map((myCateg) => {
this.categoryService.countCategoryLinks(myCateg.id)
.map(numlinks => {
myCateg.numlinks = numlinks.count;
return myCateg;
})
//Object.assign(myCateg,{numLinks: numlinks}))
.subscribe(value => console.log(value));
return myCateg
}));
example.subscribe(val => console.log("value2: "+val));
}
Once more, the solution comes from the mergeMap() operator. :-)
this.categoryService.getCategories()
.mergeMap(val => val) // "Flatten" the categories array
.mergeMap(category =>
this.categoryService.countCategoryLinks(category.id)
// Write the number of links on the `category` object
.map(numLinks => Object.assign(category, {numLinks: numLinks}))
)
.toArray()
.subscribe(allCategoriesWithNumLinks => console.log(allCategoriesWithNumLinks));
I'm not going into the specifics (the first mergeMap to flatten the array, Object.assign() to produce the final object) since it seems like we covered all that in a previous thread we had, but feel free to ask questions if anything is unclear.
Philippe's questions:
Why flatten the categories array? I'm assuming getCategories() emits a SINGLE array containing all the categories. Since you want to run an HTTP request for each category, it's more convenient to have an observable emitting each category individually. That's what the first mergeMap() does: it transforms Observable<Category[]> into Observable<Category>.
Why create an object? You said you wanted to store everything in the same data structure. That's what Object.assign does: it writes the number of links found for each category on the category object itself. This way, you end up with ONE object containing the information from TWO observables (category + numLinks).