Angular6 polling not returning data - javascript

I have an Angular application where I am trying to check an external data service for changes every few seconds, and update the view.
I've tried to implement Polling from rxjs but I'm not able to access the object, conversely it seems the polling function isn't working but assume this is because the returned object is inaccessible.
app.component.ts
export class AppComponent {
polledItems: Observable<Item>;
items : Item[] = [];
title = 'site';
landing = true;
tap = false;
url:string;
Math: any;
getScreen(randomCode) {
const items$: Observable<any> = this.dataService.get_screen(randomCode)
.pipe(tap( () => {
this.landing = false;
this.Math = Math
}
));
const polledItems$ = timer(0, 1000)
.pipe(( () => items$))
console.log(this.polledItems);
console.log(items$);
}
excerpt from app.component.html
<h3 class="beer-name text-white">{{(polledItems$ | async).item_name}}</h3>
excerpt from data.service.ts
get_screen(randomCode) {
return this.httpClient.get(this.apiUrl + '/tap/' + randomCode)
}

assuming that you want an array of items you could go with something like this.
// dont subscribe here but use the
// observable directly or with async pipe
private readonly items$: Observable<Item[]> = this.dataService.get_screen(randomCode)
// do your side effects in rxjs tap()
// better move this to your polledItems$
// observable after the switchMap
.pipe(
tap( () => { return {this.landing = false; this.Math = Math}; })
);
// get new items periodicly
public readonly polledItems$ = timer(0, 1000)
.pipe(
concatMap( () => items$),
tap( items => console.log(items))
)
the template:
// pipe your observable through async and THEN access the member
<ng-container *ngFor="let polledItem of (polledItems$ | async)>
<h3 class="item-name text-white">{{polledItem.item_name}}</h3>
</ng-container>
take a look at: https://blog.strongbrew.io/rxjs-polling/
if you are not awaiting an array but a single than you dont need the ngFor but access your item_name like:
<h3 class="item-name text-white">{{(polledItems$ | async).item_name}}</h3>

Related

How to properly get array data from observable?

I need two functions to return all data as well as specific filtered data, but my constructs are wrong. Below is what "think" I want, but am returning Subscriptions instead of arrays:
allItems() {
var collectionAll: AngularFirestoreCollection<Item> =
this._afs.collection<Item>('items');
var itemArray$: Observable<Item[]> =
collectionAll.valueChanges();
// Returns Subscription but I need Items[]
return itemArray$.subscribe(items => {
return items;
})
}
specificItems(name: string) {
var collectionSpecific: AngularFirestoreCollection<Item> =
this._afs.collection<Item>('items', ref =>
ref.where('name', '==', name));
var itemArray$: Observable<Item[]> =
collectionSpecific.valueChanges();
// Returns Subscription but I need Items[]
return itemArray$.subscribe(items => {
return items;
})
}
Also I would think that it would need to be an async function, but the subscribe function doesn't return a promise.
And I'm not even sure at what point I would actually be charged a read count from Firestore...?
If you want a promise, you need to convert the Observable to a Promise using toPromise:
specificItems(name: string): Promise<Item[]> {
var collectionSpecific: AngularFirestoreCollection<Item> =
this._afs.collection<Item>('items', ref =>
ref.where('name', '==', name));
var itemArray$: Observable<Item[]> =
collectionSpecific.valueChanges();
return itemArray$.toPromise();
}
Observables are very powerful, you should keep them as is.
allItems = this._afs.collection<Item>('items').valueChanges();
In your template, you can simply use the async pipe to read your data :
<div *ngFor="let items of allItems | async">...</div>
This is the most powerful way of using Angular for several reasons, so try learning it as soon as possible, because basically Angular = RxJS (not true of course, but it shows you how much you need RxJS in Angular)
Declare the below model in different location so that you can reuse the same model.
export class EmployeeRoster {
RosterDate: Date;
EmployeeId: number;
RosterDayName: string;
ProjectId: number;
ShiftId: number;
ShiftTime: string;
ShiftColor: string;
IsOnLeave: boolean;
}
Declare the below method in your service layer.
GetSavedEmployeeData(empIds: EmployeeIdlistToRoster[], sDate: string, eDate: string): Observable<EmployeeRoster[]> {
let empIdsValue = '';
if (empIds !== null && empIds.length > 0) {
empIds.forEach(function (em) {
empIdsValue += em.EmpId + ',';
});
}
//How to pass value as a parameter
const paramsdsf = new HttpParams()
.set('empIds', empIdsValue)
.append('sDate', sDate)
.append('eDate', eDate);
return this.http.get<EmployeeRoster[]>(apiUrl, { params: paramsdsf });
}
This is just an example you can update this method and model as per your requirement.

Cannot put subscribed observable into collection as a string

I am kind of new to typescript and javascript, and I am having a real hard time figuring out how collections and file io works. I am trying to get data from a json file, which that I am successful in although when I put it into a collection the collection does not have the data.
Here is the code:
In my service class:
private configuration = "../../assets/Configurations/testConfiguration.json";
constructor(private http: HttpClient) {}
getBlogs(blog: string): Observable<IBlogPost[]> {
return this.http.get<IBlogPost[]>(blog);
}
getConfigurations() {
var configurationsData = [];
this.http.get(this.configuration).subscribe(data => {
configurationsData.push(data);
console.log(data);
// This will work and will print all the paths
});
//This will not work and will not print them, like if the collection is empty
configurationsData.forEach(x => console.log(x));
return configurationsData;
}
Where I get my service class injected:
blogs: IBlogPost[] = [];
private blogsPaths: string[] = [];
errorMessage = "";
constructor(private appUtilityService: AppUtilityServices) {}
ngOnInit() {
//This will not work, blogsPaths will not received the full collection as it should
this.blogsPaths = this.appUtilityService.getConfigurations();
this.blogsPaths.forEach(blogPath =>
this.appUtilityService.getBlogs(blogPath).subscribe(
b =>
b.forEach(blog => {
this.blogs.push(blog);
}),
error => (this.errorMessage = <any>error)
)
);
}
testConfiguration.json:
[
"../../assets/BlogPosts/testBlog1.json",
"../../assets/BlogPosts/testBlog2.json"
]
Bonus if you include a good tutorial on how collections work in javascript and how to return them properly
Turns out to be this, yes really...
Here I made it nice:
OnInit:
this.appUtilityService.getConfigurations().subscribe(
b =>
b.forEach(x => {
this.appUtilityService.getBlogs(x).subscribe(
b =>
b.forEach(blog => {
this.blogs.push(blog);
}),
error => (this.errorMessage = <any>error)
);
}),
error => (this.errorMessage = <any>error)
);
AppUtilityService:
getBlogs(blog: string): Observable<IBlogPost[]> {
return this.http.get<IBlogPost[]>(blog);
}
getConfigurations(): Observable<string[]> {
return this.http.get<string[]>(this.configuration);
}
Make it really nice:
ngOnInit() {
this.initializeBlog();
}
private initializeBlog(): void {
this.appUtilityService.getConfigurations().subscribe(
b =>
b.forEach(x => {
this.appUtilityService.getBlogs(x).subscribe(
b =>
b.forEach(blog => {
this.blogs.push(blog);
}),
error => (this.errorMessage = <any>error)
);
}),
error => (this.errorMessage = <any>error)
);
}
Inside getConfigurations, you're invoking an asynchronous http request. So it's normal, that configurationsData is empty outside the subscribe method.
Here, I'm using the powerful of RxJS to simplify the code and avoid to use
Array.push and nested subscribe().
1) Basic async service method to get data
So getConfigurations should return an Observable:
getConfigurations(): Observable<string[]> {
return this.http.get<string[]>(this.configuration);
}
And also getBlogs should return an Observable:
getBlogs(blogsPath: string): Observable<BlogPost[]> {
return this.http.get<BlogPost[]>(blogsPath);
}
Here, to respect Angular best practice, I remove I at the beginning of each interface, so BlogPost.
2) Here is the magic of RxJS
Then, you have another method inside your service to retrieve an Observable of blog posts, which should look like this:
getBlogPosts(): Observable<BlogPost[]> {
return this.getConfigurations().pipe(
mergeAll(),
concatMap(blogsPath => this.getBlogs(blogsPath)),
concatAll(),
toArray()
)
}
Which means, retrieve all collection of blog paths, for each array of paths, for each path, get blog posts, and then return an unique array of all posts.
Finally, inside your component, you call getBlogPosts with a subscription:
ngOnInit() {
this.service.getBlogPosts().subscribe(blogs => {
...do something with your blogs array.
});
}
or even better with async pipe inside your template :
.ts file:
blogs$: Observable<BlogPost[]>;
...
this.blogs$ = this.appService.getBlogPosts();
.html file:
<ul *ngIf="blogs$ | async as blogs">
<li *ngFor="let blog of blogs">
#{{ blog.id }} - {{ blog.title }}
</li>
</ul>
Check my full demonstration of this RxJS alternative on Stackbliz.
Recommended read on medium from Tomas Trajan

Rxjs from() operator doesn't send data

I have a simple app on Angular/rxjs/Ngrx which requests list of default films from the api.
component.ts
export class MoviesComponent implements OnInit {
private movies$: Observable<{}> =
this.store.select(fromRoot.getMoviesState);
private films = [];
constructor(public store: Store<fromRoot.State>) {}
ngOnInit() {
this.store.dispatch(new MoviesApi.RequestMovies());
this.movies$.subscribe(film => this.films.push(film));
console.log(this.films)
}
effects.ts
#Effect()
requestMovies$: Observable<MoviesApi.MoviesApiAction> = this.actions$
.pipe(
ofType(MoviesApi.REQUEST_MOVIES),
switchMap(actions => this.MoviesApiServ.getDefaultMoviesList()
.pipe(
mergeMap(movies => of(new MoviesApi.RecieveMovies(movies))),
catchError(err => {
console.log('err', err);
return of(new MoviesApi.RequestFailed(err));
})
)
)
);
service.ts
export class MoviesApiService {
private moviesList = [];
constructor(private http: HttpClient) { }
public getDefaultMoviesList(): Observable<{}> {
DEFAULT_MOVIES.map(movie => this.getMovieByTitle(movie).subscribe(item => this.moviesList.push(item)));
return from(this.moviesList);
}
public getMovieByTitle(movieTitle: string): Observable<{}> {
return this.http.get<{}>(this.buildRequestUrl(movieTitle))
.pipe(retry(3),
catchError(this.handleError)
);
}
}
DEFAULT_MOVIES is just array with titles.
So my getDefaultMoviesList method is not sending data. But if I replace this.moviesList to hardcoced array of values it works as expected.
What I'm doing wrong?
UPD
I wanted to loop over the default list of films, then call for each film getMovieByTitle and collect them in array and send as Observable. Is there any better solution?
1) You should probably move this line to the service contructor, otherwise you will push a second array of default movies every time you getDefaultMoviesList:
DEFAULT_MOVIES.map(movie => this.getMovieByTitle(movie).subscribe(item => this.moviesList.push(item)));
2) Actually you should probably merge the output of each http.get:
public getDefaultMoviesList(): Observable<{}> {
return merge(DEFAULT_MOVIES.map(movie => this.http.get<{}>(this.buildRequestUrl(movieTitle))
.pipe(retry(3),
catchError(this.handleError)
)))
}
3) You should actually only do that once and store it in BehaviorSubject not to make new HTTP request on each getDefaultMoviesList
private movies$: BehaviorSubject<any> = new BehaviorSubject<any>();
public getMovies$() {
return this.movies$.mergeMap(movies => {
if (movies) return of(movies);
return merge(DEFAULT_MOVIES.map(movie => this.http.get<{}>(this.buildRequestUrl(movieTitle))
.pipe(retry(3),
catchError(this.handleError)
)))
})
}
4) Your implementation shouldn't work at all since:
public getDefaultMoviesList(): Observable<{}> {
DEFAULT_MOVIES.map(movie => this.getMovieByTitle(movie).subscribe(item =>
this.moviesList.push(item))); // This line will happen after http get completes
return from(this.moviesList); // This line will happen BEFORE the line above
}
So you will always return an Observable of empty array.
5) You shouldn't use map if you don't want to map your array to another one. You should use forEach instead.
map is used like this:
const mappedArray = toMapArray.map(element => someFunction(element));
You can try creating the observable using of operator.
Ex: of(this.moviesList);
One intersting fact to note is that Observable.of([]) will be an empty array when you subscribe to it. Where as when you subscribe to Observable.from([]) you wont get any value.
Observable.of, is a static method on Observable. It creates an Observable for you, that emits value(s) that you specify as argument(s) immediately one after the other, and then emits a complete notification.

Pass first item of subscribed data to service

I am developing angular application where I need apply following mechanism:
My view has 2 parts (list of items and detail if selected item). User can click on some item, and next service fetch additional data for that item and show them in detail view. Also I want select first item automatically on start if is available.
Here is my service:
#Injectable()
export class ItemService {
private url: string;
private itemSource = new BehaviorSubject<Item>(null);
selectedItem = this.itemSource.asObservable();
constructor(private http: HttpClient) {
this.url = 'http://localhost:8080/api/item';
}
getItems(): Observable<Item[]> {
let observable = this.http.get<Item[]>(this.url)
.map(items => items.map(item => {
return new Item(item);
}));
return observable;
}
selectItem(item: Item) {
return this.http.get<Item>(this.url + '/' + item.id)
.map(item => new Item(item))
.subscribe(t => this.itemSource.next(t));
}
}
in detail component I am subscribing selected item like this:
ngOnInit() {
this.itemService.selectedItem.subscribe(item => this.selectedItem = item);
}
and following code is from my component where I displayed list of items. I also want set selected item after data are subscribed but my code isn't works. I am iterating items[] property in html template and data are showed, but when I access to this array after I subscribed data I got undefined. Can you please fix my code? Thanks!
public items = [];
constructor(private itemService: ItemService) { }
ngOnInit() {
this.itemService.getItems()
.subscribe(
data => this.items = data,
err => console.log(err),
function () {
console.log('selected data', this.items); // this prints undefined
if (this.items && this.items.length) {
this.itemService.selectedItem(this.items[0])
}
});
}
Your problem is that you are not using an arrow function for the complete callback in your call to subscribe. As you see, you are using arrow functions for next and error.
When you define a new function with function(...) {...} you're creating a new context, and so the this keyword changes its meaning. The difference between arrow function and normal functions (besides being more elegant, in my opinion), is that arrow functions do not define a new context for this, and so the meaning of that keyword is the same as in the context they are defined. So, in your next and error callbacks, this is your component, but in your call to complete, this is, most surely, a reference to window, which does not have an items property, hence the undefined.
Change your code to:
public items = [];
constructor(private itemService: ItemService) { }
ngOnInit() {
this.itemService.getItems()
.subscribe(
data => this.items = data,
err => console.log(err),
() => {
console.log('selected data', this.items); // this prints undefined
if (this.items && this.items.length) {
this.itemService.selectedItem(this.items[0])
}
});
}
I imagine you used the function keyword there because that function had not arguments, but you can express that with the syntax () => expression, or () => {...}
data => this.items = data, after all, is a simpler and more elegant way of writing
(data) => { return this.items = data; }

Angular - detecting when two subscriptions have updated

In a component, in ngOnInit() I've got two subscriptions to a data service. I want to do some processing once both subscriptions have returned. Whats the best way to do this? I can just process at the end of each, this just seems a little inefficient and won't work for which ever subscription activates first,
Thanks,
Component.TS
ngOnInit()
{
this.dataService.dataA().subscribe((dataAJSON) =>
{
this.dataA= dataAJSON
}
this.dataService.dataB().subscribe((dataBJSON) =>
{
this.dataB= dataBJSON
}
DataService
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class PMDataService
{
constructor(public http : Http)
{
}
dataA()
{
var dataA: any;
var json;
dataA= this.http.get("./assets/dataa.json")
.map(res => res.json());
return dataA
}
dataB()
{
var dataB: any;
var json;
dataB= this.http.get("./assets/datab.json")
.map(res => res.json());
return dataB
}
}
You can use Observable#forkJoin function on the Observables. It emits the last value from each when all observables complete,
Observable.forkJoin(this.dataService.dataA(), this.dataService.dataB())
.subscribe(val => /* val is an array */)
The method used depends on how you want to receive the data:
You can use the zip function. Emits once when all have emitted once. So similar to Promise.all except not on completion.
Observable.zip(obs1, obs2).subscribe((val) => { ... });
You can use the forkJoin function. Emits once when all have completed. So exactly like Promise.all.
Observable.forkJoin(obs1, obs2).subscribe((val) => { ... });
You can use the merge function. Emits in order of emission so could be 1st then 2nd or 2nd then 1st:
obs1.merge(obs2).subscribe((val) => { ... });
You can use concat function. Emits in order 1st then 2nd regardless if 2nd emits first:
obs1.concat(obs2).subscribe((val) => { ... });
It's best practice to split these up into a couple lines for clarity.
const obs1 = Rx.Observable.of(1,2,3);
const obs2 = Rx.Observable.of(1,2,3);
const example = Observable.zip(obs1, obs2);
//const example = Observable.forkJoin(obs1, obs2);
//const example = obs1.merge(obs2);
//const example = obs1.concat(obs2);
example.subscribe(val => { ... });
You could use the operator Zip or CombineLatest from rxjs.
See ReactiveX operators
You could do something like this:
Observable.zip(
this.http.get("./assets/dataa.json"),
this.http.get("./assets/dataa.json")
.take(1)
.map(values => [values[0].json(), values[1].json()])
.subscribe(values => {
// do something with my values
});
You could You can use concat to combine the observables and return a single observable.
Subscribe to observables in order as previous completes, emit values
changed service code
import 'rxjs/add/operator/concat';
export class PMDataService
{
data(){
return this.dataA().concat(this.dataB());
}
// methods dataA and dataB are unchanged, some of the constructor
}
Component code
ngOnInit(){
this.dataService.data().subscribe((dataJSON) =>
{
this.dataA= dataAJSON[0];
this.dataB= dataAJSON[1];
}
}

Categories