BehaviourSubject.getValue() returning default values - javascript

I am fairly new to angular. I have two components namely header and profile component. The header component handles the login functionality and maintains two information- the user details which is json object and a isLoggedIn which is a boolean that saves current state of login. The general layout of the profile page is-
<header-component>
<profile-component>
Now since the header component handles the login. I want to avoid writing the logic for getting userDetails and the isLoggedIn status again for profile component. So i decided writing a shared service called profile service so that i can upload userDetails and isLogged from header and access that info in the profile component. The input in the loginlogout method comes from the header component.
SharedService code -
import { Injectable } from '#angular/core';
import { HttpService } from './https.service';
import { Observable, BehaviorSubject, of as observableOf } from 'rxjs';
import * as _ from 'lodash';
import { HttpHeaders, HttpParams } from '#angular/common/http';
import { BaseService } from './base.service';
#Injectable()
export class ProfileServices{
constructor(){};
userDetailsBS = new BehaviorSubject<any>('original value');
userDetails= this.userDetailsBS.asObservable();
isLoggedIn:boolean;
loginlogout(userDetails:any , isLoggedIn:boolean){
this.userDetails=userDetails;
this.userDetailsBS.next(this.userDetails);
console.log("Value of user details set in profile service",this.userDetails); //debug
console.log(".getValue() method:",this.userDetailsBS.getValue()); //debug
this.isLoggedIn=isLoggedIn;
}
getUserDetails(){
return this.userDetailsBS.getValue();
}
}
Post login from the header-component.ts i call the loginlogout method in the profile service to set the values. I also tried to access the value passed to the shared Service using the getUserDetails which shows that the userDetails object is passed correctly to the shared service.
The issue arises when i try to access the data from the profile component-
export class ProfileT1Component implements OnInit {
userDetails:any;
constructor(
public profileService: ProfileServices){
this.profileService.userDetails.subscribe((result)=>{
console.log(result);
this.userDetails=result;
console.log("received user details in profile component constructor: ", this.userDetails);
})
}
}
the result still shows "original value" and not the updated value. Is this wrong approach altogether or am i handling the observables incorrectly. Help would be much appreciated.

You need to make a couple of changes in your service to make it work. Add providedIn: root and remove all declarations from other modules. Secondly, you do not need this.userDetailsBS.asObservable() and you can use the subscribe directly on userDetailsBS. Your code will look something like the following.
Service:
#Injectable({
providedIn: 'root'
})
export class ProfileServices {
constructor() {}
userDetailsBS = new BehaviorSubject<any>('original value');
isLoggedIn: boolean;
loginlogout(userDetails: any, isLoggedIn: boolean) {
this.userDetailsBS.next(userDetails);
this.isLoggedIn = isLoggedIn;
}
getUserDetails() {
return this.userDetailsBS.getValue();
}
}
Component:
export class ProfileT1Component implements OnInit {
userDetails: any;
constructor(public profileService: ProfileServices) {
this.profileService.userDetailsBS.subscribe((result) => {
console.log(result);
this.userDetails = result;
console.log('received user details in profile component constructor: ', this.userDetails);
});
}
}

the implementation seems to be OK
(except you should make the BehaviorSubject private and expose only the observable)
probably you have multiple instance of the service.
try to add :
#Injectable({
providedIn: 'root',
})
and remove the service declaration from all the modules provider array
https://angular.io/guide/singleton-services

Related

Using a base component to unsubscribe from all observable subscriptions

In an Angular app, we're using a Base Component to unsubscribe from most of our app's observable subscriptions. If a component subscribes to an observable, that component will extend the Base Component. My thought is that this keeps observable subscriptions alive until the entire application is destroyed, rather than until each component is destroyed:
base.component.ts:
import { Subject } from 'rxjs';
import { OnDestroy, Component } from '#angular/core';
export abstract class BaseComponent implements OnDestroy {
protected unsubscribe$ = new Subject<void>();
ngOnDestroy(): void {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}
}
the-rest-of-our-components.ts:
import { Component, OnInit } from '#angular/core';
import { MyService } from 'src/app/services/my.service';
import { BaseComponent } from '../base/component/base-component';
export class myComponent extends BaseComponent implements OnInit {
myProperty: string;
constructor(
private myService: MyService,
) {
super();
}
ngOnInit(): void {
this.myService.doStuff$
.pipe(takeUntil(this.unsubscribe$)) // take until baseComponent's unsubscribe$
.subscribe((data) => {
this.myProperty = data;
});
}
If many components extend BaseComponent and utilize its unsubscribe$ Subject, does that mean all of my subscriptions only get unsubscribed when the entire application is destroyed (aka user closes the tab or navigates away from web app, thus Base Component is destroyed), rather than when individual components get destroyed?
Is this a strategy you've seen before, and is it advisable? If it works as I'm assuming, this means all of our subscriptions across the application stay active until the whole app is destroyed. I see how, depending on our needs, that might be a bad thing or a good thing.
Bonus question: is Base Component going to act like a singleton? AKA if multiple components simultaneously extend BaseComponent, will they all be using the same instance of unsubscribe$ or will there be multiple instances of unsubscribe$ (one per component)?
I assumed this would work, but we all know where assumptions get you, so I made a test: https://stackblitz.com/edit/angular-ivy-ueshwz?file=src/app/extended/extended.component.ts
It works, as in subscriptions get destroyed when individual components get destroyed.
We make a service that holds a subject we can subscribe to, and a value we can change with the subscription, to show that the subscription exists:
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs/internal/Subject';
#Injectable({ providedIn: 'root' })
export class UpdateService {
subject = new Subject<void>();
value = 0;
}
In the root we'll fire the subject every second, and have a component that we can toggle on and off
export class AppComponent implements OnInit {
extCompOpen = true;
constructor(public update: UpdateService) {}
ngOnInit() {
interval(1000).subscribe(() => this.update.subject.next());
}
}
<app-extended *ngIf="extCompOpen"></app-extended>
<button (click)="extCompOpen = !extCompOpen">Toggle Component</button>
<p>This counter will keep going up as long as the subscription exists:</p>
<p>{{ update.value }}</p>
Then we'll use an extended component to tick that value up by 1 with a subscription
export class ExtendedComponent extends BaseComponent implements OnInit {
constructor(private update: UpdateService) {
super();
}
ngOnInit() {
this.update.subject.pipe(takeUntil(this.unsubscribe$)).subscribe(() => {
this.update.value++;
});
}
}
<p>Extended component exists!</p>
Neat, closing the component stops the ticker, so the subscription has been unsubscribed.
Bonus question: BaseComponent does not act like a singleton, when you create an instance of an object it does not create shared instances of parent classes. Extending a class just adds properties and methods to that instance.
I'm not sure if I would recommend this, if someone overrides ngOnDestroy() they need to call super.ngOnDestroy(), which may be easy to forget. It's only four lines of code, probably better to explicitly put it in every component that needs it. Manual subscriptions should be pretty rare anyway, if you're using the async pipe.
I solved this in a project doing the following:
In base.component:
private sub: any = {};
ngOnDestroy() {
Object.keys(this.sub).map(item => {
this.sub[item].unsubscribe();
})
}
Then in any component that extends:
this.sub.myService = this.myService.doStuff$.subscribe(......
With this method, the subscription stay active until the component is destroyed.

Angular4 - let multiple unrelated components notify each other of the problem of updating data, and whether there is a cleaner coding method?

I have encountered a project in progress, let multiple unrelated components notify each other of the update data, is there a cleaner coding method?
There are 3 components (more likely later) and a common-data component. They have no parent-child relationship with each other and only show on the same screen.
The desired effect is to press the button of any component, update the contents of common-data, and notify yourself and other components to fetch new messages from common-data.
At present, my approach is to use Rx's Observable and Subscription, but they must be imported in the component.ts and service.ts files of each component, and a lot of duplicate code appears, it is very messy, I don't know what is better. practice?
Thanks!
My code :
The sample name is test-a-comp (a.b.c and so on, the code is the same)
test-a-comp.html
<p>
{{ownMessage}}
</p>
<button (click)="sendChange()">update</button>
test-a-comp.component
import { Component, OnInit } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
import { CommonData } from '../common-data/common-data';
import { TestACompService } from './test-a-comp.service';
import { TestBCompService } from '../test-b-comp/test-b-comp.service';
import { TestCCompService } from '../test-c-comp/test-c-comp.service';
#Component({
selector: 'app-test-a-comp',
templateUrl: './test-a-comp.component.html',
styleUrls: ['./test-a-comp.component.css']
})
export class TestACompComponent implements OnInit {
subscription: Subscription;
ownMessage;
constructor(
private testAService: TestACompService,
private testBService: TestBCompService,
private testCService: TestCCompService,
) {
this.subscription = this.testAService.getMessage()
.subscribe((test) => {
CommonData.message = test;
});
this.subscription = this.testBService.getMessage()
.subscribe(() => {
this.ownMessage = CommonData.message;
});
this.subscription = this.testCService.getMessage()
.subscribe(() => {
this.ownMessage = CommonData.message;
});
}
ngOnInit() {
}
sendChange() {
this.testAService.sendMessage();
}
}
test-a-comp.service:
import { Injectable } from '#angular/core';
import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';
import {Subscription} from 'rxjs/Subscription';
#Injectable()
export class TestACompService {
subscription: Subscription;
private subject = new Subject<any>();
constructor() {
}
getMessage(): Observable<any> {
return this.subject.asObservable();
}
sendMessage(): void {
this.subject.next('update message from A');
}
}
As far as i understand & you've mentioned in the above, there is a button in one of the component (test-a-component.html). If you update the button, you need to send message to other components which are subscribed.
The Components which have no Parent-Child relationship can communicate via a service:
Create a single service file (In your case: test-a-comp.service)
Create a Subject on what data you need to communicate via this service:
export class testMessageService {
constructor() {}
// Observable string sources
private message = new Subject<string>();
//Observable string streams
testMessage$ = this.message.asObservable();
constructor() {}
// Method to send message when a button is clicked
sendMessage(message: string) {
this.message.next(message);
}
/* You don't need "getMessage()" method as you've already subscribed to
the observables. There subscribed Observable string streams are
injected in your components (As below point 3) to display / do other
operation on the message. */
}
In your other Components, where you want to receive messages, do the following:
export class TestComponent 1 {
myMessage1: string;
constructor(private TestMessageService: testMessageService) {}
TestMessageService.testMessage$.subscribe(message => {
this.myMessage1 = message;
});
}
export class TestComponent 2 {
myMessage2: string;
constructor(private TestMessageService: testMessageService) {}
TestMessageService.testMessage$.subscribe(message => {
this.myMessage2 = message;
});
}
export class TestComponent 3 {
myMessage3: string;
constructor(private TestMessageService: testMessageService) {}
TestMessageService.testMessage$.subscribe(message => {
this.myMessage3 = message;
});
}
For more information/guidance refer Component interaction via a common
service: https://angular.io/guide/component-interaction
Hope this helps!

How to use cloud firestore to get data in an ionic 4 app

I was following a tutorial where a guy showed how to build a news app with ionic 4 using the news API. I also want to create a news app that shows summarized news from different sources on a particular topic but the problem is that I am thinking of using the Firebase cloud firestore for this purpose instead of using the news API and I can't figure out how to get the data from the firestore collection. You can look at the following code for reference.
news.page.ts
import { Component, OnInit } from '#angular/core';
import { NewsService } from '../news.service';
import { Router } from '#angular/router';
#Component({
selector: 'app-news',
templateUrl: './news.page.html',
styleUrls: ['./news.page.scss']
})
export class NewsPage implements OnInit {
data: any;
page = 1;
constructor(private newsService: NewsService, private router: Router) {}
ngOnInit() {
this.newsService
.getData(`top-headlines?country=us&category=business&pageSize=5&page=${this.page}`)
.subscribe(data => {
console.log(data);
this.data = data;
});
}
onGoToNewsSinglePage(article) {
this.newsService.currentArticle = article;
this.router.navigate(['/news-single']);
}
}
news.service.ts
import { Injectable } from '#angular/core';
import { environment } from '../environments/environment';
import { HttpClient } from '#angular/common/http';
const API_URL = environment.apiUrl;
const API_KEY = environment.apiKey;
#Injectable({
providedIn: 'root'
})
export class NewsService {
currentArticle: any;
constructor(private http: HttpClient) { }
getData(url) {
return this.http.get(`${API_URL}/${url}&apiKey=${API_KEY}`);
}
}
news.page.html
<ion-header>
<ion-toolbar>
<ion-title>News</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-card *ngFor="let article of data?.articles" (click)="onGoToNewsSinglePage(article)">
<!-- <ion-img [src]="article.urlToImage"></ion-img> -->
<ion-card-content>
<ion-card-title>{{article.title}}</ion-card-title>
<p>{{article.description}}</p>
</ion-card-content>
</ion-card>
</ion-content>
I have installed the angularfire 2 plugin in my project, imported all the files in app.module.ts and also prepared a config file for all the Firebase details. I just want to know how to get the data from Firebase collection and bind it to the html code.
Instead of calling your service this.newsService.getData(...) you will have to use firebase service public AngularFirestore from angularfire2/firestore . Here is an example:
Import the service and inject it in your component news.page.ts:
import {AngularFirestore} from 'angularfire2/firestore';
...
data: any;
constructor ( public db: AngularFirestore ) {
}
...
To retrieve a single post, create the function
getPostEntry ( postTitle: string ): Observable<any> {
return this.db.collection<any> ( "posts" , ref => ref.where ( 'title' , '==' , postTitle ) ).valueChanges ();
}
This will search all entries in your firestore collection called "posts" with attribute title being your postTitle.
Simillarly to retrieve all posts
getAllPosts (): Observable<any> {
return this.db.collection<any>( "post" ).valueChanges ();
}
Then invoke the functions and consume the observables. For instance you can do it in your ngOnInit:
ngOnInit() {
this.getAllPosts().subscribe((data)=>{
this.data = data;
console.log(data);
});
}
Now you have your data in your variable data, you just have to draw it in your html as you would normally do. Have in mind that it will probably be an array with all your posts (if there are any).
here is a gist with the code I edited from your class:
https://gist.github.com/HugoJBello/73fb3c5c0964f29934a2d8021efb128d
EDIT
renamed firebase collection and added subscription to observable

Global object in angular2

How can I create a global object in angular2. I am collecting data from one page to another means page by page(may be for 4-5 pages) using navParams in ionic2. but want to store it in global object & finally submit it. I have tried it using global provider but haven't got most of it & not getting any clue also. Please any suggestion.
As with my question ionic community suggest to use global provider. i did use global provider to hold my 7 step form data and final submit on last step.
i used with ionic 3.
provider code:
import { Injectable } from '#angular/core';
#Injectable()
export class NcConnectionProvider {
public statecode:any;
public districtcode:any;
constructor() {
//console.log('Hello NcConnectionProvider Provider');
}
}
Page.ts
import { Component } from '#angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { NcConnectionProvider } from '../../providers/nc-connection/nc-connection';
#IonicPage()
#Component({
selector: 'page-nc-connection',
templateUrl: 'nc-connection.html',
})
export class NcConnectionPage {
public distbform:FormGroup;
public statedata:any;
public districtdata:any;
constructor(public navCtrl: NavController,
public navParams: NavParams,
public ncdatashare: NcConnectionProvider) {
}
locateDistb(form: NgForm){
this.ncdatashare.statecode = this.distbform.value.state;
this.ncdatashare.districtcode = this.distbform.value.district;
}
}
you can inject provider to multiple pages as you want and access your global variable/object. like in example value set to this.ncdatashare.statecode = this.distbform.value.state; now you can access this.ncdatashare.statecode to other page but provider must inject there.
Make sure to provide some sample code to elaborate the problem better. Moreover, following is the way you can follow to collect the data in single object.
Create a service with a variable and its getter setter. Inject this service in your component and set the data wherever needed and later use its get method to collect it back again.
#Injectable()
export class DataService{
private _data: any;
get data() {
return this._data;
}
set data(data: any) {
this._data = data;
}
}
and in side your component
#Component({
// configurations
})
export class LoginComponent {
constructor(public dataService: DataService) {
this.dataService.data = { userAge: 37 }
}
}
and where you need to submit this data, just use the getter to collect it back.
let finalData = this.dataService.data;
Not sure about your use case. But I hope it might help you.

Angular 2 Injectable is missing object

I've created a simple injectable that I'm using for mock testing until I feel like Implementing my provider to retrieve user data. It looks like this:
import { Injectable } from '#angular/core';
#Injectable()
export class Identity {
public userInfo: {id: 1};
}
I request it in my a a component like so:
export class TabsPage {
constructor(public identity: Identity) {
console.log(this.identity.userInfo);
}
}
The Identity is Injected fine but userInfo is undefined, what is going on here? How can I pass data through my provider?
You are defining a type (typescript type) for user info, not a value, thus it is undefined. public userInfo = { id: 1 }; initializes its value. (notice you have a colon : there in your question, not a =.

Categories

Resources