How to extract values from BehaviouralSubject in Angular - javascript

I created an Account Service, for my angular application, and it handles the Login and logout. and this works perfectly. But I am having an issue, I used BehaviourSubject Observables to render the variables.
I am trying to retrieve the loginstatus value, and the username string on the component using the service, but the observable is returning an object, and I am having problems extracting the string out of the object. How can I extract variable types from Behavioursubject observables?
The Account Service...
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable, Subject, BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';
import { Router } from '#angular/router';
#Injectable({
providedIn: 'root'
})
export class AccountService {
private baseUrlLogin:string = "/api/account/login";
private loginStatus = new BehaviorSubject<boolean>
(this.checkLoginStatus());
private userName = new BehaviorSubject<string> localStorage.getItem['username']);
constructor(
private http:HttpClient,
private router: Router
){}
login(username:string, password:string){
return this.http.post<any>(this.baseUrlLogin,{username, password}).pipe(
map(result => {
if(result && result.token){
localStorage.setItem('loginStatus', '1');
localStorage.setItem('username', result.username),
}
return result;
})
);
}
logout(){
this.loginStatus.next(false);
localStorage.setItem('loginStatus', '0');
localStorage.removeItem('username'),
localStorage.clear();
//now redirect to the login page...
this.router.navigate(['/login']);
console.log("logged out successfully...");
}
get isLoggedIn(){
return this.loginStatus.asObservable();
}
get currentUserName(){
return this.userName.asObservable();
}
}
The Component Using the Service
import { Component, Input, OnInit } from '#angular/core';
import { AccountService } from 'src/app/services/account.service';
import { Observable } from 'rxjs';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
pgtitle:string = "SCB Dashboard";
loginStatus$ : Observable<boolean>;
username$ : Observable<string>;
constructor(
private acc:AccountService
){}
ngOnInit() {
this.loginStatus$ = this.acc.isLoggedIn;
this.username$ = this.acc.currentUserName;
console.log(this.loginStatus$); //here it ruturns an object
console.log(this.username$); //and here too...
}
}
The console.log() returns an object, but how do I retrieve the variables, and work with them in the controller, since they are of type observable?

Rxjs BehaviourSubject has an asObservable() method, you can generate your observable from it
let sourceSubject = new BehaviourSubject();
let source$ = sourceSubject.asObservable();
source$.subscribe(result => // Your data)
// Update the BehaviourSubject
sourceSubject.next(newValue);

You need to subscribe to the observable to get the value out of it:
this.loginStatus$.subscribe(value => {
console.log(value); // access value
});

try this:
get isLoggedIn(){
return this.loginStatus.value;
}
get currentUserName(){
return this.userName.value;
}

This should also work:
ngOnInit() {
this.loginStatus$ = this.acc.isLoggedIn.pipe(
tap(status => console.log(status))
);
this.username$ = this.acc.currentUserName.pipe(
tap(userName => console.log(userName))
);
}
Assuming that you subscribed somewhere, such as with an async pipe.

Related

Have realtime updates for a single Firestore document

There is a lot of documentation and examples of firestore collections getting realtime updates. However, there is very little for those who wish to have a single document have real time updates. I want to have a single document (an item), on a page where only the item will be viewed and manipulated and any changes to document, will have realtime updating.
Here is my component that wants to do stuff with the item:
import { Component, OnInit } from '#angular/core';
import { ItemsService } from '../shared/items.service';
import { ActivatedRoute, Router } from '#angular/router';
#Component({
selector: 'app-view-item',
templateUrl: './view-item.component.html',
styleUrls: ['./view-item.component.css']
})
export class ViewItem implements OnInit {
item;
private sub: any;
constructor(
// Service used for Firebase calls
private itemsService: ItemsService,
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
// Item retrieved from */item/:id url
this.sub = this.route.params.subscribe(params => {
this.getItem(params['id']);
});
}
getItem = (id) => {
this.itemsService.getItem(id).subscribe(res => {
console.log(res);
this.item = res;
console.log(this.item);
});
}
And the service it uses for calls:
import { Injectable } from '#angular/core';
import { AngularFirestore, AngularFirestoreDocument } from '#angular/fire/firestore';
#Injectable({
providedIn: 'root'
})
export class ItemsService {
constructor(
private firestore: AngularFirestore
)
getItem(id) {
return this.firestore.collection('items').doc(id).snapshotChanges();
}
}
The log I get for console.log(this.item) is undefined. Calling this.item in the console returns the same. I am unsure of how to proceed and would appreciate any guidance. Logging res in the console returns a byzantine object. Perhaps that's how I access the item, but if so, why is it not saved in this.item and how do I access the item's values?
snapshotChanges returns an observable of actions, not the actual value.
You should extract the value with action.payload.doc.data():
So your code should look like the following example.
getItem(id) {
return this.firestore.collection('items').doc(id).snapshotChanges()
.pipe(
map(actions => actions.map(a => {
const data = a.payload.doc.data();
const id = a.payload.doc.id;
return { id, ...data };
})
);
}
Or you can use valueChanges of doc.
getItem(id) {
return this.firestore.collection('items').doc(id).valueChanges();
}

Extracting data from model to variables

I'm new to typescript and angular and I was trying to fetch some data from firebase using angularfire2 and assign it to variables to use in some other functions later. I'm only familiar with javascript dot notation where I access members of the object using dot notation seems like it doesn't work with angular can somebody please help me with extracting data from the model to variables, please
I'm still having a hard time understanding Observable and subscribes too.
code
model
export class Reacts {
sad?: number;
happy?: number;
neutral?: number;
}
service
import { Injectable } from "#angular/core";
import {
AngularFirestore,
AngularFirestoreCollection,
AngularFirestoreDocument
} from "angularfire2/firestore";
import { Reacts } from "../models/reacts";
import { Observable } from "rxjs";
#Injectable({
providedIn: "root"
})
export class ReactService {
mapCollection: AngularFirestoreCollection<Reacts>;
reacts: Observable<Reacts[]>;
constructor(public afs: AngularFirestoreDocument) {
this.reacts = this.afs.collection("reacts").valueChanges();
}
getItems() {
return this.reacts;
}
}
component
import { Component, OnInit } from "#angular/core";
import { Reacts } from 'src/app/models/reacts';
import { ReactService } from 'src/app/services/react.service';
#Component({
selector: "app-reacts",
templateUrl: "./reacts.component.html",
styleUrls: ["./reacts.component.css"]
})
export class ReactsComponent implements OnInit {
react: Reacts[];
happy: number;
sad: number;
neutral:number;
constructor(private reactsService: ReactService ) {}
ngOnInit(): void {
this.reactsService.getItems().subscribe(reacts => {
this.react = reacts;
console.log(reacts); //this works print an array object of data from database
this.happy= reacts.happy// what i'm trying to achieve
});
}
}
Ok, I'll break it down for you. You are trying to access .happy but it is actually an array of React[]
ngOnInit(): void {
this.reactsService.getItems().subscribe((reacts:Reacts[]) => { // Note I have defined its model type
this.react = reacts;
console.log(reacts); //this works print an array object of data from database
//this.happy= reacts.happy // Now VS code will show you error itself
this.happy = reacts[0].happy;
});
}
The power of typscript comes as it is strongly typed language. If you'll make changes as below in service, the VS Code will itself explain you the error:
export class ReactService {
mapCollection: AngularFirestoreCollection<Reacts>;
reacts: Observable<Reacts[]>;
constructor(public afs: AngularFirestoreDocument) {
this.reacts = this.afs.collection("reacts").valueChanges();
}
getItems(): Observable<Reacts[]> { // added return type
return this.reacts;
}
}
Once I provide return type of getItems() , you dont even have to define type in .subscribe((reacts:Reacts[]) as I have done in your component.

_co.photo is undefined console error and error context, but code works as expected

I got problem with angular component.
When I make my component with selector, it works as expected: execute httpget, and render photo with title.
But in console I got two errors:
ERROR TypeError: "_co.photo is undefined"
View_PhotoHolderComponent_0 PhotoHolderComponent.html:2
and
ERROR CONTEXT
...
PhotoHolderComponent.html:2:8
View_PhotoHolderComponent_0 PhotoHolderComponent.html:2
I got html:
<div class="photo-holder">
<h2>{{photo.title}}</h2>
<img src="{{photo.url}}">
</div>
and ts:
import { Component, OnInit } from '#angular/core';
import { Photo } from './photo'
import { PhotoDeliveryService } from '../photo-delivery-service.service'
#Component({
selector: 'app-photo-holder',
templateUrl: './photo-holder.component.html',
styleUrls: ['./photo-holder.component.css']
})
export class PhotoHolderComponent implements OnInit {
photo:Photo
constructor( private photoService : PhotoDeliveryService) {
}
ngOnInit() {
this.photoService.getRandomPhoto().subscribe((data: Photo) => this.photo = {...data})
}
}
and service :
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Photo } from './photo-holder/photo'
#Injectable({
providedIn: 'root'
})
export class PhotoDeliveryService {
value : Number
url : string
constructor(private http: HttpClient) {
this.url = "https://jsonplaceholder.typicode.com/photos/";
this.value = Math.floor(Math.random() * 10) + 1;
}
getRandomPhoto() {
return this.http.get<Photo>(this.getUrl())
}
getUrl(){
return this.url + this.value;
}
}
I suspect that could be made by binding property before query results was returned.
How can I rid off this problem, can I wait for this query, or this is different kind of problem ?
You are getting the error because before your service could resolve, the template bindings are resolved and at that time photo object is undefined.
first thing, you can initialize the photo object but then you might have to detect the changes using ChangeDetectorRef to reflect the value returned by the service.
photo:Photo = {
title:'',
url:''
};
constructor( private photoService : PhotoserviceService, private cdr:ChangeDetectorRef) {
}
ngOnInit() {
this.photoService.getRandomPhoto().subscribe((data: Photo) => {
this.photo = data;
this.cdr.detectChanges();
});
}

Passing data between components using a service Angular

There are many examples around the web on this subject but none of them helped me. This is the scenario: I've got 2 components and a service. The two components aren't parent/children but are 2 independent components. One of them has a list of names, the other should load a table when one of the names is clicked. This is my home.html with both components
<div class="material-docs-app">
<div class="docs-primary-header">
<h1>Yep!</h1>
</div>
<div fxLayout="row" fxLayout.xs="column" class="component-layout-body">
<app-heroes-sidenav></app-heroes-sidenav>
<app-heroes-table #heroesTable fxFlex="1 2 calc(15em + 20px)" style="width: 100%"></app-heroes-table>
</div>
</div>
Heroes sidenav component:
<div *ngIf="loadingData == true">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
<nav *ngIf="loadingData == false">
<p *ngFor="let item of heroesNames.results let i = index" [attr.data-index]="i">
<button mat-button (click)="getHero(i)">
{{item.name}}
</button>
</p>
</nav>
On click getHero() is called correctly. This is the sidenav component ts:
import { Component, OnInit, Input } from '#angular/core';
import {SwCharactersServiceService} from '../sw-characters-service.service';
import {HeroesTableComponent} from '../heroes-table/heroes-table.component';
#Component({
selector: 'app-heroes-sidenav',
templateUrl: './heroes-sidenav.component.html',
styleUrls: ['./heroes-sidenav.component.css']
})
export class HeroesSidenavComponent implements OnInit {
heroesNames: any;
heroData:any;
loadingData = true;
#Input() heroesTable: HeroesTableComponent;
constructor(private _swService: SwCharactersServiceService) { }
ngOnInit() {
this.getHeroes();
}
getHeroes() {
this._swService.getCharacters().then(result => {
this.loadingData = false;
this.heroesNames = result;
});
}
getHero(index) {
this._swService.getHero(index);
}
}
and this is the service:
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import 'rxjs/add/operator/map'
import {Observable} from 'rxjs/Observable';
#Injectable({
providedIn: 'root'
})
export class SwCharactersServiceService {
param:any;
constructor(private http: HttpClient) { }
getCharacters(): Promise<any[]> {
return this.http.get<any[]>("https://swapi.co/api/people/")
.toPromise()
.then(result => result)
.catch(this.handleError);
}
getHero(index): Observable<any>{
console.log(index);
let headers = new HttpHeaders();
headers.append('Content-Type', 'application/json');
return this.http.get("https://swapi.co/api/people/" + index, {
headers: headers
}).map(res => res );
}
private handleError(error: any): Promise<any> {
console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
}
}
I can correctly see the console.log(index) but the request doesn't work. There is no request initiated in chrome console network tab.
This is the component with the table:
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
import {SwCharactersServiceService} from '../sw-characters-service.service';
#Component({
selector: 'app-heroes-table',
templateUrl: './heroes-table.component.html',
styleUrls: ['./heroes-table.component.css']
})
export class HeroesTableComponent implements OnInit {
loadingData = true;
heroData :any;
subscription: Subscription;
constructor(private _swService: SwCharactersServiceService) {
this.subscription = this._swService.getHero(1).subscribe(result => { this.heroData = result; });
console.log(this.heroData);
}
ngOnInit() {
}
ngOnDestroy() {
// unsubscribe to ensure no memory leaks
this.subscription.unsubscribe();
}
}
There are 2 problems now:
1) As you can see I wrote this._swService.getHero(1) without passing a dynamic param. How does it work? How can I pass the correct index?
2) The service doesn't fire and I haven't got any result.
Is there any other way to do that?
Thanks.
you can use BehaviourSubject to pass the index value and send the query request as the list is cliked
in the service
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
public index: BehaviorSubject<number> = new BehaviorSubject<number>(null);
in the sidenav component
getHero(index) {
this._swService.index.next(index);
}
in the hero table component
ngAfterViewInit(){
this._swService.index.subscribe(index=>{
if(index){
this._swService.getHero(index).subscribe(result => { this.heroData = result; });
}
})
}
You missed to subscribe to _swService.getHero(). If not subscribed to a method which returns an Observable, then it wont be invoked.
getHero(index) {
this._swService.getHero(index).subscribe(
(resp) => {
// manipulate your response here
console.log(resp);
},
(err) => {}
);
}

Firebase doesn't return value (Angular 2)

I try to retrieve data from my Firebase and it works, but JUST for console.log().
I can't return a value into a var...
As I'm working with Angular 2 & Typescript I have
a Service:
import {Injectable} from "angular2/core";
import 'rxjs/Rx';
import {Observable} from "rxjs/Observable";
declare var Firebase: any;
#Injectable()
export class DataService {
getAllData() {
const firebaseRef = new Firebase('https://XYZ.firebaseio.com/path/user')
firebaseRef.on("value", function (snapshot) {
console.log(snapshot.val()); // THIS WORKS!
return snapshot.val(); // THIS DOES NOT WORK!
});
}
and a Component:
#Component({
templateUrl: 'templates/user.tpl.html',
providers: [DataService],
})
export class UserComponent implements OnInit{
userData: any;
constructor(private _dataService: DataService){}
ngOnInit():any {
this.userData = this._dataService.getAllData();
console.log(this.userData); // THIS DOES NOT WORK: UNDEFINED
}
If i run that, I get nothing for my userData var... And I can't figure how to fix that. I thought I would need an Observable but I failed, whatever I tried to do...
Can someone help?
You need to wrap your call into an observable since Firebase is event-driven:
getAllData() {
const firebaseRef = new Firebase('https://XYZ.firebaseio.com/path/user')
return Observable.create((observer) => {
firebaseRef.on("value", function (snapshot) {
console.log(snapshot.val());
observer.next(snapshot.val());
});
});
}
This way you will be able to receive value by subscribing on the returned observable:
ngOnInit():any {
this._dataService.getAllData().subscribe(data => {
this.userData = data;
});
}

Categories