RxJS Subject never pushed the first next - javascript

Im having issues with subjects and a comment system Im building out. It works fine but the first comment that is posted never shows up until someone posts another comment, all other comments work fine after this. I have tried with BehaviourSubject giving an initial value of null or "" and ReplaySubject but I think I might be missing something with how these work, is there anyway to have this work in realtime i.e when a user pushes a comment it goes to the server and is added back onto the stack without having to submit a second comment right away ?
import { Injectable } from '#angular/core';
import { Observable } from "rxjs/Observable"
import { BehaviorSubject } from "rxjs/BehaviorSubject"
// import {Subject} from "rxjs/Subject"
import { ServerAPI } from '../serverapi';
import { ReplaySubject } from 'rxjs/internal/ReplaySubject';
import { Subject } from 'rxjs/Subject';
#Injectable({
providedIn: 'root'
})
export class Comments {
private _comments = new Subject<any[]>();
$comments: Observable<any> = this._comments.asObservable();
constructor(private api: ServerAPI) {
this.$comments.subscribe(() => {
console.log('comments sub\'d');
});
}
async comment(threadID: string, commentData: any): Promise<any> {
await this.api.postComment(threadID, commentData);
const fetchComments = await this.api.getComments(threadID);
this._comments.next(fetchComments);
}
}
In my template I received the comments via subscribe li
this.comment.$comments.subscribe(chats => {
this.CommentData = chats;
});

You need to have a function to subscribe on the subject like this
getData() {
return this._comments.asObservable();
}
So this is how you subscribe to getData() in your component
this.service.getData().subscribe(x=> {
console.log(x);
});
Also subscribe in constructor like you done right now will only execute one
constructor(private api: ServerAPI) {
this.$comments.subscribe(() => {
console.log('comments sub\'d');
});
}

Related

How to display a navbar component after logging in without reloading the page in angular 12

My landing page does not show a navbar, but I want to display a navbar after logging in successfully. Currently, I'm able to show navbar if I do a full page reload after logging in successfully. I'm very sure that there is a better way than this approach.
app.component.html
<app-navbar></app-navbar>
<router-outlet></router-outlet>
login.component.ts
login(){
this.credentials = this.myForm.value;
if(this.credentials){
this.loginService.authenticate(this.credentials)
.subscribe(data => {
this.storageService.setLocalStorageItem('auth', JSON.stringify(data));
this.dataService.global.showNav = true;
this.sharedService.getProjectMetadata()
.subscribe(metadata => {
this.storageService.setLocalStorageItem('projectMetaData', JSON.stringify(metadata));
this.router.navigate(['/home']);
})
}, err => console.log(err));
} else {
console.log('Please enter your username and password');
}
}
data.service.ts
import { Injectable } from '#angular/core';
import { Subject, Subscription } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { IGlobal, IMessage } from '../../Shared/interfaces';
import { MessageCallback } from '../../Shared/types';
#Injectable({
providedIn: 'root'
})
export class DataService {
constructor() { }
date: string = (new Date()).toString();
global: IGlobal = {
showNav: false,
sessionTimedOut: false,
timezone: this.date.substring(this.date.indexOf('GMT')),
projectMetaData: {
name: ''
},
isAdmin: false,
auth: {
roles: {
admin: false,
developer: false
}
}
}
private handler: Subject<IMessage> = new Subject<IMessage>();
broadcast(type: string, payload: any){
this.handler.next({type, payload});
}
subscribe(type: string, callback: MessageCallback): Subscription {
return this.handler.pipe(filter(message => message.type === type), map(message => message.payload))
.subscribe(callback);
}
}
navbar.component.html
<mat-toolbar fxLayout="row" color="primary" *ngIf='showNavbar'></mat-toolbar>
navbar.component.ts
export class NavbarComponent implements OnInit {
user: IAuth = {};
showNavbar: boolean;
progressbar: number = 0;
constructor(
private storageService: StorageService,
private dataService: DataService
) {
this.showNavbar = this.dataService.global.showNav;
}
ngOnInit(): void {
this.user = JSON.parse(this.storageService.getLocalStorageItem('auth'));
if(this.user){
this.showNavbar = true;
}
}
}
Please help me out. Your help is highly appreciated. Thank you.
The problem lies here,
once authentication is completed successfully in login() function, it is not communicated to navbar.component.ts
showNavbar in navbar.component.ts is used to display/hide navbar template.
Though dataService.global.showNav is set to true, it will not trigger change detection in navbar components. Since it is copied to `showNavbar' only in constructor.
So before login, navbar is already loaded, probably with showNavbar evaluated as false, and never recomputed until page reload.
During pagereload value is read from localStorage which provides latest value to showNavbar
I have two suggestions{S1,S2} to fix this.
S1.
1.broadcast via subject from login component about successful login status
2.And subscribe for that status in navbar component and upon subscription , control rendering of navbar template
3. Looks like as per your business logic,broadcast and subscribe functions in dataservice does that for you in IMessage type subject.
4. Consider example code below and update according to your application.
For eg:
login.component.ts
this.dataService.broadcast('authSuccess',{auth:'successful'})
in navbar.component.ts
OnInit() {
this.dataService.subscribe('authSuccess',setShowNavbar);
}
setShowNavbar() {
this.showNavbar=true;
}
S2:
This is not a clean approach and difficult for tracking, but it works for quick and dirty solutions.
navbar.component.html
<mat-toolbar fxLayout="row" color="primary" *ngIf="dataService.global.showNav"></mat-toolbar>
This case will run change detection whenever value in dataService.global.showNav is updated and evaluate ngIf accordingly.
Note: Better to add a small working proto in stackblitz/jsfiddle/codesandbox etc when posting questions in public forum. So it will be easier for everyone to identify exact problem and arrive on specific solutions quickly.

Is it possible to use rxjs take operator to limit observables returned from a service?

I am very new to angular and i'm learning by working on website that requires me to list a limited list of 4 cars on the home page, so i created a service that fetches all the cars as shown below.
import { Response } from '#angular/http';
import { Car } from '../classes/car';
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import 'rxjs/add/operator/map';
#Injectable()
export class CarService {
rootURL = 'http://localhost:3000/cars';
constructor(private _http: HttpClient) {}
getCars(): Observable<Car[]> {
return this._http.get<Car[]>(this.rootURL);
}
}
and then on my featured cars component, i access the service on component initialization,like shown below
constructor(private _carService: CarService ) { }
ngOnInit() {
this._carService.getCars()
.take(4)
.subscribe(data => this.cars = data,
error => this.errorMessage = <any>error);
}
So the featured cars components works but it gives me a list of all the cars from the API while i only need to show 4 cars, i have tried using Rxjs 'take` operator and doesn't seem to work? where might be going wrong?
You can use JavaScript's slice operator.
ngOnInit() {
this._carService.getCars()
.take(4)
.subscribe(
data => this.cars = data.slice(0, 4),
error => this.errorMessage = <any>error);
}
Emit provided number of values before completing.
Why use take
When you are interested in only the first set number of emission, you want to use take. Maybe you want to see what the user first clicked on when he/she first entered the page, you would want to subscribe to the click event and just take the first emission. There is a race and you want to observe the race, but you're only interested in the first who crosses the finish line. This operator is clear and straight forward, you just want to see the first n numbers of emission to do whatever it is you need.
taken from learn-rxjs
the 'take' operator isn't used to limit the number of data from the returned array. You could use a normal JavaScript function Array.slice to manipulate the array.
ngOnInit() {
this._carService.getCars()
.subscribe(data => this.cars = data.slice(0, 4),
error => this.errorMessage = <any>error);
}

Angular 2 - http.get never being call

Im learning Angular 4 and have run into a problem that I cannot seem to find a solution to. Here is the context:
I have a simple app that displays info about US Presidents.
The backend is a rest API provided by webapi...this works fine.
The front end is an Angular app.
Ive distilled the problem down to 3 components, 1 data service and 1 model.
Here is the model:
export class President {
constructor(
public id: number,
public presidentName: string,
public presidentNumber: number,
public yearsServed: string,
public partyAffiliation: string,
public spouse: string) {}
}
The 3 components are
1. SearchComponent
2. HomeComponent
3. PresidentComponent
When the app bootstraps, it loads the ApplicationComponent - it is the root component:
import { Component, ViewEncapsulation } from '#angular/core';
#Component({
selector: 'my-app',
template: `
<search-component></search-component>
<home-component></home-component>
`
})
export class ApplicationComponent {}
PresidentComponent is a child component of HomeComponent. When home component loads, it makes an http call to the api to get a list of presidents and renders 1 presidentComponent for each row returned. This works fine.
What Im trying to do is implement a search feature where the dataService exposes an EventEmitter and provides the search method as shown here:
import { Injectable, EventEmitter, Output } from '#angular/core'
import { President } from '../models/President'
import { Http } from '#angular/http';
import 'rxjs/add/operator/toPromise';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class DataService {
constructor(private http: Http) {
}
searchEvent: EventEmitter<any> = new EventEmitter();
// simple property for the url to the api
get presidentUrl {
return "http://localhost:51330/api/presidents";
}
search(params: any): Observable<President[]> {
let encParams = encodeParams(params);
console.log(encParams);
return this.http
.get(this.presidentUrl, {search: encParams})
.map(response => response.json());
}
getParties(): String[] {
return ['Republican', 'Democrat', 'Federalist', 'Whig', 'Democratic-Republican', 'None'];
}
getPresidents(): Observable<President[]> {
return this.http.get(this.presidentUrl)
.map(response => response.json());
}
}
/**
* Encodes the object into a valid query string.
* this function is from the book Angular2 Development with TypeScript
*/
function encodeParams(params: any): URLSearchParams {
return Object.keys(params)
.filter(key => params[key])
.reduce((accum: URLSearchParams, key: string) => {
accum.append(key, params[key]);
return accum;
}, new URLSearchParams());
}
The Search Component houses the search form and when the search button is clicked, it executes the onSearch() function and calls emit on the data service:
onSearch(){
if(this.formModel.valid){
console.log('emitting event from search.ts');
this.dataService.searchEvent.emit(this.formModel.value);
}
}
Then, in the HomeComponent, I want to subscribe to this event and execute a search via the dataservice when it fires:
ngOnInit(): void {
//when component loads, get list of presidents
this.dataService.getPresidents()
.subscribe(
presidents => {
console.log('sub');
this.presidents = presidents;
},
error => console.error(error)
)
//when search event is fired, do a search
this.dataService.searchEvent
.subscribe(
params => {
console.log('in home.ts subscribe ' + JSON.stringify(params));
this.result = this.dataService.search(params);
},
err => console.log("cant get presidents. error code: %s, URL: %s"),
() => console.log('done')
);
}
When I run this in the browser, everything works except the http call is never executed. If I subscribe() to the http.get call in the dataservice itself, it executes but why should I have to do that when I have a subscription being setup on the HomeComponent?
I want to handle the Observable in the HomeComponent and update the list of presidents that is being displayed in the UI based on the search result.
Any advice is greatly appreciated.
The entire idea of using EventEmitter in the service is not right. The EventEmitter should be used with #Output properties to send data from the child component to its parent.
Even though the EventEmitter is a subclass of the Subject, you shouldn't be using it in services. So inject the service into your component, subscribe to its observable in the component, and emit an event using EventEmitter to the parent component if need be.
In the code this.result = this.dataService.search(params);, result is an observable. You have not made a subscription.
In that case you should have used the async pipe to display the data.
Why not use Subject from rxjs. Here is what i am proposing:
DataService:
import { Observable, Subject } from "rxjs";
import 'rxjs/add/operator/catch';
#Injectable()
export class DataService {
private _dataSubject = new Subject();
constructor(private http: Http) {
this.http.get(this.presidentUrl)
.map(response => this._dataSubject.next(response.json()))
.catch(err => this._dataSubject.error(err));
);
}
// simple property for the url to the api
get presidentUrl {
return "http://localhost:51330/api/presidents";
}
search(params: any){
let encParams = encodeParams(params);
console.log(encParams);
this.http
.get(this.presidentUrl, {search: encParams})
.map(response => this._dataSubject.next(response.json()))
.catch(err => this._dataSubject.error(err));
}
getParties(): String[] {
return ['Republican', 'Democrat', 'Federalist', 'Whig', 'Democratic-Republican', 'None'];
}
getPresidents(): Observable<President[]> {
return this._dataSubject;
}
SearchComponent:
onSearch(){
if(this.formModel.valid){
console.log('emitting event from search.ts');
this.dataService.search(this.formModel.value);
}
}
With these modifications you should be able to have only 1 subscriber in homeCompoent and then get new data emitted every time onSearch() is called.

Subscribing to an observable before it's been populated

I'm trying to create a user profile service for an Angular 4 project and struggling a little with how to properly initialize and update the observable Profile object. Currently, when the user authenticates (via Firebase), AuthService passes the user's auth info to UserProfileService via the latter's initialize() function. UserProfileService then looks up the user's profile (or creates one if none exists yet) and populates a public observable with the profile.
The problem I'm running into is with other parts of the application trying to subscribe to the profile observable before all this has happened. I'd originally been initializing the observable via ...
public profileObservable: UserProfile = null;
... which of course resulted in a "subscribe() does not exist on null" error, so I changed it to ...
public profileObservable: Observable<UserProfile> = Observable.of();
This at least doesn't throw any errors, but anything that subscribes to profileObservable before I've mapped the Firebase object to it never updates.
Complete code for user-profile.service.ts below. I'm still struggling to get my head around how some of this is meant to work, so hopefully someone can shed some light. Thanks!
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { FirebaseListObservable, FirebaseObjectObservable, AngularFireDatabase } from 'angularfire2/database';
import * as firebase from 'firebase/app';
export class UserProfile {
$exists: Function;
display_name: string;
created_at: Date;
}
#Injectable()
export class UserProfileService {
private basePath: string = '/user-profiles';
private profileRef: FirebaseObjectObservable<UserProfile>;
public profileObservable: Observable<UserProfile> = Observable.of();
constructor(private db: AngularFireDatabase) {
// This subscription will never return anything
this.profileObservable.subscribe(x => console.log(x));
}
initialize(auth) {
this.profileRef = this.db.object(`${this.basePath}/${auth.uid}`);
const subscription = this.profileRef.subscribe(profile => {
if (!profile.$exists()) {
this.profileRef.update({
display_name: auth.displayName || auth.email,
created_at: new Date().toString(),
});
} else subscription.unsubscribe();
});
this.profileObservable = this.profileRef.map(profile => profile);
// This subscription will return the profile once it's retrieved (and any updates)
this.profileObservable.subscribe(profile => console.log(profile));
}
};
You must not change observable references once you constructed them. The way I found to properly decouple subscribers from the datasource is to use an intermediate Subject, which is both an observer and an observable.
Your code would look something like this:
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
...
export class UserProfileService {
...
public profileObservable = new Subject<UserProfile>();
constructor(private db: AngularFireDatabase) {
// This subscription now works
this.profileObservable.subscribe(x => console.log(x));
}
initialize(auth) {
const profileRef = this.db.object(`${this.basePath}/${auth.uid}`);
...
profileRef.subscribe(this.profileObservable);
}
};

RxJS Observable :'Skip is not a function' (or any other function)

I am getting a weird error with RxJS, getting a 'Method is not a function'.
I do have import the Observable library.
I already got Observable array with no errors raised.
But when I do a skip, or take,I get this error.
If I read correctly :
Returns an Observable that skips the first count items emitted by the
source Observable.
As my observable contains Article, it should skip x Article on the Observable right?
Here is the code
import { Component, OnInit } from '#angular/core';
import { Article} from '../../model/article';
import { ArticleService} from '../../model/article.service';
import { Router } from '#angular/router';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Component({
selector: 'app-liste-article',
templateUrl: './liste-article.component.html',
styleUrls: ['./liste-article.component.css']
})
export class ListeArticleComponent implements OnInit {
articles: Observable<Article[]>;
articlesPage: Observable<Article[]>;
selectedArticle: Article;
newArticle: Article;
page = 1;
itemPerPage = 2;
totalItems = 120;
constructor(private router: Router, private articleService: ArticleService) {
}
ngOnInit() {
this.articles = this.articleService.getArticles();
this.articlesPage = this.articles.skip((this.page - 1) * this.itemPerPage ).take(this.itemPerPage);
//this.articlesPage = this.articles.slice( (this.page - 1) * this.itemPerPage , (this.page) * this.itemPerPage );
}
onSelect(article: Article) {
this.selectedArticle = article;
this.router.navigate(['../articles', this.selectedArticle.id ]);
}
onPager(event: number): void {
console.log('Page event is' , event);
this.page = event;
this.articlesPage = this.articles.skip((this.page - 1) * this.itemPerPage ).take(this.itemPerPage);
console.log('tab' , this.articlesPage.count());
//this.articleService.getArticles().then(articles => this.articles = articles);
}
getArticles(): void {
this.articles = this.articleService.getArticles();
}
createArticle(article: Article): void {
//this.articleService.createArticle(article)
//.then(articles => {
// this.articles.push(articles);
// this.selectedArticle = null;
//});
}
deleteArticle(article: Article): void {
//this.articleService
// .deleteArticle(article)
// .then(() => {
// this.articles = this.articles.filter(b => b !== article);
// if (this.selectedArticle === article) { this.selectedArticle = null; }
//});
}
}
I tried to use it directly with .skip(1), but getting the same error.
I checked official documentations RxJS Observable and it looks OK to me. It seems the case is OK, as my call to the function. Both arryas are Observable so I should be able to use those function on one, and put the result in the other one right?
I don't see what is wrong here, probably a small detail, but can't see it myself (as I am starting with this, I see less details).
You'll want to import skip and take the same way as you have done with map and catch.
import 'rxjs/add/operator/skip';
import 'rxjs/add/operator/take';
For rxjs#6.5.5 I had to make the following changes:
import skip
import { skip } from "rxjs/operators";
change
return this.udpstream.asObservable().skip(1);
to
return this.udpstream.asObservable().pipe(skip(1));

Categories

Resources