I've got a component A, which sends a value to a service method (via calling it), and then I tried to create a subject and 'observe' it from component B, thus triggering an action on component B from component A.
This is what I did:
Component A sends the data:
this.service.method(id_estado)
Which is recieved by service:
import { Injectable } from '#angular/core';
import { BehaviorSubject, of, Observable, Subscription, Subject } from 'rxjs';
export class service {
estado: any;
subject = new Subject<any>();
constructor( private api: ApiService ) {
}
service ( id_estado ){
let subject = new Subject<any>();
this.subject.next(estado)
}
}
And in component B I'm trying to observe it like this:
this.service.subject.subscribe( (data) => {
console.log(data);
});
Component B's part takes place inside a method. Is this the cause of the problem? I can't get any data to show on my console.log
let subject = new Subject<any>();
this.subject.next(estado)
- should be moved to constructor to be initialized.
or just execute service in constructor:
import { Injectable } from '#angular/core';
import { BehaviorSubject, of, Observable, Subscription, Subject } from 'rxjs';
export class service {
estado: any;
subject = new Subject<any>();
constructor( private api: ApiService ) {
service(true); // or some value you need.
}
service (id_estado){
this.subject.next(estado);
}
}
In this case you can remove initialization subject = new Subject<any>(); before constructor.
In your case you've just got Subject, but .next() wasn't executed.
Component B's part takes place inside a method. Is this the cause of
the problem?
Yes, that's the problem. If that method isn't called, then you aren't subscribing to the Subject and that is why no data is received.
Move this subscribing part to ngOnInit of Component B.
Also in the service, you are creating a new Subject whenever that method is called.
Subject is already defined at the beginning, no need to do it again.
After this change, your service should look like:
export class service {
estado: any;
subject = new Subject<any>();
constructor( private api: ApiService ) {
}
service(id_estado) {
this.subject.next(estado);
}
}
Check out this StackBlitz where you can see components communicating in real time using Subject.
you should try event emitter service of angular, in which you subscribe your data in pass that data value in any component using event emitter service.
Related
In an Angular 11 app, I have a simle service that mekes a get request and reads a JSON.
The service:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Promo } from '../models/promo';
#Injectable({
providedIn: 'root'
})
export class PromoService {
public apiURL: string;
constructor(private http: HttpClient) {
this.apiURL = `https://api.url.com/`;
}
public getPromoData(){
return this.http.get<Promo>(`${this.apiURL}/promo`);
}
}
In the the component, I need to compare the array of products with the array of campaign products (included in the JSON mantioned above) and higlight the promoted products:
export class ProductCardComponent extends DestroyableComponent implements OnInit, OnChanges
{
public promoData: any;
public promoProducts: any;
public isPromoProduct: boolean = false;
public ngOnInit() {
this.getCampaignData();
}
public ngOnChanges(changes: SimpleChanges): void {
this.getCampaignData();
}
public getPromoData() {
this.promoService.getPromoData().pipe(takeUntil(this.destroyed$)).subscribe(data => {
this.promoData = data;
this.promoProducts = this.promoData.products;
let promoProduct = this.promoProducts.find((product:any) => {
return this.product.unique_identifier == product.unique_identifier;
});
if (promoProduct) {
// Update boolean
this.isPromoProduct = true;
}
});
}
}
In the component's html file (template), I have:
<span *ngIf="isPromoProduct" class="promo">Promo</span>
There are no compilation errors.
The problem
For a reason I have been unable to understand, the template does not react to the change of the variable isPromoProduct and the template is not updated, despite the fact that I call the function inside ngOnInit and ngOnChanges.
Questions:
Where is my mistake?
What is a reliable way to update the template?
subscribing to Observable inside .ts file it's mostly not a best practice.
try to avoid it by using async pipe of Angular.
you need to store the observable in the variable and not the data returned from the observable, for example:
// this variable holds the `observable` itself.
this.promoData$ = this.promoService.getPromoData()
and then in the template you can do it like this:
<div *ngIf="promoData$ | async as promoData">
here you can access the promoData
</div>
you can still use pipe() to map the data etc but avoid the subscribe()
The isPromoProduct boolean is not an input. The ngOnChanges gets triggered for changes on your properties that are decorated with the #Input decorator. For your particular case, you can inject the ChangeDetectorRef and trigger change detection manually:
constructor(private cdr: ChangeDetectorRef) {}
// ...
public getPromoData() {
this.promoService.getPromoData().subscribe(data => {
// ...
if (promoProduct) {
// Update boolean
this.isPromoProduct = true;
this.cdr.detectChanges();
}
});
}
You also don't need to manage httpClient subscriptions. The observables generated by a simple get or post request will complete after they emit the response of the request. You only need to explicitly manage the unsubscribe for hot observables (that you create from subjects that you instantiate yourself).
There is an Observable that sends an array of offers to my component.
But when the list is changes (one is deleted) it does not change the list that I get in the component.
I've tried it with ngOnChanges to subscribe to the list again and update the list in my component, but it doesn't detect any changes on the list.
When I use ngDoCheck it worked, but I want a little less drastic solution for this..
offer.service.ts:
// observable of offers list
public getAll(): Observable<Offer[]> {
return of(this.offers);
}
component.ts:
offers: Offer[] = [];
selectedOfferId = -1;
constructor(private offerService: OfferService) { }
ngOnInit(): void {
this.offerService.getAll().subscribe(data => {
this.offers = data;
});
}
ngOnChanges(): void {
this.offerService.getAll().subscribe(data => {
this.offers = data;
});
}
You can communicate between components using an Observable and a Subject (which is a type of observable), I won't go too much into the details, you can fin more info here, there are two methods: Observable.subscribe() and Subject.next().
Observable.subscribe()
The observable subscribe method is used by angular components to subscribe to messages that are sent to an observable.
Subject.next()
The subject next method is used to send messages to an observable which are then sent to all angular components that are subscribers of that observable.
A workaround solution:
offer.service.ts:
import { Injectable } from '#angular/core';
import { Observable, Subject } from 'rxjs';
#Injectable({ providedIn: 'root' })
export class OfferService {
private subject = new Subject<any>();
//...
getOffers(message: string) {
return this.subject.asObservable();
}
removeOffers() {
//...remove logic
this.subject.next({this.offers})
}
}
component.ts:
subscription: Subscription;
ngOnInit(): void {
this.subscription = this.offerService.getOffers().subscribe(offers => {
//...
})
}
Am working on a Single page Application built using Angular 8 on the frontend and Laravel on the backend. It is a CRUD application, on the delete functionality, it is working well by deleting the user of the specific id on the database. After the user of the specific id is deleted, am fetching all the products from the database but I want to update the data on the U.I afresh with the new data (excluding the deleted resource).
Kindly assist?
Show.component.ts file
import { Component, OnInit , ViewChild, ElementRef} from '#angular/core';
import { SharedService } from 'src/app/Services/shared.service';
import { AuthService } from 'src/app/Services/auth.service';
import { Router } from '#angular/router';
import { Observable } from 'rxjs';
import { SnotifyService } from 'ng-snotify';
#Component({
selector: 'app-show',
templateUrl: './show.component.html',
styleUrls: ['./show.component.css']
})
export class ShowComponent implements OnInit {
public userData : any[];
public error = null;
constructor(
private Shared : SharedService,
private Auth:AuthService,
private router: Router,
private Notify:SnotifyService
) { }
//Update the data when the DOM loads
ngOnInit() {
this.Shared.checkAll$.subscribe(message => this.userData = message);
}
//Method called when the delete button is triggered from the html
//Inside it we submit the data to the backend via a service and get
//the response
deleteUser(id:number){
return this.Auth.delete(id).subscribe(
data => this.handleDeleteResponse(data),
error => this.handleDeleteError(error)
);
}
//data below contains data from the backend after successful deletion
handleDeleteResponse(data:any){
this.Notify.success(`Successfully Deleted in our records`, {timeout:4000});
}
handleDeleteError(error:any){
console.log(error);
}
}
In you’re handleDeleteResponse method, there is a data if the data is the userData this.userData = data or it’s simple delete the user id from the array in you’re Js in the subscription of your delete method.
Like:
this.userData = this.userData.filter(user => user.id !== idToDelete )
Method 1:
Define a Subject in your service and subscribe to that subject in the service to receive the data. In the component, change the lifecycle hook to 'onChanges'. As soon as the data in the Subject is received/updated (with the deleted records) ngChanges shall reflect it in the DOM.
Method 2:
Track the records on the front-end in the form of list and when the service gives the response of delete as success then delete that very record in the list using ID or any other unique identifier. In this case you need not to populate all the records again.
export class MyComponent implements OnInit, OnChanges {
ngOnChanges() {
// code here
}
ngOnInit() {
// code here
}
}
I have a route which needs some data from my Firebase db before the route is loaded. It feels like the Route is not calling subscribe so the request is never being fired off. Am I missing a step?
(Angular 5)
My router:
{
path: 'class/:idName',
component: ClassComponent,
resolve: {
classData: ClassResolver
}
},
My Resolver:
#Injectable()
export class ClassResolver implements Resolve<any> {
constructor(
private db: AngularFireDatabase
) {}
resolve(route: ActivatedRouteSnapshot): Observable<any> | Promise<any> | any {
// return 'some data'; //This worked fine
return this.db
.list('/')
.valueChanges() // Returns Observable, I confirmed this.
//.subscribe(); // This returns a Subscriber object if I call it and I never get any data
}
// I tried this and it didnt work either
//const list = this.db
// .list('/')
// .valueChanges();
//console.log('list', list); // Is a Observable
//list.subscribe(data => {
// console.log('data', data); // returned data
// return data;
//});
//return list; // never gets to the component
}
My Component:
public idName: string;
// Other vars
constructor(
private fb: FormBuilder,
private route: ActivatedRoute,
private db: AngularFireDatabase
) {
// Form stuff
}
ngOnInit() {
// Never makes it here
this.idName = this.route.snapshot.params.idName;
const myclass = this.route.snapshot.data.classData;
console.log('myclass', myclass);
}
I never makes it to the component. It waits for the component to load, which it never does. If I add the subscribe and console.out the data it returns quite quickly with the correct data, so its not the service.
After calling .subscribe() in my Resolver that now returns a Subscriber object. Because my return signature allows for any its returning this Subscriber as if it was the data. This seems obvious now.
My question now becomes why isn't it resolving my Observable?
Your resolve function is returning an Observable that never completes. The Observable is indeed firing (and this can be verified by adding a tap to its pipeline with some console-logging)—but the resolve phase won't end (and therefore your component won't load) until the Observable completes. (The docs are not great at highlighting this.)
Obviously you don't want your Observable to complete either, because then you wouldn't get further data updates.
The simplest “fix” is to wrap your Observable in a Promise:
async resolve(route: ActivatedRouteSnapshot): Promise<Observable<any>> {
return this.db.list('/').valueChanges();
}
but this won't guarantee that Firebase has emitted its initial response, which I feel is what you're trying to ensure before the route loads.
The only approach I can see that would:
ensure that the component doesn't load until Firebase has returned data at least once; and
prevent two different Firebase reads (one by the resolver and then one by the component) for one effective operation
is to wrap your Firebase Observable in a service:
import { Injectable, OnDestroy, OnInit } from '#angular/core';
import { AngularFireDatabase } from '#angular/fire/database';
import { Subscription } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
#Injectable({
providedIn: 'root',
})
export class DataService implements OnInit, OnDestroy {
constructor(private readonly db: AngularFireDatabase) {}
/**
* Observable to the data.
* shareReplay so that multiple listeners don't trigger multiple reads.
*/
public readonly data$ = this.db
.list('/')
.valueChanges()
.pipe(shareReplay({ bufferSize: 1, refCount: true }));
/**
* To trigger the first read as soon as the service is initialised,
* and to keep the subscription active for the life of the service
* (so that as components come and go, multiple reads aren't triggered).
*/
private subscription?: Subscription;
ngOnInit(): void {
this.subscription = this.data$.subscribe();
}
ngOnDestroy(): void {
this.subscription?.unsubscribe();
}
}
and then your resolver would look like this:
async resolve(route: ActivatedRouteSnapshot): Promise<Observable<any>> {
// ensure at least one emission has occurred
await this.dataService.data$.pipe(take(1)).toPromise();
// ...then permit the route to load
return this.dataService.data$;
}
By wrapping your Firebase Observable in a service, you get OnInit and OnDestroy lifecycle hooks, which you can use to ensure that the observable "lives on" between component loads (and prevent multiple Firebase reads where one would suffice). Because the data is then hanging around, subsequent loads of the data would also be quicker. Lastly, this still enables you to use a resolver to ensure that the data will be instantly available before proceeding to load the component.
Your code looks to be correct. Have you been passing a parameter to your class route? It wont resolve without a parameter, that might be why you are not reaching your ngOnInit function. I would suggest console logging your route snapshots as well to make sure you are grabbing the right objects. I'll also post a resolve example that I got working:
Component.ts
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { Observable } from 'rxjs/Observable';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
public data: Observable<any>;
constructor(private router: ActivatedRoute) { }
ngOnInit() {
this.data = this.router.snapshot.data.test;
}
}
Routing.ts
{ path: 'home/:id', component: HomeComponent, resolve: { test: ResolverService } },
ResolverService
import { Injectable } from '#angular/core';
import { Resolve } from '#angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
#Injectable()
export class ResolverService implements Resolve<Observable<any>> {
constructor() { }
public resolve(route: ActivateRouteSnapShot): Observable<any> {
return Observable.of({test: 'Test Observable'});
}
}
HTML
{{this.data.test}}
You just need to add a take(1) operator to the Observable the resolver returns so that it completes.
resolve(route: ActivatedRouteSnapshot): Observable<any> {
return this.db.list('/').valueChanges()
.pipe(take(1)); // <-- The Magic
}
#AlexPeters was on the right track, but you don't have to go so far as to return a promise. Just force the completion with take(1). Alex is also spot-on that the docs are not very clear on this. I just spent an couple hours debugging this same issue.
I have a ShareService in angular 2,
******************************shareService*************************
import { BehaviorSubject , Subject} from 'rxjs/Rx';
import { Injectable } from '#angular/core';
#Injectable()
export class shareService {
isLogin$:BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
CheckUser = this.isLogin$.asObservable();
public isLogin (bool){
this.isLogin$.next(bool);
}
}
and its my another component and subscibe the CheckUser;
***********************another Component*******************************
_shareService.CheckUser.subscribe((val) =>{
*********all of this scope execute for several times just i have one another component and one next function*******
this.isLogin = val;
alert(val);
if(this.isLogin){
console.log("req req req");
this.MyBasket();
}
else if(this.ext.CheckLocalStorage("ShopItems")){
this.ShopItems = JSON.parse(localStorage.getItem("ShopItems"));
setTimeout(() => {
_shareService.sendShopItems(this.ShopItems);
},100);
}
});
my problem is i execute once this.isLogin$.next(bool) but subscribe function execute twice or several times !!!! my basket function is an xhr request this means when user loged in i get the several request to server!!!i cant fix it...i dont know this problem is for angular 2 or not,Anyone have this problem??
last a few days i Involved in this problem!
The problem is that your shareService is getting multiple instances.
One of the solutions is forcing the service to be a singleton.
Something like this should work:
import { provide, Injectable } from '#angular/core';
#Injectable()
export class shareService {
private static instance: shareService = null;
// Return the instance of the service
public static getInstance(/*Constructor args*/): shareService {
if (shareService.instance === null) {
shareService.instance = new shareService(/*Constructor args*/);
}
return shareService.instance;
}
constructor(/*Constructor args*/) {}
}
export const SHARE_SERVICE_PROVIDER = [
provide(shareService, {
deps: [/*Constructor args dependencies*/],
useFactory: (/*Constructor args*/): shareService => {
return shareService.getInstance(/*Constructor args*/);
}
})
];
Everything that is required on your current constructor should be placed where it says constructor args
Now on your components you use the service like this:
#Component({
providers: [SHARE_SERVICE_PROVIDER]
})
And then you can call it like you usually do.
Another solution would be injecting your current service on the main component of the app. See here for more info.
The problem is that the service is singleton and the component subscribe to it each time it created or (I don't see the full code) at the point the
_shareService.CheckUser.subscribe
is placed , so CheckUser should be a method that returns an Observable . if you have plunkr I can edit it .
Another semantic problem is that the observable should end with $ and not the BehaviorSubject.