Hi i am getting an error in my code . I have an angular 5 formGroup and i am trying to use the pipe operation and switchMap inside.
However it give me an error. The following is my code snippet.
this.formGroup.valueChanges.pipe(
switchMap(
(formValue) => {
console.log(formValue);
}
)
).subscribe();
the error i is as below
Argument of type '(formValue: any) => void' is not assignable to parameter of type '(value: any, index: number) => ObservableInput<{}>'.
Type 'void' is not assignable to type 'ObservableInput<{}>'.ts(2345)
really appreciate any help
thank you
You have to return an observable in the switchMap. switchMap switches to a new observable and you can do it conditionally based on the previous value.
If you want to just console.log, I would use tap.
this.formGroup.valueChanges.pipe(
tap(
(formValue) => {
console.log(formValue);
}
),
).subscribe();
====================== Edit ==========================
I assume this.generatePerformanceGraph(formValue); is a regular function and it doesn't return an observable, then you can do it in the subscribe.
this.formGroup.valueChanges.pipe(
tap( // you can remove this tap, it is just for logging
(formValue) => {
console.log(formValue);
}
),
).subscribe(formValue => {
this.generatePerformanceGraph(formValue);
});
Related
Is there an alternative to doing .subscribe() in map?
return this.http.patch<any>(URL, { test: true }).pipe(
tap(_ => this.nextSomething$.next({})),
filter(_ => this.filter),
map(resp => {
this.someObservable({ message: 'do' }).subscribe()
return resp
})
)
I tried doing switchMap and returning the previous response but my observable does not complete.
switchMap(prevResp =>
this.someObservable({ message: 'do' }) }).pipe(map( _ => prevResp))
)
Thank you
If your observable is not completing when you switch to using the flattening operator then that means the target observable in that operator isn't completing. So you might want to check what's going on in someObservable that causes it to never complete. It is likey that behavior isn't desirable.
If you know that someObservable will emit at least once, then you can add the first operator to the inner pipe method:
switchMap(prevResp =>
this.someObservable({ message: 'do' }) }).pipe(
first(),
map( _ => prevResp)
)
)
If you don't care what someObservable does - you don't want to wait for it just want it to execute, then wrap the observable in firstValueFrom. It converts an observable into a promise that emits the first result. This code smells, but it should do the trick.
this.http.patch<any>(URL, { test: true }).pipe(
tap(() => this.nextSomething$.next({})),
filter(() => this.filter),
tap(() => firstValueFrom(this.someObservable({ message: 'do' })))
)
Maybe, you don't care to wait for this observable to emit, but you're still looking for an orderly execution of someObservable. In that case you can use a subject that emits every time you want it called, and use concatMap to ensure the execution is performed in an orderly fashion.
private readonly someObservableRequest = new Subject<string>();
constructor() {
this.someObservableRequest.pipe(
concatMap((message) => this.someObservable({ message }))
).subscribe(); // be kind, please unsubscribe
}
someMethod() {
return this.http.patch<any>(URL, { test: true }).pipe(
tap(_ => this.nextSomething$.next({})),
filter(_ => this.filter),
tap(() => this.someObservableRequest('do'))
);
}
If I understand your point right, you have to execute consecutively 2 Observables.
In this case you need yo use one so called "higher order" operators, i.e. either concatMap or switchMap (there are other "higher order" operators but I feel they do not apply to your case).
The code then would look like this
myNewOservable this.http.patch<any>(URL, { test: true }).pipe(
tap(_ => this.nextSomething$.next({})),
filter(_ => this.filter),
// an higher order operator returns an Observable
concatMap(resp => {
return this.someObservable({ message: 'do' })
})
)
return myNewOservable
Now you can subscribe to myNewOservable.
In my example I have used concatMap which ensures that a value notified by the upstream Observable is processed through the downstream pipeline before processing the next value from upstream.
I could have used also switchMap, which has a slightly different behavior: as soon as a new value is notified by the upstream Observable, any downstream processing is terminated.
In this case, given that http emits only one value and then completes the 2 operators act the same.
I have the following code:
this.userService.get().pipe(
catchError(() => EMPTY) // Do something here
).subscribe(() => {}) // And here
I want to do some stuff no matter there was an error or not. Just like finalize but for not completed observable. I tried to use tap, but it works only if there's no error. I don't want to duplicate my code and add it to the catchError. What should I do?
there's no other way to do it with catchError without touching it in the current example . Because everything after catchError won't get any notification, it's like an observable that never emits.
you can use 2nd argument of tap, it is triggered on error, the same as in subscribe.
this.userService.get().pipe(
tap(
() => console.log('emit'),
() => console.log('error'),
() => console.log('complete'),
),
// catchError(() => EMPTY), // closing the stream and hiding the error.
// repeat(), // in case if you want to resubscribe once the stream has been closed.
).subscribe(
() => console.log('emit'),
() => console.log('error'),
() => console.log('complete'),
);
Emit a default value in catchError.
this.userService.get().pipe(
catchError(() => of(null)) // catch error and emit null instead (or some other value)
).subscribe(value => {
// do something here,
// value will be null when this.userService.get() errors */
})
You should consider moving your error handling with catchError into the Service.
I want to understand what happens when I directly pipe to an action$ and try to use forkJoin operator
const action1 = { type: "ACTION_1" };
const action2 = { type: "ACTION_2" };
in a switchMap forkJoin works fine.
export const testForkJoinSwitchMap: Epic<Action> = action$ =>
action$.pipe(
ofType(action1),
switchMap(() =>
forkJoin(
from(fetch("https://api.github.com/users")).pipe(
map((res: any) => {
return res;
})
)
)
),
map((data: any) => {
// do something with data
return action2;
})
);
If I took it out of the switchMap then:
export const testForkJoin: Epic<Action> = action$ =>
action$.pipe(
ofType(action1),
forkJoin(from(fetch("https://api.github.com/users"))).pipe(
map((response: any) => {
return action2;
})
)
);
I get the typing error:
Argument of type 'Observable<{ type: string; }>' is not assignable to parameter of type 'OperatorFunction<{}, Action<any>>'.
I want to know why it does not compile? and the reason of the type mismatch, what makes the epics without the forkJoin invalid in this case?
edit: I know that forkJoin is not meaningful for a single observable, but I put 1 to keep the example smaller
observable.pipe() only takes operators inside it.
forkJoin is an operator which returns an observable that's why you get that error. Argument of type 'Observable' is not assignable to parameter of type 'OperatorFunction' i.e forkJoin which returns an observable is not assignable to type of operator function.
switchMap, on the other hand, is an operator which returns an OperatorFunction and operates on an observable that's why your first approach worked. switchMap(() => someObservable) and someObservable in this case is forkJoin()
It's also evident from your imports. You might have imported forkJoin from rxjs library whereas switchMap from rxjs/operators library.
I call backend that respond with:
[
"https://some-url.com/someData1.json",
"https://some-url.com/someData2.json"
]
Each JSON can have following schema:
{
"isValid": boolean,
"data": string
}
I want to get array with all data, that have isValid is set to true
backend.get(url)
.pipe(
mergeMap((urls: []) =>
urls.map((url: string) =>
backend.get(url)
.pipe(
filter(response => response.isValid),
map(response => response.data)
)
)
),
combineAll()
)
When both .json have "isValid" set to true, I get array with both data.
But when one of them has "isValid" set to false observable never completes.
I could use mergeAll instead of combineAll, but then I receive stream of single data not collection of all data.
Is there any better way to filter out observable?
As you said, the inner observable never emits, because filter does not forward the only value that is ever emitted by the backend.get observable. In that case, the operator subscribing on that observable - in your case combineAll - will also never receive any value and cannot ever emit itself.
What I would do is just move the filtering and mapping to combineAll by providing a project function, like that:
backend.get(url)
.pipe(
mergeMap((urls: string[]) =>
urls.map((url: string) => backend.get(url))
),
combineAll(responses =>
responses
.filter(response => response.isValid)
.map(response => response.data)
)
)
See if that works for you ;)
import { forkJoin, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
interface IRes {
isValid: boolean;
data: string;
}
interface IResValid {
isValid: true;
data: string;
}
function isValid(data: IRes): data is IResValid {
return data.isValid;
}
const res1$: Observable<IRes> = backend.get(url1);
const res2$: Observable<IRes> = backend.get(url2);
// When all observables complete, emit the last emitted value from each.
forkJoin([res1$, res2$])
.pipe(map((results: IRes[]) => results.filter(isValid)))
.subscribe((results: IResValid[]) => console.log(results));
I want to resolve an observable but I don't want the return value to replace the previous value in the pipe. Is there any asynchronous tap()? I need an operator like a switchMap but I want to ignore the return.
of(1).pipe(switchMap(() => of(2))).subscribe(console.log); // expected: 1
I could create a custom operator but sure there's something built-in in rxjs.
I ended up with this custom operator. It is like tap but resolves observables (and should be updated to also support promises).
export function switchTap<T, R>(next: (x: T) => Observable<R>): MonoTypeOperatorFunction<T>;
export function switchTap<R>(observable: Observable<R>): MonoTypeOperatorFunction<R>;
export function switchTap<T, R>(
arg: Observable<T> | ((x: T) => Observable<R>)
): MonoTypeOperatorFunction<T> {
const next: (x: any) => Observable<T | R> =
typeof arg === 'function' ? arg : (x: any): Observable<T> => arg;
return switchMap<T, T>(value => next(value).pipe(ignoreElements(), concat(of(value))));
}
Usage:
of(1).pipe(switchTap(of(2))).subscribe(console.log) // 1
or with a function:
of(1)
.pipe(
switchTap(value => {
console.log(value); // value: 1
return of(value + 1);
})
)
.subscribe(console.log); // 1
If you just want to simply ignore the values of the subscribe, then just don't pass in any arguments in the subscribe callback:
of(1).pipe(switchMap(() => of(2))).subscribe(()=>{
console.log('no arguments')
});
If you however want to retain the values of the first observable, things can get tricky. One way is to use Subject to retain the value:
//create a BehaviorSubject
var cache = new BehaviorSubject<any>(0);
of(1).pipe(switchMap((first) => {
cache.next(first);
return of(2);
})).subscribe(() => {
console.log(cache.value) //gives 1
});
Or you can use .map() to alter the values. This is kind of hacky and the code is harder to maintain:
of(1).pipe(switchMap((first) => {
return of(2).map(() => first);
})).subscribe((second) => {
console.log(second) //gives 1 because the values was mapped
});
I do it like so
of(2).pipe(
switchMap( num => this.doSmtg(num), num => num)
).subscribe(num => console.log(num)); // 2
Second param of switchmap receives two value the one passed to this.doSmtg and the value returned by doSmtg(num)'s observable.
For anyone new having the same problem I would advise using the resultSelector parameter supported by switchMap and other RxJS mapping operators.
Example:
switchMap(1 => of(2), (one, two) => one)
For further reading: https://www.learnrxjs.io/operators/transformation/mergemap.html
I think you could use delayWhen operator to achieve a similar functionality.
of(1)
.pipe(
delayWhen(value => {
console.log(value); // value: 1
return of(value + 1);
})
).subscribe(console.log); // 1