Trying to communicate with 2 components
I thought I'd be able to make a http call, then maybe mergeMap or switchMap to a subject?
Something like
import {Subject} from 'rxjs/Subject';
constructor(private _http: HttpClient) {
this.populateList = new Subject<Blog[]>();
}
getBlogs(){
return this._http.get(this.blogsURL+'blogs')
.map((result: Response ) => {
this.blogs = result['blogs'];
return this.blogs;
}).switchMap((blogs)=>this.populateList.next(blogs))
}
But I get:
You provided 'undefined' where a stream was expected. You can provide
an Observable, Promise, Array
I'm getting errors just trying to subscribe to the subject:
this.blogsService.populateList()
.subscribe((res)=>{
console.log(res)
})
this.blogsService.populateList is not a function
What I'm looking for is a way to update views after http calls
You need to subscribe like this without (). Cause its not a function. surprise
this.blogsService.populateList.subscribe()
and rewrite first function like this cause you dont need switch map you just need to do is side effect to populate list.
getBlogs(){
return this._http.get(this.blogsURL+'blogs')
.map((result: Response ) => {
this.blogs = result['blogs'];
return this.blogs;
}).do((blogs)=>this.populateList.next(blogs))
}
Related
I have a function to get rates from products, so lets say I have one product with two rates. So my product has two rates. Then, when I get those rates I must get the prices attached to my product. So for each rate I have to look for its prices.
The next code below explains this:
this.loadProductInfo = true; // bool to load data in my form
// First of all, I get rates from API
// const rates = this._http....
// Now, for each rate I must search If my product/products have a price:
this.rates.forEach((rate, index, arr) => {
this._glbGetPricesForProduct.getPrice(params).subscribe(response => {
if (!arr[index + 1]) {
this.initForm();
this.loadProductInfo = false;
}
})
});
The variable loadProductInfo it loads content in my form, so in my html I have:
<form *ngIf="!loadProductInfo"></form>
But form it still give me error: could not find control name.
But if I do this instead, it works correctlly:
setTimeout(() => {
this.initForm();
this.loadProductInfo = false;
}, 2000);
So what I want its to say my form to wait until I have all code loaded and then after it, load its contents. But instead it cant find the control because it loads before code. Any help I really appreciate it.
The main mistake I see there is that you are looping over async data which may not be there when your code execute the for each loop (your rates).
I would build an observable with your rates as a source:
...
$rates: Observable<any> = this._http.get(...);
rates.pipe(
mergeMap((rates) => {
const priceByRates: Observable<any>[] = rates.map((rate, index, arr) => this._glbGetPricesForProduct.getPrice(params));
return combineLatest(pricesByRates); // if getPrice complete right away, use forkJoin() instead
})
).subscribe(res => {
// No need to check for the last item, all rates have been checked for possible price
this.initForm();
this.loadProductInfo = false;
});
...
This implementation should wait for your api calls to resolve before printing your form.
Since you are hiding the entire form, it may be better to just move the API call into a resolver so that the page does not render until the data is ready.
Here is a minimal StackBlitz showcasing this behavior: https://stackblitz.com/edit/angular-ivy-4beuww
Component
In your component, include an ActivatedRoute parameter via DI.
#Component(/*omitted for brevity*/)
export class MyComponent {
constructor(private route: ActivatedRoute) {
// note: 'data' is whatever you label your resolver prop in your routing setup
route.data.subscribe(resolved => {
if ("data" in resolved) this.resolveData = resolved["data"];
});
}
}
Route Setup
And in your router setup you would have the following:
const routes: Routes = [
{
path: 'my-route-path',
component: MyComponent,
resolve: {
data: MyResolver
}
}
];
Resolver
Finally, your resolver would make your API call utilizing your service:
#Injectable({providedIn: 'root'})
export class MyResolver() implements Resolve<T> {
constructor(private service: MyService) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | any {
return this.service.myRequest();
}
}
The final result will be that your view will not be rendered until your data is ready.
I have 2 services. Both services have individual subjects and I am exposing both of them to other components by returning asObservable. Both have an addDataN function and both are emitting data to respecting subjects in services by on subject,so the getdataN method which is also emitting data on subject.
Now at the consumer side I am receiving two services completely independent. In component I am subscribing to the Listener, which are returning asObservable, and getdataN funnction, which are emitting data
Serv1
getdata1() {
this.http.get<{message:string,Data1:any}>('http://localhost:3000/api/1')
.pipe(map((data1)=>{
return Data1.Data1.map(data=>{
return {
id: data._id,
data1Title:data1.data1Title,
}
})
})).subscribe((data1) => {
this.data1=data1
this.serv1Subject.next([...this.data1])
})
}
addData1(){
this.http.post<{message:string,Data1:any}>('http://localhost:3000/api/1',dataObject)
.subscribe((data)=>{
this.data1=data1
this.serv1Subject.next([...this.data1])
})
}
getData1Listener() {
return this.serv1Subject.asObservable()
}
Serv2
getdata1() {
this.http.get<{message:string,Data2:any}>('http://localhost:3000/api/2')
.pipe(map((data1)=>{
return Data2.Data2.map(data=>{
return {
id: data._id,
data1Title:data1.data1Title,
}
})
})).subscribe((data1) => {
this.data1=data1
this.serv2Subject.next([...this.data1])
})
}
addData2(){
this.http.post<{message:string,Data2:any}>('http://localhost:3000/api/2',dataObject)
.subscribe((data)=>{
this.data1=data1
this.serv2Subject.next([...this.data1])
})
}
getData2Listener() {
return this.serv2Subject.asObservable()
}
Now at consumer component at ngOnInit at beginning I want to make sure that both subjects emit data and perform function x on data received from both end. And after that both work independently.
ngOnInit(){
this.serv1.getdata1()
this.serv2.getdata2()
combineLatest(this.serv1.getData1Listener(), this.serv2.getData2Listener())
.subscribe(([data1,data2])=>{function x(){
something with data1,data2
}})
}
My Problem is with zip at beginning. Both work fine at beginning as both services emit, but after emitting data to serv1 the other is not emitted anything so it stuck there.
In withLatestFrom only recent data is received and I want to avoid spread operator
Is there any way I can cleanly implement this?
Any help is appreciated.
I'm using Angular 9 in my web app. I'm using a lot of services to connect to a web service. Sometimes a lot of identical requests are sent to a service. Maybe when a user click on a button repeatedly.
I want to cancel previews incomplete requests for all of my services. What's the best solution? (Maybe using RXJS)
This is one of my service functions:
constructor(private http: HttpClient) {}
getList(options?: ApiListOptions) {
return this.http.post<ApiResponse<UserGet[]>>(
environment.apiRoot + 'user/list',
options,
{ headers: this.headers() }
);
}
Thanks
Please try below with takeUntil rxjs operator you will be able to do the same.
export class YourComponent {
protected ngUnsubscribe: Subject<void> = new Subject<void>();
[...]
public httpGet(): void {
this.http.get()
.takeUntil(this.ngUnsubscribe)
.subscribe( (data) => { ... })
}
public ngOnDestroy(): void {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
}
For your specific case, like frequent button clicks, you can use switchMap() combine with debounceTime() to limit the frequency of action and http calls. You can use Subject for action trigger instead of component method
<button click="getList.next()">click</button>
getList=new Subject()
getList.pipe(debounceTime(2000),switchMap(()=>
this.http.post<ApiResponse<UserGet[]>>(
environment.apiRoot + 'user/list',
options,
{ headers: this.headers() }
);
)).subscribe()
In my Angular 8 application, I have a basic caching interceptor:
export class CacheInterceptor implements HttpInterceptor {
constructor(private cache: CacheService) {}
public intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if (req.method !== 'GET') {
return next.handle(req);
}
const cachedResponse = this.cache.get(req);
if (cachedResponse) {
console.log(cachedResponse);
return of(cachedResponse);
}
return next.handle(req).pipe(
filter(event => event instanceof HttpResponse),
map((response: HttpResponse<any>) => {
this.cache.addToCache(req, response);
return response;
})
);
}
}
I also have a service which retrieves data from an external API:
public getCases(options: ModuleArguments): Observable<CaseResponse> {
return this.http
.get<CaseResponse>(this.URL_BASE, {
params: options as HttpParams
})
.pipe(map(this.cleanData, this));
}
The 'cleanData' method just loops through the received data and amends some of the values to make them more human friendly (e.g. turns 'support_request' to 'Support Request').
What appears to be happening is the response is being added to the cache by the CacheInterceptor after the data has been 'cleaned' within the service. Therefore, when the same request is made again, and received from the cache, the service is attempting to clean data which has already been cleaned.
How do I ensure that the the HTTP Response has been intercepted and added to the cache before it has been amended by the service?
How about you approach this by moving the pipe(map(this.cleanData, this)) operation to the point when the Observable has completed and returned your CaseResponse. Likely, this will mean that the HttpInterceptor has been applied first.
i.e. In the place where you invoke getCases you could try something like this:
service.getCases(options).subscribe(resolvedData => {
// assuming cleanData(data: CaseResponse) signature
const cleanedData = this.cleanData(resolvedData);
// .. do something with cleanedData
});
Also, from a design perspective, you wouldn't want getCases to do more than exactly what it's supposed to - It's a service method that performs an HTTP request and returns the cases in the format they are sent to you. The reformatting of the data could be ideally done at the consumer of that service function - as it's very likely the consumer that needs it cleaned/reshaped.
If I use the tap rxjs operator on an observable to call another observable, can I guarantee that it completes before the rest of the pipe?
The idea here is to have a service make an http call to the backend, if it's a good login, create a cookie, then return a mapped response to the consuming component. I want to make sure the cookie is added before continuing to make sure there are no race conditions.
import { of, Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
const httpObservable = loginAccount('fsdfds', 'fdsfsd');
httpObservable.subscribe(x => {
console.log(x);
});
function loginAccount(username, password): Observable<any> {
const httpResponse = of({ loggedIn: false, data: 'faketokenfrombackend' });
return httpResponse.pipe(
// Will this AWLAYS complete before map?
tap(resp => fakeLocalStorage('Do something with the result')),
// Will this AWLAYS complete before map?
tap(resp => fakeLocalStorage('Do something else with the result')),
map(resp => {
if (!resp.loggedIn)
return { success: false, message: 'really bad thing happened' };
else
return {success: true, message: 'WEEEEEE, it worked!'}
}));
}
function fakeLocalStorage(data: string): Observable<boolean> {
console.log('adding token to cookie');
return of(true);
}
The above script outputs this to the console window as expected, but can I rely on it?
adding token to cookie
adding token to cookie
{success: false, message: "really bad thing happened"}
Yes, RxJS will run the piped operators in order. As long as the tap operators are synchronous they will complete before the map operator is run. If they do anything asynchronous they will not.