I am using Angular 2 with RxJs. I get the following compilation exception,
[ts]
Type 'Observable<Observable<B>>' is not assignable to type
'Observable<B>'.
Type 'Observable<B>' is not assignable to type 'B'.
Property 'id' is missing in type 'Observable<B>'.
Below is the code in question. I can't really change the method signature for 'getB' since I am overriding it. The base method does not need the 2nd API call to fill children, hence overriding.
export interface A {
b: B;
}
export class B {
cList: C[];
}
export interface C {
name: string;
type: string;
}
getB(): Observable<B> {
let entries = <A[]> []; // get A[] via an server call
// in this case entries will have only one element in the array
return this.loadBChilds(entries[0])
.map(e => e.b);
}
loadBChilds(a: A): Observable<A> {
// API call to fetch childs
// set childs to a.b.List
return this.rest.get('someapi')
.map(res => {
res.record.map(r => this.setData(r));
return a;
});
}
Any idea how to achieve this?
TIA
Managed to solve this. The fix was to collapse the first array being received from the first API via a flatMap and then use that for subsequent API call.
getB(): Observable<B> {
return this.rest.get('firstapi')
.flatMap(res => {
let entries = res.record
.map(record => this.getEntry(record));
return this.loadBChilds(entries[0])
.map(e => e.b);
});
}
Related
I am trying to iterate over an "array" that's being returned from a result set in order to process the objects contained within.
I have tried various approaches including data.forEach(), const myStuff = {...data} and for (let item of data) {} in both the "success" function of the .subscribe and in the processData function, but in all cases I'm unable to extract the values desired.
DevTools reports the result to be Array(51) but it sure doesn't want to process like a typical Array.
submitCarrierSearchForm = (): void => {
this.carrierService
.carrierGetCarrierListByOrgCode({
organizationCode: this.searchForm
.get('carrierSearch')
?.get('organizationCode')?.value
})
.pipe(takeUntil(this.destroy$))
.subscribe(
(data) => {
// this.processData(data);
console.log(data);
},
(error: unknown) => {
console.log('Errors encountered: ', error);
},
);
};
processData(carriers: Carrier): void {
console.log('coming from data: ', carriers);
}
Errors vary, including:
Property 'forEach' doesn't exist on type (lists all properties of the object) when using
const stuff = { ...carriers };
stuff.forEach(element => {
console.log(element.carrierName);
});
No doubt, it's something simple that I'm missing.
It seems that you declare 1 item in stead of an array of items:
maybe try this:
processData(carriers: Carrier[]): void {
console.log('coming from data: ', carriers);
}
Turned out to be an error in the return type of the API which was preventing the object from actually being an array. Once the return type was corrected, all the "normal" ways of manipulating an array worked as expected.
I'm currently busy with an Angular project where I receive an object containing an array of objects from an API.
"reportData" is the object being sent to the API as a parameter through my service.
Example of data sent back from the API:
I want to loop through the array and push the product names to an array and the same with the averageQuantityOrdered but I don't know how to loop through the array and get the values.
One thing that is confusing me is that when you look at the attached screenshot it looks like the result is an object containing an array of objects. And I think that is why I can't loop through it, since technically its an object and not an array.
I tried something like this (retrievedData is the array of objects) but then I get the error "Cannot read property 'forEach' of undefined":
retrievedData: any;
this.retrievedData.array.forEach(element => {
this.productNames.push(element.ProductName);
});
I use a service to retrieve the data:
onSubmit(form:NgForm)
{
this.service.postReportData().subscribe(
res => {
this.retrievedData = res;
console.log(this.retrievedData);
},
err => {
console.log(err);
}
);
}
Any help would be appreciated!
so what you receive is the an object containing array of objects
your res should be of type
type Item = {
ProductName: string;
AverageQuantityOrdered: number;
ProductOrders: unknown[];
}
type RetrievedData = {
reportData: Item[];
}
so you can set RetrievedData as a type to your retrievedData variable
retrievedData: RetrievedData
instead of any
and then retrievedData.reportData will give you the array.
Thank you everyone for your useful comments!
I got it working with the following code:
onSubmit(form:NgForm)
{
this.service.postReportData().subscribe(
res => {
this.retrievedData = res;
this.retrievedData.reportData.forEach(function (item) {
console.log(item);
console.log(item.ProductName);
console.log(item.AverageQuantityOrdered);
});
},
err => {
console.log(err);
}
);
}
Since reportData contained the array of objects I had to loop through that and not retrievedData.
Once again thanks to everyone, I appreciate the help!
I have two objects of the same interface.
interface SizeFilter {
key: string | undefined
}
const defaultFilter = {
key: undefined,
} as SizeFilter;
I have a function which basically checks if there is a key set in a state and accordingly get filter options:
const { filters } = useSelector(({ sizeCurves }) => sizeCurves);
// Get FilterOptions for filtering on the page.
const getSelectedFilters = () => {
const pageFilter = defaultFilter;
// TODO: add an Object loop to check all properties and define value.
pageFilter.sizeCurveKey = filters.sizeCurveKey ?? undefined ;
return pageFilter;
};
I have to decide whether I should take a filter value from the filters or from a default filter. And for that, I am setting pageFilter in the getSelectedFilters function.
When I try to iterate over defaultFilter using Object.Keys().map,
Object.keys(defaultFilter).map(key =>{
const pageFilter[item] = (defaultFilter[ key ])? defaultFilter[ key ] : undefined;
});
My editor throws warning on defaultFilter[ key ]
Element implicitly has an 'any' type because expression of type
'string' can't be used to index type 'SizeFilter'. No index
signature with a parameter of type 'string' was found on type
'SizeFilter'.
I am not sure how to fix this issue.
The reason for this behavior is to prevent runtime errors that come from indexing an object by unknown keys. To prevent that you need to check first if the key is valid for defaultFilter. You can achieve this with a small helper function:
function hasKey<O>(obj: O, key: keyof any): key is keyof O { return key in obj }
You can use the function like this afterwards:
if (hasKey(defaultFilter, key)) {
defaultFilter[key]
}
I have some objects containing DB documents that I constantly need to convert to arrays.
Example:
const MY_OBJECT = {
docId_1: {...doc1},
docId_2: {...doc2},
docId_3: {...doc3},
// AND SO ON
}
I need to convert it to an array like this:
const MY_ARRAY_FROM_OBJECT = [
{...doc1},
{...doc2},
{...doc3},
// AND SO ON...
]
And this is code I use to do the conversion:
function buildArrayFromObject(obj) {
const keys = Object.keys(obj);
const arr = [];
for (const key of keys) {
arr.push(obj[key]);
}
return arr;
}
And I need to type that function so I can use it with objects that has types like these:
interface BLOGPOSTS_ALL {
[key: string]: BLOGPOST
}
interface PRODUCTS_ALL {
[key: string]: PRODUCT
}
So when I call them with each different object, I want Typescript to know what the return array type will be.
For example:
const BLOGPOSTS_ALL_ARRAY = buildArrayFromObject(BLOGPOSTS_ALL); // SHOULD BE "BLOGPOST[]"
const PRODUCTS_ALL_ARRAY = buildArrayFromObject(PRODUCTS_ALL); // SHOULD BE "PRODUCT[]"
Here is what I've tried:
function buildArrayFromObject<T, K extends keyof T>(obj: T): T[K][] {
const keys = Object.keys(obj);
const arr = [];
for (const key of keys) {
arr.push(obj[key]);
}
return arr;
}
But I'm getting this error:
And the returning type of the function is being evaluated by Typescript as type never[]
What you could do is the following:
Create an abstraction type to define what your database handles look like:
// Create an Entity type to define how your database objects look like
type Entities<T> = { [key: string]: T | undefined }
Therefore you can express your products and blogposts like this:
type BLOGPOSTS = Entities<BLOGPOST>;
type PRODUCTS = Entities<PRODUCT>;
In order to convert them in type-safe manner into an array you can use the Object.values method provided by the JavaScript API - for more information please see MDN
It's possible to replace the method buildArrayFromObject by something like this:
const isNil = <T>(a: T | null | undefined): a is null | undefined => a === null || a === undefined;
const isAssigned = <T>(a: T | null | undefined): a is T => !isNil(a);
const entitiesToArray = <T>(entity: Entities<T>): T[] => Object.values(entity).filter(isAssigned);
This method uses the Object.values method to convert the object into an array. Afterwards it get's filtered to contain an array without undefined values. Therefore I made use of two helper methods isAssigned and isNil. Please see this CodeSandbox for an example: Code SandBox
Based on your concern, that the Object.value method is not supported by IE11, you can add a polyfill for that one. Either by pasting a polyfill in or by adding it using npm to your project.
Alternatively you can replace this code by the following answer Use Object.keys to mimick Object.values
I'm connecting to Firebase and retrieving data from a given location, what I'm trying to do is loop through the snapshot.val() build and Array, return the Array and then loop through it on the component.html and build a Dropdown.
At present I'm having difficulty figuring out the syntax for such service method, this is my current code. - This is called from app.component.ts within ngOnInit()
getExerciseOptions(): Array<Exercise> {
const items: Exercise[] = [];
this.exerciseDbRef2.on("value", (snapshot) => {
snapshot.forEach((snap) => {
items.push({
id: snap.key,
description: snap.val().description
});
return false;
});
});
}
So this.exerciseDbRef2 points to the table within Firebase as shown here:
private exerciseDbRef2 = this.fire.database.ref('/exercise_description');
The error I'm currently receiving is
A function whose declared type is neither 'void' nor 'any' must return a value.
Which I understand, so when I change return false to return items the new error is:
Argument of type '(snap: DataSnapshot) => Exercise[]' is not assignable to parameter of type '(a: DataSnapshot) => boolean'.
Type 'Exercise[]' is not assignable to type 'boolean'.
I have looked at using child_added but from my understanding this will be called every time a new child has been added into that location, which is not what I'm looking for. The children in this location will not change nor will any be added. - Maybe I've misinterpreted 'child_added' ?
I'm rather new to Firebase so I'm at the beginning on the learning curve so please bare with, I would also like to mention if the way I'm currently doing this is not correct then please bring it to my attention.
So for clarification : Connect to Firebase, retrieve all children from given location i.e exercise_description table, loop through the snapshot, build an Array and return it.
Then on the component loop through the Array and build the Dropdown.
Can someone please explain how I go about returning the Array based off the snapshot.val() ?
You cannot return an array from getExerciseOptions, as the value event is asynchronous.
However, you could return a promise:
getExerciseOptions(): Promise<Array<Exercise>> {
return this.exerciseDbRef2
.once("value")
.then(snapshot => {
const exercises: Exercise[] = [];
snapshot.forEach(snap => {
exercises.push({
id: snap.key,
description: snap.val().description
});
return false;
});
return exercises;
});
}
You would then call it like this:
getExerciseOptions().then(exercises => console.log(exercises));
If you are unfamiliar with promises, you might want to read JavaScript Promises: an Introduction.