I'm facing a random behavior with the flatMap operator and I can't find the reason why. Sometimes it triggers, sometimes it doesn't...
Here is the situation:
The user can change the language in my app, so I have a BehaviorSubject on the language (which is triggered by a select list) returned as an observable by its provider. When there's a change I call (via flatMap) a http request to fetch the data in the language selected.
It looks like this:
this.languageProvider.getLang$().flatMap(langCode => {
return this.http.get(`https://SERVER_URL.net/datas?lang=${langCode}`)
.map(data => data.json())
})
.subscribe(
data => {
// do smth
},
err => {
// do smth
}
);
The thing is, when I change the language the http call is most often not triggered.
If I add a simple subscribe it always work...
this.languageProvider.getLang$().subscribe(langCode => {
console.log(langCode);
});
Any idea why I have this issue ?
Here is the languageProvider:
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class LanguageProvider {
private lang$: BehaviorSubject<string>;
constructor() {
this.lang$ = new BehaviorSubject('en');
}
setLang(langCode: string) {
this.lang$.next(langCode);
}
getLang$(): Observable<string> {
return this.lang$.asObservable();
}
}
Thanks a lot
OK, fixed it.
It wasn't random actually...
In my (simplified) snippet I didn't write how I handled the server response. Actually I could receive a 304 STATUS CODE if I tried to fetch datas that have not changed on the server (I'm doing this to avoid downloading server datas each time a user starts the app)
THE THING IS, I had to handle the 304 in the error callback since angular takes everything above 299 as an error.
WHAT I DIDN'T KNOW was that the error callback was killing my observable. So the pseudo "random" behavior was actually:
- If I tried to reload the app without changing the language, then I got a 304, so the observable was killed
- If I tried reload the app after changing the language, then the app would fetch the data for this language and get a 200. So the obserbavle would keep on working
Maybe this will help someone.
Cheers
Related
how to subscribe to response when tap operator is used in the service.
Have anyone know how to resolve this?
edit(status) {
dataObj.val = status;
// call post service with status..
this.service
.update(dataObj)
.pipe(takeUntil(this._ngUnsubscribe$))
.subscribe(() => {
//i would like to wait until response come from backend and then navigate to the page so i get data over there.
if (res.status === 'Success') {
this.router
.navigate(['../../success'], {
relativeTo: this.route,
})
.then(() => {});
} else {
this.location.back();
}
});
}
//akita store service
update(
obj: any,
): Observable < any > {
return this.service.update(obj).pipe(
delay(800),
map((data: RestfulResponse < any > ) => data.data),
tap((data: anny) => {
this.store.update((state) => {
state.updateValue = data; // value is not updating and it is navigating to route
});
}),
);
}
//post service
update(obj){
//post call
}
Is there any way I can use tap and in service side and subscribe on component side?
I know I can use finalize but it is not helping for writing conditions inside.
The tap operator, by design, handles side effects which don't happen within the context of your observable pipeline. This means that your pipeline will never wait for results from the tap itself. I don't recommend using it in this manner. Under most circumstances, I only use tap for debugging.
If you are waiting for a particular state change, you should create a separate observable, selecting from your store, to watch the state for the expected change.
If you want to trigger an additional action when something happens, I recommend using ngrx Effects to achieve this.
Have a look at this post, where I talked about how to implement a similar use case:
https://stackoverflow.com/a/64491398/166850
You should also strive to set up reducers that apply your state changes, rather than updating the store directly.
Consider each of the following as a separate concern that you can implement independently of the others:
When user does an edit, trigger an edit action.
The reducer should update the state based on the edit action (for example, to show that a save is in progress)
When the edit action is triggered, trigger an effect. The app should make an HTTP call to save the change, then trigger a save finished action.
When the save is finished, the router navigation should be triggered.
This separates your code into multiple units which are easy to test and verify independently.
If #1 produces an action which is consumed by your reducer (#2), you can also create an ngrx Effect for #3 which listens for the same action, handles the HTTP call using switchMap, then triggers another action to signal that it's done.
Edit
Here's a simple example. The first time an action called APP_LOADED is triggered (from the AppComponent), this Effect makes an HTTP call to get data from the server, then triggers an action using the response data as the action payload.
The actual HTTP call is delegated to another service, the HttpMyConfigDataService, which simply calls HttpClient and returns an Observable.
#Injectable({
providedIn: 'root'
})
export class LoadMyConfigEffect {
constructor(
private httpMyConfigDataService: HttpMyConfigDataService,
private action$: Actions
) {
}
loadMyConfigData$ = createEffect(() => {
return this.action$.pipe(
filter((action) => action.type === 'APP_LOADED'),
take(1),
switchMap(() => this.httpMyConfigDataService.get().pipe(
map(data => {
return {type: 'MY_CONFIG_DATA_LOADED', payload: data};
}),
catchError(err => {
console.error('Error loading config data.', err);
return of({type: 'CONFIG_LOAD_ERROR', payload: err.message, isError: true);
})
))
);
});
}
I'm writing an integration test for a component that should redirect to a specific path depending on the response from an asynchronous (thunk) redux action.
This is a simplified version of my component:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
redirect: false
}
this.props.dispatch(asyncThunkAction())
.then( () => this.setState({redirec: true}) )
.catch( (err) => console.log('action failed') )
}
...
render() {
if (this.state.redirect) {
return <Redirect to='/whocares' />
}
...
}
}
function mapStateToProps(state) {
return {
...
};
}
export default connect(mapStateToProps)(MyComponent);
I want to write a test that asserts that the component redirected to the expected path.
I am using this technique for inspecting the actual redirection path (It's not perfect but it's not the focus of this question).
The place where I am stuck is the state change in the .then() following the redux/thunk action. Because it's a promise, the redirect happens after my expect statement, so I have not been able to test that.
Here's what my test looks like:
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
test('redirects after thunk action', () => {
const redirectUrl = '/whocares'
const data = {};
jest.mock('../actions');
act(() => {
ReactDOM.render(
<TestRouter
ComponentWithRedirection={<MyComponent store={mockStore(data)} />}
RedirectUrl={redirectUrl}
/>,
container);
});
expect(container.innerHTML).toEqual(
expect.stringContaining(redirectUrl)
)
})
My TestRouter just prints the anticipated redirect URL into the DOM. (Check out the link above for a full explanation of this hack.) So right now instead of hitting the expected route, my test (correctly) identifies the loading screen that appears while the thunk action is in progress.
I think the right way to do this is to mock the response from asyncThunkAction so that it returns a resolved promise with matching data, but so far I have not been able to figure out how to do that. I followed the Jest documentation on manual mocks and created the corresponding mock file:
// __mocks__/actions.js
const asyncThunkAction = function(){
return Promise.resolve({foo: 'bar'});
};
export { asyncThunkAction };
...but my test still "sees" the loading state. I don't even think it's looking at my mocked file/action.
What is the right way to do this?
Here's my "recipe" for how I was able to get this working...
Use testing-library/react...
import { render, fireEvent, waitForElement, act } from '#testing-library/react';
(+1 to #tmahle for this suggestion)
Mock axios (or in my case the API module that wraps it) by creating a "manual mock" which basically entails creating a __mocks__ directory next to the real file containing a file by the same name. Then export an object with a property that replaces the get method (or whichever one your code uses).
//__mocks__/myclient.js
export default {
get: jest.fn(() => Promise.resolve({ data: {} }))
};
Even if you don't call the mocked code in your test, you need to import it in the test file...
import myapi from '../api/myapi';
jest.mock('../api/myai');
You can mock the response from the mocked API call like this:
myapi.get.mockResolvedValueOnce({
data: { foo: "bar" },
});
I'm a little fuzzy on this part...
Even though the mocked API request responds immediately with a resolved promise, you probably need to wait for it to write expects
const { getByText, getByTestId, container } = render(<MyComponent />);
await wait(() => getByText('Some text that appears after the '));
expect(container.innerHTML).toEqual('whatever');
All of this was "out there" in various docs and SO questions... but it took me a long time to cobble it all together. Hopefully this saves you time.
This is a little bit of a sideways answer to your question, admittedly, but I would recommend trying out testing-library and the ideals that it embodies, especially for integration tests.
It is available in both DOM and React flavors, which one to use likely depends on what level of abstraction your redirect is happening at:
https://github.com/testing-library/dom-testing-library
https://github.com/testing-library/react-testing-library
With this paradigm you would not try to assert that the user gets redirected to the correct path, but rather that the correct thing is on the screen after they are redirected. You would also limit your mocking to the absolutely bare necessities (likely nothing or only browser API's that your test environment cannot emulate if you are doing a true integration test).
The overall approach here would probably have you mocking out much less and perhaps rendering a larger portion of the app. A likely-helpful example to draw from can be found here: https://codesandbox.io/s/github/kentcdodds/react-testing-library-examples/tree/master/?fontsize=14&module=%2Fsrc%2F__tests__%2Freact-router.js&previewwindow=tests
Because there's less mocking in this approach, the specifics for how you can accomplish this would likely come from outside the scope of the example you've given, but the above example link should help a lot with getting started.
I'm still in the process of getting comfortable with RxJS, so this is potentially an easy question.
Currently I am attempting to lazily make an XHR request for some data that I only need to fetch once and then cache indefinitely while the page is open, and I think I'm on the right track by trying to leverage an AsyncSubject with the value emitted from Angular's HTTP client. What I have so far basically looks like this:
#Injectable()
class AuthService {
user$ = new BehaviorSubject(null);
// do .switchMap() so we reset when the auth'ed user changes
extraInfo$ = this.user$.switchMap(() => {
return this.http.get('/api/account').share();
});
constructor(http: HttpClient) { }
...
}
This almost works since the request isn't made until something subscribes to extraInfo$, and .share() should prevent additional requests being made when I have more than 1 observer on it.
However, if I unsubscribe to it and extraInfo$ becomes cold (since there are 0 subscribers), subscribing to it again causes an additional request to be made again.
Right now I'm tempted to override the ._subscribe() property on an AsyncSubject so that I can run the request when it gets its first observer, but that feels a bit too hackish.
If you want to perform a request and then cache the result for this on any subsequent subscription, you can do this a lot easier:
#Injectable()
class AuthService {
user$ = new BehaviorSubject(null);
extraInfo$ = this.user$.switchMap(() => {
return this.http.get('/api/account').shareReplay(1);
});
constructor(http: HttpClient) { }
...
}
By using shareReplay(1) you are fixing the problem you had with the share operator. They are both multicasting operators but with different properties. You want the operator to be repeatable and not retryable (checkout this article I wrote on the subject to help you http://blog.kwintenp.com/multicasting-operators-in-rxjs/ understanding what I mean).
Just remember, If you want to cache a certain observables result indefinitely, shareReplay is the one you need.
I came up with something eventually, the following works for what I need:
const lastFetchedInfo = new WeakMap();
#Injectable()
class AuthService {
user$ = new BehaviorSubject(null);
// do .switchMap() so we reset when the auth'ed user changes
extraInfo$ = this.user$.switchMap(user => !user ? Observable.empty() : new Observable((observer) => {
if (!lastFetchedInfo.has(user)) {
const subject = new BehaviorSubject(null);
lastFetchedInfo.set(user, subject.filter(o => o !== null));
this.http.get('/api/account').subscribe(info => subject.next(info));
}
const subscription = lastFetchedInfo.get(user);
return () => subscription.unsubscribe();
}));
constructor(http: HttpClient) { }
...
}
I decided to use a BehaviorSubject instead of AsyncSubjectsince I can expand to set intervals on refreshing the data from here if I need to.
I'm building upon the experience with a previous large scale angular 2 app. I've been really careful to keep the rendering cycles under control. Keeping a log is how I investigate what happens.
Controller
public debugTemplate() {
DEBUG.render && debug('Render FooCmp');
}
Template
{{ debugTemplate() }}
I've been using only ngrx state store subscriptions in the smart components. This way I can avoid completely the need of using ActivatedRouteSnapshot or RouteReuseStrategy
Guard
import { Injectable } from '#angular/core';
import { CanActivate, ActivatedRouteSnapshot,
RouterStateSnapshot } from '#angular/router';
import { Observable } from 'rxjs/Observable';
import { environment } from '../../../environments/environment';
import { DEBUG } from '../../../config/config';
import * as Debug from 'debug';
// Interfaces
import { Foo } from '../interfaces/foo';
// Services
import { BarService } from '../services/bar.service';
import { FooService } from '../services/foo.service';
// Debug
const debugOff = (...any) => { }, debug = Debug('app:FooPageGuard');
#Injectable()
export class FooPageGuard implements CanActivate {
constructor(
private _barService: BarService,
private _fooService: FooService
) {
DEBUG.constr && debug('Construct FooPageGuard');
}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> {
DEBUG.guard && debug('Guard FooPageGuard');
return this._fooService.foo$().switchMap(
foo => this._barService.getBar(foo)
)
.map(data => { if (data) {return true} })
.first() // Take first and enable the route
.do(foo => DEBUG.guard && debug('Guard OK FooPageGuard:', foo))
}
}
Smart component with ChangeDetectionStrategy.OnPush
// Ngrx state store subscription
this._fooService.foo$().subscribe(exp => this.foo = exp);
Using ng serve --prod --aot I've been able to see that the rendering of parent components is executed onlz once if I use guards instead of resolvers. Using resolvers leads to multiple renderings to get the same initial state. In the Angular documentation guards are recommended for login and resolvers for data retrieval. Looks like this comes at the cost of multiple wasteful renderings if you have lots of streams of data getting resolved async.
So the question. Is it ok to bypass this convention? Using ngrx state store subscription and ditching resolvers + route subscription in the component in favor of guards that trigger the data request.
Another strange behavior is that no matter what I do initally I still have a few AppCmp renderings which seem to be triggered by the observables themselves before the children comps are even Inited.
Edit
I just had some trouble today. It was a mistake to use OnPush for container components such as pages (smart components). The subscriptions will fire but the template will not receive the updated values. That's expected from OnPush since no inputs are triggered. So I'm using OnPush only on the dumb components, which is still a significant improvement since they do the bulk of the hard work.
Edit 2 - Use resolvers, not guards
Well... This didn't work out as expected. Let's just say that if you have an observable that hasn't fired yet, the guard will simply block the flow permanently. So my fancy example was working just because the observables had already some values inside that mapped to a true. After doing a thorough cleanup I found that my app stopped working.
In essence, the following basic example works within a resolver but not a guard. This is because the observable can't get any value back the moment he asks for it so he just assumes it's a no go. I'll just have to investigate further where are those extra renderings coming from. There must be some faulty code somewhere.
return Observable.interval(1000)
.take(1) // Needed to trigger the guard. Resolvers do just fine without
.map(() => true )
Use resolvers, not guards
Well... This didn't work out as expected. Let's just say that if you have an observable that hasn't fired yet, the guard will simply block the flow permanently. So my fancy example was working just because the observables had already some values inside that mapped to a true. After doing a thorough cleanup I found that my app stopped working.
In essence, the following basic example works within a resolver but not a guard. This is because the observable can't get any value back the moment he asks for it so he just assumes it's a no go. I'll just have to investigate further where are those extra renderings coming from. There must be some faulty code somewhere.
return Observable.interval(1000)
.take(1) // Needed to trigger the guard. Resolvers do just fine without
.map(() => true )
Have discovered a bug with respect to debounced input and handling responses.
I have a search input that queries the server as you type. I put a debounce on it set to 300ms. However, sometimes there's some odd behavior:
User types "ab", waits 300ms, types "c" before that first request resolves. In the search bar they see "abc", but there are now two network requests. Sometimes the second request ("abc") resolves first, then the first request ("ab") resolves and overwrites the results list. So the user sees a list of results for "ab", but the search input has "abc".
This seems like less of an issue of debounce per se, and more along the lines of finding a way to discard "old" promises so that they can be ignored when they resolve.
So for example - what I want
types "ab"
send request "ab"
types "c"
send request "abc"
"abc" returns response, handle promise resolution
"ab" returns response, ignore promise
Are there any common patterns/approaches for this sort of thing in Angular? It sounds like it would be a common issue.
E.g "Resolve only the latest promise that was created"?
This is a perfect use-case to introduce RxJS, Angular 2 has default support for RxJS. However in Angular 1 it's also possible to use this library, take a look at the official rx.angular.js library over here.
If you include this library you should be able to solve your problem as follows:
HML
<input type="text" ng-model="search">
JS
observeOnScope($scope, 'search')
.debounceTime(300)
.distinctUntillChanged()
.switchMap(search)
.safeApply($scope, function (data) {
$scope.data = data;
})
.subscribe();
function search() {
return rx.Observable.fromPromise($http({ ... }));
}
Edit: a more in depth article can be found over here
You can achieve this simply using subscription.
import { Subscription } from 'rxjs';
export class Component {
constructor(){}
subscription: Subscription;
getData() {
// just check if subscription is already there then unsubscribe that
if (this.subscription) {
this.subscription.unsubscribe();
}
this.subscription = this._http.get(url).subscribe(
data => {
console.log(data);
},
error => {
console.log(error);
}
)
}
}