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,
}))
);
})
);
}
Related
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'},...]
}
)
}
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.
In my app i have something like:
this._personService.getName(id)
.concat(this._documentService.getDocument())
.subscribe((response) => {
console.log(response)
this.showForm()
});
//Output:
// [getnameResult]
// [getDocumentResult]
// I want:
// [getnameResult][getDocumentResult]
Then i get two separated results, first of the _personService and then the _documentService. How can I wait for both results before call this.showForm() to finish an then manipulate the results of each one.
Last Update: Mar, 2022.
RxJS v7: combineLatestWith
From reactiveX documentation:
Whenever any input Observable emits a value, it computes a formula using the latest values from all the inputs, then emits the output of that formula.
// Observables to combine
const name$ = this._personService.getName(id);
const document$ = this._documentService.getDocument();
name$.pipe(
combineLatestWith($document)
)
.subscribe(([name, document]) => {
this.name = name;
this.document = pair.document;
this.showForm();
})
(Deprecated) RxJS v6 combineLatest()
From reactiveX documentation:
Whenever any input Observable emits a value, it computes a formula using the latest values from all the inputs, then emits the output of that formula.
(Update: Feb, 2021):
// Deprecated (RxJS v6)
// Observables to combine
const name$ = this._personService.getName(id);
const document$ = this._documentService.getDocument();
name$.combineLatest(document$, (name, document) => {name, document})
.subscribe(pair => {
this.name = pair.name;
this.document = pair.document;
this.showForm();
})
(alternate syntax): combineLatest(observables)
// Deprecated (RxJS v6)
// Observables to combine
const name$ = this._personService.getName(id);
const document$ = this._documentService.getDocument();
combineLatest(name$, document$, (name, document) => ({name, document}))
.subscribe(pair => {
this.name = pair.name;
this.document = pair.document;
this.showForm();
})
zip vs combineLatest
(Update: Oct, 2018)
I previously suggested the use of zip method. However, for some use cases, combineLatest has a few advantages over zip. So it is important to understand the differences.
CombineLatest emits the latest emitted values from observables. While zip method emits the emitted items in sequence order.
For example if observable #1 emits its 3rd item and observable #2 has emitted its 5th item. The result using zip method will be the 3rd emitted values of both observables.
In this situation the result using combineLatest will be the 5th and 3rd. which feels more natural.
Observable.zip(observables)
(Original answer: Jul, 2017) Observable.zip method is explained in reactiveX documentation:
Combines multiple Observables to create an Observable whose values are calculated from the values, in order, of each of its input Observables.
// Observables to combine
const name$ = this._personService.getName(id);
const document$ = this._documentService.getDocument();
Observable
.zip(name$, document$, (name: string, document: string) => ({name, document}))
.subscribe(pair => {
this.name = pair.name;
this.document = pair.document;
this.showForm();
})
a side note (applies for both methods)
The last parameter, where we have provided a function, (name: string, document: string) => ({name, document}) is optional. You can skip it, or do more complex operations:
If the latest parameter is a function, this function is used to compute the created value from the input values. Otherwise, an array of the input values is returned.
So if you skip the last part, you get an array:
// Observables to combine
const name$ = this._personService.getName(id);
const document$ = this._documentService.getDocument();
Observable
.zip(name$, document$)
.subscribe(pair => {
this.name = pair['0'];
this.document = pair['1'];
this.showForm();
})
Use forkJoin() method of observables. Check this link for reference
From the RXJS docs
This operator is best used when you have a group of observables and only care about the final emitted value of each. One common use case for this is if you wish to issue multiple requests on page load (or some other event) and only want to take action when a response has been receieved for all. In this way it is similar to how you might use Promise.all
forkJoin([character, characterHomeworld]).subscribe(results => {
// results[0] is our character
// results[1] is our character homeworld
results[0].homeworld = results[1];
this.loadedCharacter = results[0];
});
Code taken from: https://coryrylan.com/blog/angular-multiple-http-requests-with-rxjs
The RxJS Operators for Dummies: forkJoin, zip, combineLatest, withLatestFrom helped me a lot. As the name states it describes the following combination operators:
ForkJoin
zip
combineLatest
withLatestFrom
Any of them could be the thing you are looking for, depends on the case. Check the article for more info.
Improvement of Hamid Asghari answer which use direct arguments decomposition and automatically add types (when you use typescript)
const name$ = this._personService.getName(id);
const document$ = this._documentService.getDocument();
combineLatest([name$, document$]).subscribe(([name, document]) => {
this.name = name;
this.document = document;
this.showForm();
});
BONUS: You can also handle errors using above approach as follows
import { combineLatest, of } from 'rxjs';
//...
const name$ = this._personService.getName(id);
const document$ = this._documentService.getDocument();
combineLatest([
name$.pipe( catchError( () => of(null as string ) ) ),
document$.pipe( catchError( () => of(null as Document) ) ), // 'Document' is arbitrary type
]).subscribe(([name, document]) => {
this.name = name; // or null if error
this.document = document; // or null if error
this.showForm();
});
June 2021
With rxjs 6.6.7
Use combineLatest like this otherwise is deprecated
combineLatest([a$ , b$]).pipe(
map(([a, b]) => ({a, b})) //change to [a , b] if you want an array
)
Also see #nyxz post
zip - the love birds, always work as a team, triggers only when all
observables return new values
combineLatest - the go dutch, start trigger once all observables
return new values, then wait for no man, trigger every time when
either observable return new value.
withLatestFrom - the master slave, master first waits for slave, after
that, action get triggered every time only when master return new
value.
forkJoin - the final destination, trigger once when all observables
have completed.
From : https://scotch.io/tutorials/rxjs-operators-for-dummies-forkjoin-zip-combinelatest-withlatestfrom/amp
Have a look at the 'combineLatest' method, it might be appropriate here.
http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#static-method-combineLatest
const { Observable } = Rx
const name$ = this._personService.getName(id);
const document$ = this._documentService.getDocument();
Observable
.combineLatest(name$, document$, (name, document) => ({ name, document }))
.first() // or not, implementation detail
.subscribe(({ name, document }) => {
// here we have both name and document
this.showForm()
})
For me this sample was best solution.
const source = Observable.interval(500);
const example = source.sample(Observable.interval(2000));
const subscribe = example.subscribe(val => console.log('sample', val));
So.. only when second (example) emit - you will see last emited value of first (source).
In my task, I wait form validation and other DOM event.
You can use 'zip' or 'buffer' like the following.
function getName() {
return Observable.of('some name').delay(100);
}
function getDocument() {
return Observable.of('some document').delay(200);
}
// CASE1 : concurrent requests
Observable.zip(getName(), getDocument(), (name, document) => {
return `${name}-${document}`;
})
.subscribe(value => console.log(`concurrent: ${value}`));
// CASE2 : sequential requests
getName().concat(getDocument())
.bufferCount(2)
.map(values => `${values[0]}-${values[1]}`)
.subscribe(value => console.log(`sequential: ${value}`));
I want to monitor when an object is pushed onto an array using observables. I want to start with an empty array and when a push occurs I want the observable to detect and process it and then wait till the next push. This would be very similar to "fromEvent" where the observable waits for the event. The code below immediately calls completed() because the array is empty, how do I make it wait for a push?
var testArray = [];
test(){
var o = {timestamp: new Date()}
testArray.push(o)
}
var o = Observable
.from(testArray)
.concatMap( x => {
return x;
});
o.subscribe(
x => { console.log("onNext x=",x.timestamp) },
e => console.log('onError:', e),
() => {console.log('onCompleted');} );
Note: The input mechanism does not have to be an array. Any type of message queue object will work for me.
If all you're trying to do is create an Observable that you can 'push' values into, I recommend using an RXJS Subject.
i.e.
const date$ = new Rx.Subject();
date$.next(new Date());
Now you have an Observable stream of Date objects that you can "push" to with the next() method.
If you really need to have an intermediate (non-Observable) data type for your queue, then I recommend using a new ES6 feature, proxies.
const queue = new Proxy([], {
set: function(obj, prop, value) {
if (!isNaN(prop)) {
date$.next(value)
}
obj[prop] = value
return true
},
})
Now you have an array that is proxied so that any time a value is added to it, it will be added to your Observable stream.
You could subclass Array and implement some kind of notification mechanism to tell you when pushes happen (this is really bare bones):
class CustomArray extends Array {
push(e) {
super.push(e)
if (this._listeners) {
this._listeners.forEach(l => l(e))
}
}
addPushListener(listener) {
this._listeners = this._listeners || []
this._listeners.push(listener)
}
removePushListener(listener) {
if (this._listeners) {
const index = this._listeners.indexOf(listener)
if (index >= 0) {
this._listeners.splice(index, 1)
}
}
}
}
Then with a function you could wrap this into an Observable
const observePushes = array => Rx.Observable.fromEventPattern(
array.addPushListener.bind(array),
array.removePushListener.bind(array)
)
Then you would be able to subscribe to changes and unsubscribe whenever you want, like with any other observable.
const arr = new CustomArray()
const pushObservable = observePushes(arr)
const subscription = pushObservable.subscribe(e => console.log(`Added ${e}`))
arr.push(1)
arr.push(2)
arr.push(3)
arr.push("a")
subscription.dispose()
arr.push("b")
Also mind that this Observable never really completes, since at no point in time can you guarantee that nothing more will be added to an array.
A fiddle: http://jsfiddle.net/u08daxdv/1/
How can Observable class be extended by applying built-in RxJS operators to it?
I would like to do something like this:
class TruthyObservable extends Observable {
constructor(subscriber) {
super(subscriber);
return this.filter(x => x);
}
}
class TruthyMappedObservable extends TruthyObservable {
constructor(subscriber) {
super(subscriber);
return this.map(x => `'${x}'`);
}
}
Can this be done without constructor return?
This pretty much depends on what you want to do but let's say you want to make a TruthyObservable that behaves very much like the default Observable.create(...) but passes only even numbers:
import { Observable, Observer, Subscriber, Subject, Subscription } from 'rxjs';
import 'rxjs/add/operator/filter';
class TruthyObservable<T> extends Observable<T> {
constructor(subscribe?: <R>(this: Observable<T>, subscriber: Subscriber<R>) => any) {
if (subscribe) {
let oldSubscribe = subscribe;
subscribe = (obs: Subscriber<any>) => {
obs = this.appendOperators(obs);
return oldSubscribe.call(this, obs);
};
}
super(subscribe);
}
private appendOperators(obs: Subscriber<any>) {
let subject = new Subject();
subject
.filter((val: number) => val % 2 == 0)
.subscribe(obs);
return new Subscriber(subject);
}
}
let o = new TruthyObservable<number>((obs: Observer<number>) => {
obs.next(3);
obs.next(6);
obs.next(7);
obs.next(8);
});
o.subscribe(val => console.log(val));
This prints to console:
6
8
See live demo: https://jsbin.com/recuto/3/edit?js,console
Usually classes inheriting Observable override the _subscribe() method that actually makes the subscription internally but in ours case we want to use the callback where we can emit values by ourselves (since this Observable doesn't emit anything itself). Method _subscribe() is overshadowed by _subscribe property if it exists so we wouldn't be able to append any operators to it if we just overrode this method. That's why I wrap _subscribe in the constructor with another function and then pass all values through a Subject chained with filter() in appendOperators() method. Note that I replaced the original Observer with the Subject at obs = this.appendOperators(obs).
At the end when I call eg. obs.next(3); I'm in fact pushing values to the Subject that filters them and passes them to the original Observer.
I think you can get what you need with custom operator:
Observable.prototype.truthy = function truthy() {
return this.filter(x => x);
}