Subject doesn't emit data - javascript

I have an app which receives data from user and validate them in the form. When validation is true button is getting enabled and user is getting permitted to submit his order in this scenario.
I don't know why in this component my subjects don't work. I mean I can .next(value) in a component and in service I can console.log(value) to check its getting arrived to service or not.
I can see that in service is getting received but ,that received value isn't being subscribed in the component I want to use them. I stopped running projects but couldn't be fixed. Here is what I tried:
AuthService.ts
emailSubject=new Subject<string>();
getEmail(value)
{
console.log(value);
this.emailSubject.next(value); //prints email to the console correctly
}
CarService.ts
export class CarService
{
carrierSubject=new Subject<number>();
orderSubject=new Subject<Order[]>();
totalCostSubject=new Subject<number>();
lastTotalCostSubject=new Subject<number>();
getId(myIndex:number)
{
this.carrierSubject.next(myIndex);
}
setOrders(value)
{
console.log(value);
this.orderSubject.next(value);
}
setTotalCost(value)
{
this.totalCostSubject.next(value);
}
lastSetTotalCost(value)
{
this.lastTotalCostSubject.next(value);
}
CarPayment.ts
export class CarPaymentComponent implements OnInit {
car:Car;
selectedCar:string;
somePlaceholder : number = 0;
myArray:Order[];
email:string;
constructor(private carService:CarService,private authService:AuthService) { }
ngOnInit() {
this.carService.carrierSubject.subscribe(value=>
{
this.car=this.carService.getCar(value);
this.selectedCar=this.car.brand;
});
this.carService.lastTotalCostSubject.subscribe(value=>
{
this.somePlaceholder=value;
});
this.carService.orderSubject.subscribe(value=>
{
this.myArray=value;
}
);
this.authService.emailSubject.subscribe(value=>
{
this.email=value;
});
}
onSubmit()
{
console.log("ORDER INFO")
console.log('This order ordered by:'+this.email);
console.log("Ordered Car:"+this.selectedCar);
console.log("Ordered Parts:"+this.myArray);
console.log("Total Cost:"+this.somePlaceholder);
}
}

As #lealceldeiro and #FatemeFazli have mentioned, you'd need to use BehaviorSubject or ReplaySubject. The reason you code is not working is because your observables haven't fired any value yet. Essentially, when you do .subscribe, you are hooking into change event. But in your case, the change hasn't been fired yet.
AuthService.ts
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs'; //<----Add this line
#Injectable()
export class AuthService {
emailSubject = new BehaviorSubject<string>("test#test.com"); //<--- provide an initial value here
getEmail(value) {
console.log(value);
this.emailSubject.next(value); //prints email to the console correctly
}
}
CarService.ts
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class CarService {
carrierSubject = new BehaviorSubject<number>(0); //<-- provide an initial value here
orderSubject = new BehaviorSubject<Order[]>([]); //<-- provide an initial value here
totalCostSubject = new BehaviorSubject<number>(0); //<-- provide an initial value here
lastTotalCostSubject = new BehaviorSubject<number>(0); //<-- provide an initial value here
getId(myIndex: number) {
this.carrierSubject.next(myIndex);
}
setOrders(value) {
console.log(value);
this.orderSubject.next(value);
}
setTotalCost(value) {
this.totalCostSubject.next(value);
}
lastSetTotalCost(value) {
this.lastTotalCostSubject.next(value);
}
}

Related

Array not updated when changes occur Angular 10

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 => {
//...
})
}

subscription to behaviour subject don't work on all components

I my global service I instiante a behaviourSubject variable
dataWorkFlowService:
export class CallWorkflowService {
url = 'http://localhost:3000/';
selectedNode : BehaviorSubject<Node> = new BehaviorSubject(new Node(''))
dataflow : BehaviorSubject<any> = new BehaviorSubject<any>({});
constructor(private http: HttpClient) {}
getDataflow() {
return this.http.get(this.url);
}
updateNode(node :Node) {
this.selectedNode.next(node);
}
}
In my component ReteComponent I set behaviourSubject value using
this.dataFlowService.selectedNode.next(node);
Im my second component I subscribe to the BehaviourSubject
export class ComponentsMenuComponent implements OnInit {
constructor(private callWorkflowService:CallWorkflowService) { }
selectedNode:Node = new Node('');
dataFlow:any;
nxtElements:String[]=[]
ngOnInit() {
this.callWorkflowService.dataflow.subscribe(data=> {
this.dataFlow=data
})
this.callWorkflowService.selectedNode.subscribe( (node) => {
this.selectedNode=node; <=== ###### Subscription is not triggered
if(this.dataFlow) {
this.nxtElements=this.dataFlow[node.name].next;
}
})
}
When I trigger new value to selectedNode my subscription does not work
But in another component it's working well
export class AppComponent {
opened:boolean=false;
events: string[] = [];
constructor(private callWorkflowService:CallWorkflowService) { }
ngOnInit() {
this.callWorkflowService.selectedNode.pipe(
skip(1)
)
.subscribe( (node) => {
this.opened=true; <== subscription is working
})
}
}
I have noticed in that in ComponentsMenuComponent when I change it to
export class ComponentsMenuComponent implements OnInit {
constructor(private callWorkflowService:CallWorkflowService) { }
selectedNode:Node = new Node('');
dataFlow:any;
nxtElements:String[]=[]
ngOnInit() {
this.callWorkflowService.getDataflow().subscribe(data=> {
this.dataFlow=data;
}) ####CHANGE HERE ### <== using `getDataFlow` method which is not observable
this.callWorkflowService.selectedNode.subscribe( (node) => {
this.selectedNode=node; ### <=== subscription is triggered
if(this.dataFlow) {
this.nxtElements=this.dataFlow[node.name].next;
}
})
}
the selectNode subscription is working.
Update
I have tried to change how I proceed
In my service I added a method that return last value
updateDataFlow() {
return this.dataflow.getValue();
}
In ComponentsMenuComponent
this.callWorkflowService.node.subscribe( (node) => {
this.dataFlow = this.callWorkflowService.updateDataFlow();
this.selectedNode=node;
if(this.dataFlow) {
this.nxtElements=this.dataFlow[node.name].next;
}
})
Here again subscription is not working..
I have tried to comment the line
this.dataFlow = this.callWorkflowService.updateDataFlow();
And here surprise.. subscription works.
I don't know why it don't subscribe when I uncomment the line that I have mentioned
You must be providing your CallWorkflowService incorrectly and getting a different instance of the service in different components. If one component is working and another is not then I would guess that they are not both subscribed to the same behavior subject.
How are you providing the service? Is it provided in a module, component or are you using provided in?

Observable next() callback not triggered

I'm trying to implement a global loading indicator that can be reused in the entire application. I have an injectable service that has the show and hide functions:
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
#Injectable()
export class SpinnerOverlayService {
private loaderSubject = new Subject<any>();
public loaderState = this.loaderSubject.asObservable();
constructor() { }
/**
* Show the spinner
*/
show(): void {
this.loaderSubject.next(<any>{ show: true });
}
/**
* Hide the spinner
*/
hide(): void {
this.loaderSubject.next(<any>{ show: false });
}
}
And this is the code of the spinner overlay component. I'll exclude details about the HTML and CSS implementation as they're not important here.
import { Component, OnInit } from '#angular/core';
import { Subscription } from 'rxjs';
import { SpinnerOverlayService } from '../spinner-overlay.service';
#Component({
selector: 'spinner-overlay',
templateUrl: './spinner-overlay.component.html',
styleUrls: ['./spinner-overlay.component.scss']
})
export class SpinnerOverlayComponent implements OnInit {
show = false;
private _subscription: Subscription;
constructor(private spinnerOverlayService: SpinnerOverlayService) { }
ngOnInit(): void {
this._subscription = this.spinnerOverlayService.loaderState.subscribe((state) => {
console.log("Subscription triggered.");
this.show = state.show;
});
}
ngOnDestroy(): void {
this._subscription.unsubscribe();
}
}
The problem: In the code of the overlay component I'm subscribing to the observable loaderState of the service. However when I call the show() function which triggers the next() of the observable, the subscription callback is not triggered.
This is how I call the show() function in the app.component.ts:
ngOnInit() {
this.spinnerOverlayService.show();
}
What could I be missing? Seems really strange that the callback is not triggered.
Here is an example in Stackblitz: https://stackblitz.com/edit/angular-7-registration-login-example-2qus3f?file=app%2Fspinner-overlay%2Fspinner-overlay.component.ts
The problem is you call this.spinnerOverlayService.show(); before spinner-overlay is initialized. Subjects do not hold previous emitted value, so late subscribers won't get any value unless there is a new value.
One thing you can do is to change Subject to BehaviorSubject which emits the last value to new subscribers.
Or, you can call this.spinnerOverlayService.show(); within ngAfterViewInit.
This way, you'll know spinner-overlay will get initialized and subscribe to spinnerOverlayService.loaderState
ngAfterViewInit() {
this.spinnerOverlayService.show();
}
Check it out
In addition to the above answer you can have a state in your spinnerOverlayService service to check the show hide and also have a subject to subscribe if new value is ready:
public state = { show: false };
constructor() { }
/**
* Show the spinner
*/
show():void {
this.state = { show: true };
this.loaderSubject.next(<any>{ show: true })
}
/**
* Hide the spinner
*/
hide():void {
this.state = { show: false };
this.loaderSubject.next(<any>{ show: false })
}
and in your ngOnInit:
ngOnInit(): void {
if(this.spinnerOverlayService.state.show){
console.log('Subscription triggeredd.');
};
this._subscription = this.spinnerOverlayService.loaderState.subscribe((state) => {
console.log("Subscription triggered.");
this.show = state.show;
});
}
OR you can use:
private loaderSubject = new ReplaySubject(1); // to cache last value
demo.

ngFor loop content disapears when leaving page

I am new to Angular and Ionic. I am looping through an array of content that is store in my Firestore database. When the app recompiles and loads, then I go to the settings page (that's where the loop is happening), I see the array of content just fine. I can update it on Firestore and it will update in real time in the app. It's all good here. But if I click "Back" (because Settings is being visited using "navPush"), then click on the Settings page again, the whole loop content will be gone.
Stuff is still in the database just fine. I have to recompile the project to make the content appear again. But once again, as soon as I leave that settings page, and come back, the content will be gone.
Here's my code:
HTML Settings page (main code for the loop):
<ion-list>
<ion-item *ngFor="let setting of settings">
<ion-icon item-start color="light-grey" name="archive"></ion-icon>
<ion-label>{{ setting.name }}</ion-label>
<ion-toggle (ionChange)="onToggle($event, setting)" [checked]="setting.state"></ion-toggle>
</ion-item>
</ion-list>
That Settings page TS file:
import { Settings } from './../../../models/settings';
import { DashboardSettingsService } from './../../../services/settings';
import { Component, OnInit } from '#angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
#IonicPage()
#Component({
selector: 'page-dashboard-settings',
templateUrl: 'dashboard-settings.html',
})
export class DashboardSettingsPage implements OnInit {
settings: Settings[];
checkStateToggle: boolean;
checkedSetting: Settings;
constructor(public dashboardSettingsService: DashboardSettingsService) {
this.dashboardSettingsService.getSettings().subscribe(setting => {
this.settings = setting;
console.log(setting.state);
})
}
onToggle(event, setting: Settings) {
this.dashboardSettingsService.setBackground(setting);
}
}
And my Settings Service file (the DashboardSettingsService import):
import { Settings } from './../models/settings';
import { Injectable, OnInit } from '#angular/core';
import * as firebase from 'firebase/app';
import { AngularFireAuth } from 'angularfire2/auth';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class DashboardSettingsService implements OnInit {
settings: Observable<Settings[]>;
settingsCollection: AngularFirestoreCollection<Settings>;
settingDoc: AngularFirestoreDocument<Settings>;
public checkedSetting = false;
setBackground(setting: Settings) {
if (this.checkedSetting == true) {
this.checkedSetting = false;
} else if(this.checkedSetting == false) {
this.checkedSetting = true;
};
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({state: this.checkedSetting});
console.log(setting);
}
constructor(private afAuth: AngularFireAuth,private afs: AngularFirestore) {
this.settingsCollection = this.afs.collection('settings');
this.settings = this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
return data;
});
});
}
isChecked() {
return this.checkedSetting;
}
getSettings() {
return this.settings;
}
updateSetting(setting: Settings) {
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({ state: checkedSetting });
}
}
Any idea what is causing that?
My loop was in a custom component before, so I tried putting it directly in the Dashboard Settings Page, but it's still not working. I have no idea what to check here. I tried putting the :
this.dashboardSettingsService.getSettings().subscribe(setting => {
this.settings = setting;
})
...part in an ngOninit method instead, or even ionViewWillLoad, and others, but it's not working either.
I am using Ionic latest version (3+) and same for Angular (5)
Thank you!
From the Code you posted i have observed two findings that might be the potential cause for the issue ,
Calling of the Service method in the constructor :
When your setting component is created , then that constructor will be called but but if you were relying on properties or data from child components actions to take place like navigating to the Setting page so move your constructor to any of the life cycle hooks.
ngAfterContentInit() {
// Component content has been initialized
}
ngAfterContentChecked() {
// Component content has been Checked
}
ngAfterViewInit() {
// Component views are initialized
}
ngAfterViewChecked() {
// Component views have been checked
}
Even though you add your service calling method in the life cycle events but it will be called only once as you were subscribing your service method in the constructor of the Settings service file . so just try to change your service file as follows :
getSettings() {
this.settingsCollection = this.afs.collection('settings');
this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
return data;
});
});
}
Update :
Try to change the Getsettings as follows and please do update your question with the latest changes
getSettings() {
this.settingsCollection = this.afs.collection('settings');
return this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
return data;
});
});
}
I'm not certain, but I suspect the subscription to the settings observable settings: Observable<Settings[]> could be to blame. This may work on the first load because the DashboardSettingsService is being created and injected, therefore loading the settings, and then emitting an item (causing your subscription event in DashboardSettingsPage to fire).
On the second page load, DashboardSettingsService already exists (services are created as singletons by default) - this means that the constructor does not get called (which is where you set up your observable) and therefore it does not emit a new settings object for your component.
Because the Observable does not emit anything, the following event will not be fired, meaning your local settings object is never populated:
this.dashboardSettingsService.getSettings().subscribe(setting => {
this.settings = setting;
console.log(setting.state);
})
You could refactor your service with a method that provides the latest (cached) settings object, or a new Observable (dont forget to unsubscribe!!), rather than creating a single Observable which will only be triggered by creation or changes to the underlying storage object.
Here's a simple example that doesnt change your method signature.
import { Settings } from './../models/settings';
import { Injectable, OnInit } from '#angular/core';
import * as firebase from 'firebase/app';
import { AngularFireAuth } from 'angularfire2/auth';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
#Injectable()
export class DashboardSettingsService implements OnInit {
settings: Observable<Settings[]>;
cachedSettings: Settings[];
settingsCollection: AngularFirestoreCollection<Settings>;
settingDoc: AngularFirestoreDocument<Settings>;
public checkedSetting = false;
setBackground(setting: Settings) {
if (this.checkedSetting == true) {
this.checkedSetting = false;
} else if(this.checkedSetting == false) {
this.checkedSetting = true;
};
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({state: this.checkedSetting});
console.log(setting);
}
constructor(private afAuth: AngularFireAuth,private afs: AngularFirestore) {
this.settingsCollection = this.afs.collection('settings');
this.settings = this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
this.cachedSettings = data;
return data;
});
});
}
isChecked() {
return this.checkedSetting;
}
getSettings() {
return Observable.of(this.cachedSettings);
}
updateSetting(setting: Settings) {
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({ state: checkedSetting });
}
}

Not able to get the data from shared service to component in angular 2

In our Angular 2 project. I am trying to use shared services for communication between two components.
We've a BotData.SharedService.ts file like this:
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
export interface Data {
name: string,
}
#Injectable()
export class BotDataService {
sharingData: Observable<Data[]>
private _sharingData: BehaviorSubject<Data[]>;
private dataStore: {
sharingData: Data[]
};
constructor() {
this.dataStore = { sharingData: [] };
this._sharingData = <BehaviorSubject<Data[]>>new BehaviorSubject([]);
}
saveData(userData) {
console.log(userData)
this._sharingData.next(userData);
}
getData() {
console.log('get data function called' + JSON.stringify( this.sharingData ) );
return this._sharingData.asObservable();
}
}
Here, we're passing data using this._sharingData.next(userData); inside saveData(userData).
Hence, this same data shall be available inside getData().
However, when we do console.log('get data function called' + JSON.stringify( this.sharingData ) this gives undefined.
Hence, botdataservice.getData() is breaking inside component:
constructor(private botdataservice: BotDataService) {
this.botdataservice = botdataservice;
this.botdataservice.getData().subscribe(_sharingData => {
// this.userKonte = _sharingData;
console.log(JSON.stringify(_sharingData));
});
}
What is the fix here?

Categories