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);
})
};
Related
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();
}
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.
I am new to Angular and Ionic. I am looping through an array of content that is store in my Firestore database. When the app recompiles and loads, then I go to the settings page (that's where the loop is happening), I see the array of content just fine. I can update it on Firestore and it will update in real time in the app. It's all good here. But if I click "Back" (because Settings is being visited using "navPush"), then click on the Settings page again, the whole loop content will be gone.
Stuff is still in the database just fine. I have to recompile the project to make the content appear again. But once again, as soon as I leave that settings page, and come back, the content will be gone.
Here's my code:
HTML Settings page (main code for the loop):
<ion-list>
<ion-item *ngFor="let setting of settings">
<ion-icon item-start color="light-grey" name="archive"></ion-icon>
<ion-label>{{ setting.name }}</ion-label>
<ion-toggle (ionChange)="onToggle($event, setting)" [checked]="setting.state"></ion-toggle>
</ion-item>
</ion-list>
That Settings page TS file:
import { Settings } from './../../../models/settings';
import { DashboardSettingsService } from './../../../services/settings';
import { Component, OnInit } from '#angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
#IonicPage()
#Component({
selector: 'page-dashboard-settings',
templateUrl: 'dashboard-settings.html',
})
export class DashboardSettingsPage implements OnInit {
settings: Settings[];
checkStateToggle: boolean;
checkedSetting: Settings;
constructor(public dashboardSettingsService: DashboardSettingsService) {
this.dashboardSettingsService.getSettings().subscribe(setting => {
this.settings = setting;
console.log(setting.state);
})
}
onToggle(event, setting: Settings) {
this.dashboardSettingsService.setBackground(setting);
}
}
And my Settings Service file (the DashboardSettingsService import):
import { Settings } from './../models/settings';
import { Injectable, OnInit } from '#angular/core';
import * as firebase from 'firebase/app';
import { AngularFireAuth } from 'angularfire2/auth';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class DashboardSettingsService implements OnInit {
settings: Observable<Settings[]>;
settingsCollection: AngularFirestoreCollection<Settings>;
settingDoc: AngularFirestoreDocument<Settings>;
public checkedSetting = false;
setBackground(setting: Settings) {
if (this.checkedSetting == true) {
this.checkedSetting = false;
} else if(this.checkedSetting == false) {
this.checkedSetting = true;
};
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({state: this.checkedSetting});
console.log(setting);
}
constructor(private afAuth: AngularFireAuth,private afs: AngularFirestore) {
this.settingsCollection = this.afs.collection('settings');
this.settings = this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
return data;
});
});
}
isChecked() {
return this.checkedSetting;
}
getSettings() {
return this.settings;
}
updateSetting(setting: Settings) {
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({ state: checkedSetting });
}
}
Any idea what is causing that?
My loop was in a custom component before, so I tried putting it directly in the Dashboard Settings Page, but it's still not working. I have no idea what to check here. I tried putting the :
this.dashboardSettingsService.getSettings().subscribe(setting => {
this.settings = setting;
})
...part in an ngOninit method instead, or even ionViewWillLoad, and others, but it's not working either.
I am using Ionic latest version (3+) and same for Angular (5)
Thank you!
From the Code you posted i have observed two findings that might be the potential cause for the issue ,
Calling of the Service method in the constructor :
When your setting component is created , then that constructor will be called but but if you were relying on properties or data from child components actions to take place like navigating to the Setting page so move your constructor to any of the life cycle hooks.
ngAfterContentInit() {
// Component content has been initialized
}
ngAfterContentChecked() {
// Component content has been Checked
}
ngAfterViewInit() {
// Component views are initialized
}
ngAfterViewChecked() {
// Component views have been checked
}
Even though you add your service calling method in the life cycle events but it will be called only once as you were subscribing your service method in the constructor of the Settings service file . so just try to change your service file as follows :
getSettings() {
this.settingsCollection = this.afs.collection('settings');
this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
return data;
});
});
}
Update :
Try to change the Getsettings as follows and please do update your question with the latest changes
getSettings() {
this.settingsCollection = this.afs.collection('settings');
return this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
return data;
});
});
}
I'm not certain, but I suspect the subscription to the settings observable settings: Observable<Settings[]> could be to blame. This may work on the first load because the DashboardSettingsService is being created and injected, therefore loading the settings, and then emitting an item (causing your subscription event in DashboardSettingsPage to fire).
On the second page load, DashboardSettingsService already exists (services are created as singletons by default) - this means that the constructor does not get called (which is where you set up your observable) and therefore it does not emit a new settings object for your component.
Because the Observable does not emit anything, the following event will not be fired, meaning your local settings object is never populated:
this.dashboardSettingsService.getSettings().subscribe(setting => {
this.settings = setting;
console.log(setting.state);
})
You could refactor your service with a method that provides the latest (cached) settings object, or a new Observable (dont forget to unsubscribe!!), rather than creating a single Observable which will only be triggered by creation or changes to the underlying storage object.
Here's a simple example that doesnt change your method signature.
import { Settings } from './../models/settings';
import { Injectable, OnInit } from '#angular/core';
import * as firebase from 'firebase/app';
import { AngularFireAuth } from 'angularfire2/auth';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
#Injectable()
export class DashboardSettingsService implements OnInit {
settings: Observable<Settings[]>;
cachedSettings: Settings[];
settingsCollection: AngularFirestoreCollection<Settings>;
settingDoc: AngularFirestoreDocument<Settings>;
public checkedSetting = false;
setBackground(setting: Settings) {
if (this.checkedSetting == true) {
this.checkedSetting = false;
} else if(this.checkedSetting == false) {
this.checkedSetting = true;
};
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({state: this.checkedSetting});
console.log(setting);
}
constructor(private afAuth: AngularFireAuth,private afs: AngularFirestore) {
this.settingsCollection = this.afs.collection('settings');
this.settings = this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
this.cachedSettings = data;
return data;
});
});
}
isChecked() {
return this.checkedSetting;
}
getSettings() {
return Observable.of(this.cachedSettings);
}
updateSetting(setting: Settings) {
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({ state: checkedSetting });
}
}
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.
I need to get array from JSON API and then iterate it. I still can't understand how it works. Thank you for help.
This is how looks my service.
import {Injectable} from '#angular/core';
import { Http } from "#angular/http";
import "rxjs/Rx";
#Injectable()
export class PlayersService {
roster:Roster[];
constructor(private http: Http){
this.roster = [];
}
getPlayer(id) {
for (let player of this.roster) {
console.log(player["id"]);
}
}
getRoster(season,category) {
this.roster.push(this.http.get("http://API JSON LIST OF ID")
.map(res => res.json()));
}
}
interface Roster {
id:number
}
This how I call it
ngOnInit() {
this.getRoster();
this.getPlayers();
}
Where is the fail please?
This should do what you want:
#Injectable()
export class PlayersService {
roster:Roster[];
constructor(private http: Http){
this.roster = [];
}
getPlayer(id) {
for (let player of this.roster) {
console.log(player["id"]);
}
}
getRoster(season,category) {
return this.http.get("http://API JSON LIST OF ID")
.map(res => res.json())
.do(val => this.roster.push(val)); // the do operator should be used for side effects (eg modifying an existing array)
}
}
ngOnInit() {
this.playerService.getRoster().subscribe(val => this.playerService.getPlayer());
}