Confusing behavior of a BehaviorSubject in my Angular App - javascript

I recently ran into a problem and can't really figure out what's wrong with my code at this point, hopefully someone of you can help me.
All I am trying to do is changing the value of my BehaviorSubject with a function but it isn't working out.
chat.service.ts
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable()
export class ChatService {
chatId = new BehaviorSubject<number>(0);
constructor() {
this.chatId.next(1);
}
changeChatId(chatId: number) {
console.log(chatId);
this.chatId.next(chatId);
}
}
So the subscribers get the default as well as the changed chatId from the constructor. But as soon as I try to change it with the changeChatId function nothing happens at all. The right id's get passed into the function I already debugged that but the line this.chatId.next(chatId) doesn't seem to do anything.
ADD
These are the other components the service is currently used in.
chat-message-list
import { Component, OnInit, Input} from '#angular/core';
import { ChatService } from "../../../shared/services/chat.service";
#Component({
selector: 'app-chat-message-list',
templateUrl: './chat-message-list.component.html',
styleUrls: ['./chat-message-list.component.css'],
providers: [ChatService]
})
export class ChatMessageListComponent implements OnInit {
chatId: number;
constructor(private chat: ChatService) { }
ngOnInit() {
this.chat.chatId.subscribe(
chatId => this.updateMessageList(chatId)
);
}
}
chat-item
import { Component, OnInit, Input} from '#angular/core';
import { User } from '../../../shared/models/user.model';
import { ChatService } from '../../../shared/services/chat.service';
#Component({
selector: 'app-chat-list-item',
templateUrl: './chat-list-item.component.html',
styleUrls: ['./chat-list-item.component.css'],
providers: [ChatService]
})
export class ChatListItemComponent implements OnInit {
#Input()
user: User;
constructor(private chat: ChatService) { }
ngOnInit() {
}
onChatItemSelected(){
this.chat.changeChatId(this.user.id);
}
}

You need to make your ChatService a singleton (shared) service. Add it to the providers of your ngModule. This allows all the components that use the ChatService to share the same service instance.
#NgModule({
providers: [ChatService]
})
And remove it from your components providers. When you are adding it to your components providers, that component gets its own instance of ChatService which can not be used by other components.

Related

Sending calculated data from one component to another without Services

I want to send the value from one component to another, they are not related so all solutions are saying that I must use shared service to do that. But these services are using templates (if I'm right). Is there a way to do this sharing without services?
I want to send the BMI value from homepage.component.ts to result.component.ts.
homepage.component.ts:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-homepage',
templateUrl: './homepage.component.html',
styleUrls: ['./homepage.component.css']
})
export class HomepageComponent implements OnInit {
constructor() { }
myHeight!:number;
myWeight!:number;
bmi!:number;
ngOnInit(): void {
}
onGenerate( height:string,width:string){
this.myHeight = +height;
this.myHeight=Math.pow(this.myHeight/100,2);
this.myWeight = +width;
this.bmi=this.myWeight/this.myHeight
console.log(this.bmi); //this is the calculated value to send
}
}
result.component.ts:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-result',
templateUrl: './result.component.html',
styleUrls: ['./result.component.css']
})
export class ResultComponent implements OnInit {
constructor() { }
//I want to get the bmi here
ngOnInit(): void {
}
}
There are Two ways to communicate between unrelated components in angular:
1 - Through services, you have to understand where to inject it, in your case I think it should be injected in root, so try this with your service ( follow this tutorial to implement your service, just add my code instead of theirs )
#Injectable({
providedIn: 'root',
})
2 - Through a store ( a lot of boilerplate coding, to use if you have complexe states to keep synchronized through the whole app, by the way the store is basically a service )
If your components are not related then you can create a shared service between them. Then, you need to use dependency injection to communicate between these components. So, there is a great Angular tutorial which describes how to do it.
The service code would look like this:
#Injectable()
export class FooService {
constructor( ) { }
private yourData;
setData(data){
this.yourData = data;
}
getData(){
let temp = this.yourData;
this.clearData();
return temp;
}
}
and sender component:
import { Router } from '#angular/router';
import { FooService} from './services/foo.service';
export class SenderComponent implements OnInit {
constructor(
private fooService: FooService,
private router:Router) {}
somefunction(data){
this.fooService.setData(data);
this.router.navigateByUrl('/reciever');//as per router
}
}
and subscriber:
import { Router } from '#angular/router';
import { TransfereService } from './services/transfer.service';
export class RecieverComponent implements OnInit {
data;
constructor(
private fooService: FooService){
}
ngOnInit() {
data = this.transfereService.getData();
console.log(`data: `, data)
}
}
Solution: To pass the data from one component to another we can store it in a session storage or a local storage and then access it in other components from that storage. Here I have provided a sample code using local storage for your reference.
homepage.component.ts:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-homepage',
templateUrl: './homepage.component.html',
styleUrls: ['./homepage.component.css']
})
export class HomepageComponent implements OnInit {
constructor() { }
myHeight!:number;
myWeight!:number;
data:string='';
bmi!:number;
ngOnInit(): void {
}
onGenerate( height:string,width:string){
this.myHeight = +height;
this.myHeight=Math.pow(this.myHeight/100,2);
this.myWeight = +width;
this.bmi=this.myWeight/this.myHeight;
this.data=localStorage.setItem('bmi',this.bmi);
console.log(this.bmi); //this is the calculated value to send
}
}
resultcomponent.ts:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-result',
templateUrl: './result.component.html',
styleUrls: ['./result.component.css']
})
export class ResultComponent implements OnInit {
data:any;
constructor() { this.data=localstorage.getItem('bmi')}
//Access the bmi using the data variable here
ngOnInit(): void {
}
}

Angular 5 - HttpClient Service - Component not getting data

I am using Angular 5 and trying to get some data from JsonPlaceholder.
First I created the service, then added:
import { HttpClientModule } from '#angular/common/http';
This is the service code:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class DataService {
private ROOT_URL = 'http://jsonplaceholder.typicode.com';
constructor(private http: HttpClient) {}
getPosts() {
this.http.get(`${this.ROOT_URL}/posts`).subscribe(data => {
return data;
});
}
}
And finally, on my app.component.ts:
import { Component, OnInit } from '#angular/core';
import { DataService } from '../../services/data.service';
#Component({
selector: 'app-root',
templateUrl: './parent.component.html',
styleUrls: ['./parent.component.css']
})
export class AppComponent implements OnInit {
data;
constructor(private dataService: DataService) {
this.data = dataService.getPosts();
console.log(this.data;
}
ngOnInit() {
}
}
On the console it's just returning 'Undefined'
What I'm I doing wrong?
Don't subscribe on the service, return the observable and subscribe to it on the component. Because it is asynchronous your data variable on the component will be undefined because you assigned before the http request could resolve a value.
On the service:
getPosts() {
return this.http.get(`${this.ROOT_URL}/posts`);
}
On the coponent:
ngOnInit() {
this.dataService.getPosts().subscribe(posts => this.posts = posts);
}
Try following code snippet.
You are getting undefined because you assigning the data before the http request could resolve a value. Remove the subscription from service and move it to component.
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class DataService {
private ROOT_URL = 'https://jsonplaceholder.typicode.com';
constructor(private http: HttpClient) {}
getPosts() {
return this.http.get(`${this.ROOT_URL}/posts`);
}
}
AppComponent.ts
import { Component } from '#angular/core';
import {DataService} from './data.service';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
constructor(public data:DataService){
this.data.getPosts().subscribe(data=>{
console.log(data);
})
}
}
See the Demo here
import { HttpClientModule } from '#angular/common/http';
//This is the service code:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class DataService {
private ROOT_URL = 'http://jsonplaceholder.typicode.com';
constructor(private http: HttpClient) {}
getPosts() {
//if you want to use the observable which returns from .get()
//.. you need to do "return"
return this.http.get(`${this.ROOT_URL}/posts`);
}
}
//app.component.ts:
import { Component, OnInit } from '#angular/core';
import { DataService } from '../../services/data.service';
#Component({
selector: 'app-root',
templateUrl: './parent.component.html',
styleUrls: ['./parent.component.css']
})
export class AppComponent implements OnInit {
data;
constructor(private dataService: DataService) {
this.data = dataService.getPosts();
//so if you want to use the value of your http call outside..
//..of the service here is a good place where to do subscribe()
this.data.subscribe(data => {
console.log(this.data;
});
}
ngOnInit() {
}
}
You expect a return value and returns nothing. Your return statement is placed inside a nested-lambda function, thus using "return" in the place you used it causes the inner function to return a value, and not the outer one as you needed.
I suggest you to read about asynchronous programming, and particularly about Observable (which works on the same concept of Promise) in Angular.
I basically agree to #Eduardo Vargas answer, but it might be also a good idea to do it in resolver, which will call api, and put data into route snapshot. Thanks to this it won't wait on empty page for loading the data on subscribe in constructor. More info here:
https://blog.thoughtram.io/angular/2016/10/10/resolving-route-data-in-angular-2.html

shared information between components through service not working

Im having some trouble figuring this out, basically I have a headerTitleService which I want to be able to dynamically set the title in my header component but for some reason when I set the title nothing shows up? Im not getting any errors so I can seem to figure out what the problem is..
headerTitle.service.ts
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable()
export class HeaderTitleService {
title = new BehaviorSubject('');
constructor() { }
setTitle(title: string) {
this.title.next(title);
}
}
header.component.ts
import { Component, OnInit } from '#angular/core';
import { HeaderTitleService } from '../../../services/headerTitle.service'
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'],
providers: [HeaderTitleService]
})
export class HeaderComponent implements OnInit {
title: string;
constructor(
private headerTitleService: HeaderTitleService
) { }
ngOnInit() {
this.headerTitleService.title.subscribe(updatedTitle => {
this.title = updatedTitle;
});
}
}
header.component.html
<h1>{{title}}</h1>
home.component.ts
import { Component, OnInit } from '#angular/core';
import { HeaderTitleService } from '../../services/headerTitle.service';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
providers: [HeaderTitleService]
})
export class HomeComponent implements OnInit {
constructor(
private headerTitleService: HeaderTitleService
) { }
ngOnInit() {
this.headerTitleService.setTitle('hello');
}
}
The line providers: [HeaderTitleService] in each component means that they will be given one HeaderTitleService each, rather than one between them.
To fix this remove providers: [HeaderTitleService] from your components, and place it in your module definition instead:
#NgModule({
providers: [HeaderTitleService]
})
Move HeaderTitleService in providers of your module. With your implementation you receive new instance of the HeaderTitleService in each component.
Hope this helps.

How to send data from one component to another using a shared service

I wanted to send data using subject to another component (for a earning purpose). I am not able to fetch back the data. Here is my code:
app.component.ts
import { Component } from '#angular/core';
import { shareService } from './share.service';
#Component({
selector: 'my-app',
template: `
<hello></hello>
<button (click)="passData()">
Start
</button>
`,
styleUrls: [ './app.component.css' ],
providers:[shareService]
})
export class AppComponent {
constructor(private service : shareService){}
passData(){
this.service.send("hello");
}
}
hello.component.ts
import { Component, Input } from '#angular/core';
import { shareService } from './share.service';
import { Subscription } from 'rxjs/Subscription';
#Component({
selector: 'hello',
template: `<h1>Hello!</h1>`,
styles: [`h1 { font-family: Lato; }`],
providers:[shareService]
})
export class HelloComponent {
subscription: Subscription;
constructor(private share : shareService){
this.subscription = share.subj$.subscribe(val=>{
console.log(val);
})
}
}
share.service.ts
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class shareService{
private sub = new Subject();
subj$ = this.sub.asObservable();
send(value: string) {
this.sub.next(value);
}
}
I am not getting the value in console.
Here is the working Demo : DEMO
By putting:
#Component({
.....
providers: [sharedService]
})
in both components, you are creating two distinct instances of the shared service.
Each instance is not 'aware' of the data from each component.
Provide it at module level and create a singleton service:
#NgModule({
....
providers: [sharedService]
})
This way, you inject the service as a single instance in the both components, so they can share it as they will share the data.
Or using the Angular's preferred new way :
Beginning with Angular 6.0, the preferred way to create a singleton
service is to specify on the service that it should be provided in the
application root. This is done by setting providedIn to root on the
service's #Injectable decorator:
#Injectable({
providedIn: 'root',
})
Demo
See also
I dont know why sub$ is used but you dont need that
// just push data to subject. you can use BehavourSubject to initiatte a value.
#Injectable()
export class shareService{
private sub = new Subject();
confirmMission(astronaut: string) {
this.sub.next(astronaut);
}
}
And then in your 2nd component sub scribe it
#Component({
selector: 'hello',
template: `<h1>Hello!</h1>`,
styles: [`h1 { font-family: Lato; }`],
providers:[shareService] // this can be shared in module lebel or componenet level
})
export class HelloComponent {
subscription: Subscription;
constructor(private share : shareService){
this.subscription = share.subj.subscribe(val=>{
console.log(val);
})
}
}
make sure to provide your service in module level or provide it in both the component.

implement angular2-virtual-scroll with observable in angular4

I am trying to implement angular2-virtual-scroll in an angular4 project. Firstly, can anyone confirm if I would have a problem implementing it in NG4. I don't think I should and I haven't seen any notices to the contrary. Secondly I am using an observable instead of a promise shown in the NPM angular2-virtual-scroll docs. However, when I applied the virtual-scroll tag there is no change in my output...no scroll bar ..the data from the observable is displayed but no scrolling. The following are the relevant code segments:
Home.component.html
<h1 style="color: #76323f">
{{title}}
</h1>
<h4>
{{description}}
</h4>
<h2>Coming Soon....</h2>
<app-events-list [upcomingEvents]="upcomingEvents"></app-events-list>
home.component.ts
import {Component, OnInit,OnDestroy } from '#angular/core';
import {UpcomingEvent} from './../../interface/upcomingevent';
import {EventService} from './../../services/event.service';
import {Subscription} from 'rxjs/Subscription';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit, OnDestroy {
upcomingEvents: Array<UpcomingEvent>;
title = 'Welcome....';
description='Events promotions....';
eventServiceSub: Subscription;
constructor(private eventService: EventService){
this.upcomingEvents = [];
}
ngOnInit() {
this.eventServiceSub=this.eventService.getEvents().subscribe(upcomingEvents=>{this.upcomingEvents=upcomingEvents.slice(0,25);
});
}
ngOnDestroy(){
if (this.eventServiceSub){
this.eventServiceSub.unsubscribe();
}
}
}
event-list-component.html
<virtual-scroll [items]="items" (update)="upcomingEvents = $event"
(change)="onlistChange($event)">
<app-upcomingevent *ngFor="let upcomingEvent of upcomingEvents"[upcomingEvent]="upcomingEvent" [eventItemCss]="'event-item'"></app-upcomingevent>
<div *ngIf="loading" class="loader">Loading.....</div>
</virtual-scroll>
event-list-component.ts
import { Component, Input, OnDestroy, OnInit } from '#angular/core';
import { UpcomingEvent } from './../../interface/upcomingevent';
import { trigger,state,style,transition,animate,keyframes } from '#angular/animations';
import { ChangeEvent } from 'angular2-virtual-scroll';
import {EventService} from './../../services/event.service';
import {Subscription} from 'rxjs/Subscription';
#Component({
selector: 'app-events-list',
templateUrl: './events-list.component.html',
styleUrls: ['./events-list.component.css'],
animations: []
})
export class EventsListComponent implements OnInit, OnDestroy {
#Input()
upcomingEvents: Array<UpcomingEvent>;
items=this.upcomingEvents;
protected buffer: Array<UpcomingEvent> =[];
protected loading: boolean;
eventServiceSub: Subscription;
constructor(private eventService: EventService) {
this.upcomingEvents=[];
}
ngOnInit() {
}
ngOnDestroy(){
if (this.eventServiceSub){
this.eventServiceSub.unsubscribe();
}
}
protected onlistChange(event: ChangeEvent){
if (event.end !==this.buffer.length) return;
this.loading=true;
this.eventServiceSub=this.eventService.getEvents().subscribe(upcomingEvents=>{
this.buffer=upcomingEvents.slice(this.buffer.length,25);
this.loading=false;
}, ()=> this.loading=false);
}
}
eventService.ts
import { Injectable } from '#angular/core';
import {UpcomingEvent} from './../interface/upcomingevent'
import {Http} from '#angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class EventService {
constructor(private http: Http) { }
getEvents(): Observable<UpcomingEvent[]>
{
return this.http
.get(`https://jsonplaceholder.typicode.com/posts`)
.map(response => response.json() as UpcomingEvent[]);
}
}
upcomingEvents.html
<md-card [ngClass]="'event-item'">
<h4 [ngClass]="'event-title'" [ngStyle]="{'color':'purple'}"> {{upcomingEvent.title}}</h4>
<md-card-actions>
<button md-button>LIKE</button>
<button md-button>SHARE</button>
</md-card-actions>
</md-card>
upcomingEvents.component.ts
import { Component, Input,OnInit } from '#angular/core';
import { UpcomingEvent } from './../../interface/upcomingevent';
#Component({
selector: 'app-upcomingevent',
templateUrl: './upcomingevent.component.html',
styleUrls: ['./upcomingevent.component.css']
})
export class UpcomingeventComponent implements OnInit {
#Input()
upcomingEvent: UpcomingEvent;
#Input()
eventItemCss: string;
constructor() { }
ngOnInit() {
if (!this.upcomingEvent) {
this.upcomingEvent=<UpcomingEvent> {};
}
}
}
The way it suppose to work is that the HomeComponent request data from the eventService via an observable..the data is then passed to the eventList component where it is iterated on and pass to the upcomingEvents component to present via HTML. 25 events are requested at first and if the user scroll to the end another 25 is requested from the eventService...this time by the upcomingEvents component. I am not sure this is the most efficient way to do it but in either case it doesn't work. The virtual-scroll seems to have no effect on the output....I would really appreciate someone showing me what I am doing wrong....Thanks
I think I see a problem in your code. When using virtual-scroll you need to keep two arrays - one for all loaded data, and one for currently rendered items. Looks like you kinda mixed them up.
Let's call all-items-array as items and render-array - upcomingEvents
First chunk of items will be passed from the parent component, hence:
Home.component.html
...
<app-events-list [items]="upcomingEvents"></app-events-list>
event-list-component.html looks good
event-list-component.ts
import { Component, Input, OnDestroy, OnInit } from '#angular/core';
import { UpcomingEvent } from './../../interface/upcomingevent';
import { trigger,state,style,transition,animate,keyframes } from '#angular/animations';
import { ChangeEvent } from 'angular2-virtual-scroll';
import {EventService} from './../../services/event.service';
import {Subscription} from 'rxjs/Subscription';
#Component({
selector: 'app-events-list',
templateUrl: './events-list.component.html',
styleUrls: ['./events-list.component.css'],
animations: []
})
export class EventsListComponent implements OnInit, OnDestroy {
#Input()
items: Array<UpcomingEvent> = [];
upcomingEvents: Array<UpcomingEvent>;
protected loading: boolean;
eventServiceSub: Subscription;
constructor(private eventService: EventService) {
this.upcomingEvents=[];
}
ngOnInit() {
}
ngOnDestroy(){
if (this.eventServiceSub){
this.eventServiceSub.unsubscribe();
}
}
protected onlistChange(event: ChangeEvent){
if (event.end !==this.items.length) return;
this.loading=true;
this.eventServiceSub=this.eventService.getEvents().subscribe(upcomingEvents=>{
this.items=upcomingEvents.slice(0, this.items.length + 25);
this.loading=false;
}, ()=> this.loading=false);
}
}
warning! code not tested, I simply edited your code.

Categories