Why is the service called twice in this angular 2 component? - javascript

I have here the component code, when I am subscribing to the observable the service is called twice, however if I subscribe to the Behaviorsubject it is only triggered once,
I can see on my logs that those are the result, please see my code below for my component
the method subscribeToMap() method is called on ngOninit.
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
// Observable class extensions
import 'rxjs/add/observable/of';
// Observable operators
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import { HeroSearchService } from './hero-search-service';
import { Hero } from './../hero';
#Component({
selector: 'hero-search',
templateUrl: './hero-search.component.html',
styleUrls: [ './hero-search.component.css' ],
providers: [HeroSearchService]
})
export class HeroSearchComponent implements OnInit {
heroes: Observable<Hero[]>;
private searchTerms = new Subject<string>();
constructor(
private heroSearchService: HeroSearchService,
private router: Router) {}
// Push a search term into the observable stream.
search(term: string): void {
this.searchTerms.next(term);
console.log("new " + term);
}
ngOnInit(): void {
this.heroes = this.searchTerms
.debounceTime(300) // wait 300ms after each keystroke before considering the term
.distinctUntilChanged() // ignore if next search term is same as previous
.switchMap(term => {
return term // switch to new observable each time the term changes
// return the http search observable
? this.heroSearchService.search(term)
// or the observable of empty heroes if there was no search term
: Observable.of<Hero[]>([])})
.catch(error => {
// TODO: add real error handling
console.log(error);
return Observable.of<Hero[]>([]);
});
this.subscribeToMap();
}
subscribeToMap(): void{
this.heroes.subscribe(() => console.log("called twice"));
this.searchTerms.subscribe(() => console.log("called once"));
}
gotoDetail(hero: Hero): void {
let link = ['/detail', hero.id];
this.router.navigate(link);
}
}
Here is the code for my service
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { Hero } from './../hero';
#Injectable()
export class HeroSearchService {
constructor(private http: Http) {}
search(term: string): Observable<Hero[]> {
console.log("service is called");
return this.http
.get(`api/heroes/?name=${term}`)
.map(response => response.json().data as Hero[]);
}
}
thank you ver much!!!

When subscription is implemented properly it has nothing to do with "unsubscribe" method, Observable, etc. This behavior is by design of Angular itself.
https://www.reddit.com/r/Angular2/comments/59532r/function_being_called_multiple_times/d95vjlz/
If you're running in development mode, it will run the function
at least twice. since in development mode it does a check, changes,
then rechecks to verify, where production mode only does the first
check, assuming you've done your quality assurance and resolved any
values the get changed post checking.
P.S. This is probably the next issue you will face to in Dev Mode :)
Angular2 change detection "Expression has changed after it was checked"

Try replacing this line:
this.heroes = this.searchTerms
With this one:
this.heroes = this.searchTerms.asObservable()
to ensure that heroes is an observable and your code can't accidentally invoke next() on it.
Your code casts hero to a Subject so you can still do next() on it.

Related

How to extract values from BehaviouralSubject in Angular

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.

AngularJS Version 6.* Service method is coming up undefined

So I am simply trying to write a component and service that from one view takes the user input and passes it to an api for validation. The problem is that in my component, it's saying that the service essentially has no method login and is coming up undefined. However I've checked and rechecked following Angular.io's documentation very closely but can't get anything to work.
LoginComponent.ts
import { Component, OnInit } from '#angular/core';
import { UserService } from '../../../services/user.service';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
constructor(private userService: UserService) {
console.log('userService', userService);
}
ngOnInit() {}
handleSubmit(data) {
// https://api-test.sarahlawrence.edu:82/
this.userService.login(data)
.subscribe(user => console.log('user', user));
}
}
user.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable, of } from 'rxjs/index';
import { catchError, map, tap } from 'rxjs/internal/operators';
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
interface CredsInterface {
username: string;
password: string;
};
interface LoggedIn {
token: string;
}
#Injectable({
providedIn: 'root'
})
export class UserService {
private apiUrl = '<apiUrl>';
constructor(
private http: HttpClient
) { }
login (creds: CredsInterface): Observable<any> {
console.log('UserService.login()', creds);
return this.http.post<any>(`${this.apiUrl}/signin`, creds, {})
.pipe(
tap((loggedIn: LoggedIn) => {
console.log(`Login: ${loggedIn}`);
}),
catchError(this.handleError('login()', []))
);
}
/**
* Handle Http operation that failed.
* Let the app continue.
* #param operation - name of the operation that failed
* #param result - optional value to return as the observable result
*/
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
// TODO: send the error to remote logging infrastructure
console.error(error); // log to console instead
// TODO: better job of transforming error for user consumption
console.log(`${operation} failed: ${error.message}`);
// Let the app keep running by returning an empty result.
return of(result as T);
};
}
}
I don't understand why I get this error:
So I logged the service out to see the object and weirdly the method is being placed in the prototype:
I don't get it, am I doing something wrong?
How do you call that handleSubmit method?
The error says that it can't read login property of undefined which means that this.userService is undefined. The fact that login method is inside prototype is okay. Remember that gets are deep and sets are shallow
I think that you call handleSubmit with some tricky way which makes this to refer other object than you think it is.
I've just saw stackblitz. You pass reference to your function using [onHandleSubmit]="handleSubmit" but when it's executed this is not your LoginComponent anymore.
Add this to component constructor
this.handleSubmit = this.handleSubmit.bind(this)
For more details see this post: Angular pass callback function to child component as #Input

Route Resolver not firing observable without subscribe

I have a route which needs some data from my Firebase db before the route is loaded. It feels like the Route is not calling subscribe so the request is never being fired off. Am I missing a step?
(Angular 5)
My router:
{
path: 'class/:idName',
component: ClassComponent,
resolve: {
classData: ClassResolver
}
},
My Resolver:
#Injectable()
export class ClassResolver implements Resolve<any> {
constructor(
private db: AngularFireDatabase
) {}
resolve(route: ActivatedRouteSnapshot): Observable<any> | Promise<any> | any {
// return 'some data'; //This worked fine
return this.db
.list('/')
.valueChanges() // Returns Observable, I confirmed this.
//.subscribe(); // This returns a Subscriber object if I call it and I never get any data
}
// I tried this and it didnt work either
//const list = this.db
// .list('/')
// .valueChanges();
//console.log('list', list); // Is a Observable
//list.subscribe(data => {
// console.log('data', data); // returned data
// return data;
//});
//return list; // never gets to the component
}
My Component:
public idName: string;
// Other vars
constructor(
private fb: FormBuilder,
private route: ActivatedRoute,
private db: AngularFireDatabase
) {
// Form stuff
}
ngOnInit() {
// Never makes it here
this.idName = this.route.snapshot.params.idName;
const myclass = this.route.snapshot.data.classData;
console.log('myclass', myclass);
}
I never makes it to the component. It waits for the component to load, which it never does. If I add the subscribe and console.out the data it returns quite quickly with the correct data, so its not the service.
After calling .subscribe() in my Resolver that now returns a Subscriber object. Because my return signature allows for any its returning this Subscriber as if it was the data. This seems obvious now.
My question now becomes why isn't it resolving my Observable?
Your resolve function is returning an Observable that never completes. The Observable is indeed firing (and this can be verified by adding a tap to its pipeline with some console-logging)—but the resolve phase won't end (and therefore your component won't load) until the Observable completes. (The docs are not great at highlighting this.)
Obviously you don't want your Observable to complete either, because then you wouldn't get further data updates.
The simplest “fix” is to wrap your Observable in a Promise:
async resolve(route: ActivatedRouteSnapshot): Promise<Observable<any>> {
return this.db.list('/').valueChanges();
}
but this won't guarantee that Firebase has emitted its initial response, which I feel is what you're trying to ensure before the route loads.
The only approach I can see that would:
ensure that the component doesn't load until Firebase has returned data at least once; and
prevent two different Firebase reads (one by the resolver and then one by the component) for one effective operation
is to wrap your Firebase Observable in a service:
import { Injectable, OnDestroy, OnInit } from '#angular/core';
import { AngularFireDatabase } from '#angular/fire/database';
import { Subscription } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
#Injectable({
providedIn: 'root',
})
export class DataService implements OnInit, OnDestroy {
constructor(private readonly db: AngularFireDatabase) {}
/**
* Observable to the data.
* shareReplay so that multiple listeners don't trigger multiple reads.
*/
public readonly data$ = this.db
.list('/')
.valueChanges()
.pipe(shareReplay({ bufferSize: 1, refCount: true }));
/**
* To trigger the first read as soon as the service is initialised,
* and to keep the subscription active for the life of the service
* (so that as components come and go, multiple reads aren't triggered).
*/
private subscription?: Subscription;
ngOnInit(): void {
this.subscription = this.data$.subscribe();
}
ngOnDestroy(): void {
this.subscription?.unsubscribe();
}
}
and then your resolver would look like this:
async resolve(route: ActivatedRouteSnapshot): Promise<Observable<any>> {
// ensure at least one emission has occurred
await this.dataService.data$.pipe(take(1)).toPromise();
// ...then permit the route to load
return this.dataService.data$;
}
By wrapping your Firebase Observable in a service, you get OnInit and OnDestroy lifecycle hooks, which you can use to ensure that the observable "lives on" between component loads (and prevent multiple Firebase reads where one would suffice). Because the data is then hanging around, subsequent loads of the data would also be quicker. Lastly, this still enables you to use a resolver to ensure that the data will be instantly available before proceeding to load the component.
Your code looks to be correct. Have you been passing a parameter to your class route? It wont resolve without a parameter, that might be why you are not reaching your ngOnInit function. I would suggest console logging your route snapshots as well to make sure you are grabbing the right objects. I'll also post a resolve example that I got working:
Component.ts
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { Observable } from 'rxjs/Observable';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
public data: Observable<any>;
constructor(private router: ActivatedRoute) { }
ngOnInit() {
this.data = this.router.snapshot.data.test;
}
}
Routing.ts
{ path: 'home/:id', component: HomeComponent, resolve: { test: ResolverService } },
ResolverService
import { Injectable } from '#angular/core';
import { Resolve } from '#angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
#Injectable()
export class ResolverService implements Resolve<Observable<any>> {
constructor() { }
public resolve(route: ActivateRouteSnapShot): Observable<any> {
return Observable.of({test: 'Test Observable'});
}
}
HTML
{{this.data.test}}
You just need to add a take(1) operator to the Observable the resolver returns so that it completes.
resolve(route: ActivatedRouteSnapshot): Observable<any> {
return this.db.list('/').valueChanges()
.pipe(take(1)); // <-- The Magic
}
#AlexPeters was on the right track, but you don't have to go so far as to return a promise. Just force the completion with take(1). Alex is also spot-on that the docs are not very clear on this. I just spent an couple hours debugging this same issue.

How get data from server? Use interval operator

I get data from remote server. CORS is configured. I use interval() operator form RXJS library.
Component:
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/switchMap';
private getUserTasks(userId): void {
Observable
.interval(5000)
.switchMap(() => this.tasksService.getUserTasks(userId))
.subscribe(
data => {
this.userTasks = JSON.parse(data);
console.log('userTasks', this.userTasks);
}
)
};
Service return observable object:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class TasksService {
constructor(private http: HttpClient) { };
getUserTasks(userId): Observable<any> {
return this.http.get('http://blabla.com/app_tasks/user_tasks?user_id=' + userId);
};
}
But after the user enters the page, he waits 5 seconds (and looks at the white screen). I need to send a request immediately after the page is loaded. And then every 5 seconds the request was repeated
You can use startWith to have the sequence emit an initial value
private getUserTasks(userId): void {
Observable
.interval(5000)
.startWith(0)
.switchMap(() => this.tasksService.getUserTasks(userId))
.subscribe(data => {
this.userTasks = JSON.parse(data);
console.log('userTasks', this.userTasks);
})
};

NodeJS, Angular 2 | Executing method on Observable next

I'm currently getting started with Angular 2 and got stuck on something probably pretty simple:
I have a shared service chatMessageService.ts:
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable()
export class ChatMessageService {
private messageList = new BehaviorSubject<string>("");
currentMessage = this.messageList.asObservable();
constructor() {
}
public addMessage(msg:string) {
this.messageList.next(msg) }
}
The service is imported by two components, one that calls it's addMessage function to add the message to the Observable and then my chatComponent.ts looks like this (shortened fpr convinience):
import { Component } from '#angular/core';
import { Message } from './message';
import { ChatMessageService } from './chatMessage.service';
#Component({
selector: 'app-chat',
templateUrl: './chat.component.html',
styleUrls: ['./chat.component.css']
})
export class ChatComponent {
conversation: Message[] = [];
//.....
constructor(private chatMessageService: ChatMessageService) { }
addUserMessage(message) {
this.conversation.push({
content: message
});
}
ngOnInit() {
this.chatMessageService.currentMessage.subscribe(message => {this.addUserMessage(message);} )
}
}
My crisis arises at that last subscripion part. When I replace
{this.addUserMessage(message);}
with
{console.log(message)}
the message is printed out perfectly fine. If I call the addUserMessage()-method manually it works just fine. But when I call the method right there, with the message as argument, nothing happens. The method isn't even executed?
Thankful for your insights!
It looks like you need some buffering in the service.
Instead of BehaviorSubject, try
private messageList = new ReplaySubject<string>(10);
See working example: Plunker

Categories