I have created one service to load all the files data:
readonly file= new BehaviorSubject(null);
readonly file$ = this.pnlNumbers.asObservable();
getFile(filename: string) {
this.file.next(null);
this.subscriptions.push(this.http.get(`/file/${filename}).subscribe(data => {
this.file.next(data);
}, error => {
this.file.next(error);
}));
}
This will return an single object with file information ,eg:
{
id:0001,
name: 'test_file.txt',
...
}
I have created ab function to store all the result data that comes from the getFile service:
getAllFiles(): any {
let filesList= [];
this.activeFilesList.forEach(fileName => {
this.fileService.getFile(fileName);
});
this.fileService.file$.subscribe((data) => {
if (data) {
fileList?.push(data);
}
});
return filesList;
}
I don't know why , but "typeOf this.getAllFiles()" will return an Object instead of Array, for that reason I cant access the indices of filesList[], eg on chrome console:
[]
1:{id:0001,name:'test.file.text'}
2:{id:0002,name:'test.file2.text'}
3:{id:0003,name:'test.file3.text'}
the filesList.lenght = 0
I need that this filesList[] be a type of Array instead of an Object.
Few things here:
Firstly, a common JavaScript gotcha is the return type of an array is in fact 'object'
typeof [] // 'object'
typeof ['any array contents'] // 'object'
i.e. typeof is an ineffective heuristic for determining whether the return type of this function is an array. Use Array.isArray instead:
Array.isArray(['any array contents']) // true
Array.isArray({ someKey: 'someValue' }) // false
Secondly, am I safe to assume that this line
readonly file$ = this.pnlNumbers.asObservable();
Should instead be
readonly file$ = this.file.asObservable();
otherwise the rest of the code does not really have much relevance, as this.pnlNumbers is not referenced anywhere else
Thirdly, it appears that you are trying to combine the results of multiple asynchronous streams (in this case http calls) into a single array to be returned from getAllFiles(). As these are asynchronous, they by nature take some time to return their data. While this data is being returned, the rest of your synchronous function code will run, and that means your return statement will be hit before the http calls have returned their data.
In its current state, getAllFiles() is simply returning the value of filesList before any of the http calls have returned their values, i.e. the default value it was assigned, [].
What you will need to do instead is to
Use an RxJs operator to combine those independent http streams into one stream
Subscribe to this combined stream and handle the combination of values as is appropriate for the operator being used
An example implementation using forkJoin is here, but depending on your use case, other joining operators like concat, mergeMap, combineLatest etc may be preferable:
type FileType = { id: number; name: string }
getAllFiles$(): Observable<FileType[]> {
const filesListObservables: Observable<FileType>[] =
this.activeFilesList
.map(
(fileName: string) => this.fileService.getFile(fileName)
);
const filesList$: Observable<FileType[]> = forkJoin(filesListObservables);
return filesList$;
}
getAllFiles(): void {
this.getAllFiles$()
.subscribe(
(allFiles: FileType[]) => {
console.log(allFiles) // [{id:0001,name:'test.file.text'},...]
}
)
}
Related
I have a function _populateData that creates a new list of properties from another list of properties.
There is an observable getOwnerForProperty that returns the owner's value.
//Get single owner observable
public getOwnerForProperty(prop: any){
return this._manageOwnerService._getOwnersOfProperty(prop).pipe(map(o => o[0]))
How can I call the observable from within the .map() function to obtain the observable's value and attach it to the new object as seen below?
In my opinion, it would not be a good idea to subscribe getOwnerForProperty function in the .map(). Please advise on the best way to approach this following best practices.
/**
* Returns the active properties data.
*
* #param props - The property list.
* #returns An array of properties
*/
private _populateData(props: Property[]) {
return
const populated = props
.filter(prop => !prop.isArchived)
.map((p: Property) => {
// refactoring here
this.getOwnerForProperty(p).pipe(
map((owner: Owner) => {
const obj = {
propertyName: p.info.name.toUpperCase(),
owner: owner.name,
createdOn: p.createdOn ? __FormatDateFromStorage(p.createdOn) : ''
}
return obj;
})
)
}
)
return populated;
}
}
It's not entirely clear from your question what exactly you are trying to achieve, but here is my solution, so you will hopefully get the idea:
filter for the properties you want to "enrich".
use forkJoin to create an array of observables and wait for all of them to complete.
map each property to the observable you want to wait for.
map the result of the observable to the initial property and enrich it with the owner object.
forkJoin returns an observable which will basically emit a single array of enriched objects and complete. If you wish to await this, you can wrap this in lastValueFrom operator, like await lastValueFrom(forkJoin(...))
function _populateData(props: Property[]) {
const propertiesToPopulate = props.filter((prop) => !prop.isArchived);
forkJoin(
propertiesToPopulate.map((p: Property) => {
return getOwnerForProperty(p).pipe(
map((owner) => ({
...p,
owner,
}))
);
})
);
}
I have a validation process which validates data in the table row by row. Because each row validation uses a shared resource, access to it must be serialized.
public validate():Observable<boolean>{
const rowValidations:Observable<boolean>[] = dataRows.map(row=>this.validateSingleRow(row);
return forkJoin(...rowValidations).pipe(
map(results=>results.every(r=>r))
)
}
If I understand correctly, forkJoin will not wait for each observable to finish before subscribing to the next one like concat would so that will probably fail. concat on the other hand serializes all the observables into a single stream.
How can I get a subscription order like with concat but have an array of results of each observable like with forkJoin effectively synchronizing execution of each inner observable (like Javas synchronzied validateSingleRow)?
Actually, if you know that each this.validateSingleRow(row) will always emit only once you can use toArray():
concat(...rowValidations).pipe(
toArray(),
);
concat will guarantee correct order and toArray() will collect all emissions into a single array and reemit it after the source Observable completes.
Otherwise, if validateSingleRow might emit multiple times and you always want only its last value you could use scan:
const indexedRowValidations = rowValidations.map((o, index) => o.pipe(
map(result => [index, result]),
));
concat(...indexedRowValidations ).pipe(
scan((acc, [index, result]) => {
acc[index] = result;
return acc;
}, {}),
takeLast(1),
);
(I didn't test it but I believe you get the idea :)).
Would something like this do the trick for you?
class SomeClass {
dataRows = [1, 2, 3];
public validate(): Observable<boolean[]> {
return this.resolveSequentially(this.dataRows);
}
private validateSequentially<T>([cur, ...obs]: T[]): Observable<boolean[]> {
return cur
? this.validateSingleRow(cur).pipe(
switchMap((x) =>
this.validateSequentially(obs).pipe(map((arr) => [x, ...arr]))
)
)
: of([]);
}
// Mock
private validateSingleRow(cur: any) {
console.log(`Validating ${cur}...`);
return of(Math.floor(Math.random() * 2) === 1).pipe(
delay(1000),
tap((x) => console.log(`Result: ${x}`))
);
}
}
const obj = new SomeClass();
obj.validate().subscribe(console.log);
StackBlitz demo
Solution that meets my requirement is simpler than one might think. I have used concat with toArray() like this
const rowValidations:Observable<boolean>[] = dataRows.map(row=>defer(()=>this.validateSingleRow(row));
return concat(...rowValidations).pipe(
toArray(),
map(results=>results.every(r=>r))
)
so validateSingleRow is executed one by one and toArray transforms boolean stream into array of boolean.
Hello so I am creating a filter search and I 'm trying to collect all the key (tags) that the user press, inside an array, however every time that a new value is push it does override the entire array. So I tried a couple of things, like spread syntax, concat, etc. But with no luck.
So my action looks like this:
const setCurrentFilters = async (context, payload) => {
if (payload) {
context.commit('setCurrentFilter');
}
}
My state
state:{
filters: JSON.parse(sessionStorage.getItem('currentFilters') || '[]'),
}
The mutation
setCurrentFilter(state, payload) {
state.filters.push(payload);
sessionStorage.setItem('currentFilters', JSON.stringify(payload));
}
And my getter
currentFilters(state) {
return state.filters;
},
Thank you in advance for any help : )
This is simply because you set const filters = []; which means that the next condition if (filters.length) will always return false (as you just created this array) and therefore the else statement will execute.
in the else statement you basically push the new payload to the empty array you just initialized - which makes your array always hold only the new value
i believe that you just need to remove the const filters = []; line, and access the filters property that exists in your state
In this code:
insert1(data: iFlower) {
...
return data;
}
insert2(data: iFlower[]) {
...
return data;
}
public insert (data: iFlower | iFlower[]) {
if (data as iFlower) {
return this.insert1(data as iFlower);
}
else if (data as iFlower[]) {
return this.insert2(data as iFlower[]);
}
when I call the insert() function, depending on the type the variable data has, I want to call one method or the other. But in this case, if data is an array of objects (iFlower[] type), it still enters in the insert1 function... Or better said, even if data is of iFlower or iFlower[] type, it will call the same method.
How can I solve this? :(
as is a typescript operator that can be used to typecast types. It does not make any checks at runtime, therefore it is useless. Your code basically does this at runtime:
if(data /*as iFlower*/) // data is truthy, enters branch
You could use Array.isArray to determine wether the passed value is an array:
if (Array.isArray(data)) // iFlower[]
So I've got a service call going out to the back end and saving an object, the call has a promise set up to return a number.
That call looks like this
saveTcTemplate(item: ITermsConditionsTemplate): ng.IPromise<number> {
item.modifiedDate = new Date();
return this.$http.post(this.api + '/SaveTcTemplate', item)
.then(this.returnData);
}
returnData = (response: any) => {
return response.data;
};
This is when creating a new object, all the fields are set to their needed values, passed to save in, then called to be displayed.
This is the get function used to pull the object after it's been saved.
getTermsConditions(id: number): ng.IPromise<ITermsConditionsTemplate> {
return this.$http.get(this.api + '/GetTermsConditions',
{
params: {
id: id
}
}).then(this.returnData);
}
This is the initial construction, saving, and getting, of the object
this.newTemplate.copyId = 0;
this.newTemplate.id = 0;
this.newTemplate.isLibrary = true;
this.newTemplate.studyFacilityScheduleId = this.studyFacilityScheduleId;
this.studyTermsConditionsService.saveTcTemplate(this.newTemplate)
.then(this.studyTermsConditionsService.getTermsConditions)
.then(this.setTemplateData);
When set up like this I can successfully save a new item, and have its id (the ng.IPromise part) returned to me, and passed into my Get service call.
The problem is, when set up this way, this.$http.get comes back undefined. From what I think I understand from other stack overflow issues that are similar, it is happening because I called the function without explicitly passing anything into it's parameter when I say
.then(this.studyTermsConditionsService.getTermsConditions)
To test this I also set up the save and get like this
var data: any;
data = this.studyTermsConditionsService.saveTcTemplate(this.newTemplate);
this.studyTermsConditionsService.getTermsConditions(data)
.then(this.setTemplateData);
And that worked, sort of. $http.Get was recognized, and could be used, but the problem with this setup was; due to the asynchronous nature of ng.IPromise the data isn't being sent to my Get function as the promised number value. It's being sent as type $$promise which results in the parameter in my get function being NaN.
So one way I'm passing a usable value, i.e. 32, but I'm not explicitly passing this value, so $http.get is undefined.
The other way, I am explicitly passing a parameter, so $http.get is recognized, and usable, but the explicitly called parameter is of type $$promise, not type number, It seems, unfortunately, the ng.IPromise< number > is not resolved by time I call my Get function.
How do I proceed from here??
I guess you are messed up with this. As you directly pass the function reference as parameter it lost with the context. You should call them explicitly like below.
Code
this.newTemplate.copyId = 0;
this.newTemplate.id = 0;
this.newTemplate.isLibrary = true;
this.newTemplate.studyFacilityScheduleId = this.studyFacilityScheduleId;
this.studyTermsConditionsService.saveTcTemplate((response) => this.newTemplate(response))
.then((response) => this.studyTermsConditionsService.getTermsConditions(response))
.then((response) => this.setTemplateData(response));
Otherwise you could make your function to use of arrow function which would help to available this context inside function.
saveTcTemplate(item: ITermsConditionsTemplate): ng.IPromise<number> { .. }
should be
saveTcTemplate = (item: ITermsConditionsTemplate): ng.IPromise<number> => { .. }
And
getTermsConditions(id: number): ng.IPromise<ITermsConditionsTemplate> { .. }
should be
getTermsConditions = (id: number): ng.IPromise<ITermsConditionsTemplate> => { .. }