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

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));

Related

Observable rxjs filter

In my project, I wanted to create some sort of "Recommended Products" in each product page,
but having trouble with making my function filtering an observable.
I have tried using .pipe(filter()) in different ways, but to no use.
Basically the fucntion should filter products with the same type and id, and show them in the proper product page, but pretty much got stuck after subscribing all of my products(which is marked down below).
Much Appreciated!
import { Component, OnInit } from '#angular/core';
import { ProductService } from '../services/product.service';
import { ActivatedRoute, ParamMap, Router } from '#angular/router';
import Product from '../interfaces/product';
import { map, filter} from 'rxjs/operators';
import { Observable } from 'rxjs';
#Component({
selector: 'app-product',
templateUrl: './product.component.html',
styleUrls: ['./product.component.css']
})
export class ProductComponent implements OnInit {
recommandedProducts: Product[];
allProducts:Product[];
// allProducts:Observable< Product> = new Observable< Product>();
product: Product;
constructor(private productService: ProductService, private route: Router, private actRoute: ActivatedRoute) { }
ngOnInit() {
this.findRecommendedProducts(this.product)
};
//From ProductService:
// getProducts(){
// return this.http.get(`${this.uri}`);
// }
findRecommendedProducts(currectProduct: Product){
this.productService.getProducts().subscribe((data: Product[]) => {
this.allProducts = data;
console.log(this.allProducts)
this.recommandedProducts = this.allProducts.filter(otherProduct =>
otherProduct.type == currectProduct.type && otherProduct.id == currectProduct.id)
console.log(this.recommandedProducts);
});
};
}
A filter in rxjs is not the same as an Array.filter. In rxjs, a filter is used to emit values that pass the provided condition. So if you use a filter, based on the condition, the observable will either emit your data or return nothing.
Instead, what you need is pipe(map) along with an Array.filter. Also, as #jzzfs pointed out, your error shows currentProduct could be undefined, so you can pass a default value in your findRecommendedProducts function.
Try the below code.
findRecommendedProducts(currectProduct: Product = {} as Product) {
this.productService.getProducts()
.pipe(
map((products: Product[]) => products.filter(product => product.type == currectProduct.type && product.id == currectProduct.id))
)
.subscribe((data: Product[]) => {
this.recommandedProducts = data;
});
};
Now your subscribed data should directly return the recommendedProducts.
Looks like the currectProduct passed onto findRecommendedProducts is undefined, since the logged this.allProducts do contain values.
With that being said, notice that when you define product: Product, you've only defined its type but you have not initialized it. The default value is therefore undefined -- if I remember correctly.
So change product: Product to product: Product = {};, for instance or pass a value to it within the constructor or within ngInit.

RxJS Subject never pushed the first next

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');
});
}

Showing the loading spinner icon on all components

I have written the code to show the loading spinner on all components when any event is triggered. It works fine on a single component but the issue with it, I have to show the same loading spinner on the around multiple components when certain event is triggered. See below code:
tasks() {
this.handler.activateLoader();
this.tasksService.get(this.page, this.pageSize).subscribe(results => {
this.handler.hideLoader();
if (this.handler.handle(results)) {
return;
}
this.tasksRes = results['data'];
for (let i = 0; i < this.tasksRes.length; i++) {
if (this.tasksRes[i].status == 'In_progress' && this.tasksRes[i].eventType == 'Sync' &&
this.tasksRes[i].entityId == this.id) {
this.progressFlag = true;
break;
} else {
this.progressFlag = false;
}
}
this.length = results['totalElements'];
}, error => {
this.handler.hideLoader();
this.handler.error(error);
});
}
connect() {
let source = new EventSource('/api/v1/events/register');
source.addEventListener('message', message => {
this.tasks();
});
}
And on ngOnInit(), I have called these 2 methods as below then its working fine.
ngOnInit() {
this.tasks();
this.connect();
}
The actual requirement is when I run a particular event the button is going to be disabled and at the same time the spinner loading will come. I have achieved this one. But how to show the same spinner on multiple components so that the user can know that the task is running.
This is how I am showing the loading spinner. See below:
<span class="text-warning pull-right" *ngIf="progressFlag">
<i class="fa fa-spinner fa-spin fa-2x"></i>
</span>
In my code, I have many components at around 17-18 where I need to show the loading spinner. If I want to show the spinner globally means I can show it on either header and footer component which is common to my entire template. Can any one provide any ideas on it.
Thanks.
Please search keyword HttpInterceptor learn details. One simple example below:
// siteHttpInterceptor.ts
import { Injectable } from '#angular/core';
import { HttpRequest, HttpInterceptor, HttpHandler, HttpEvent, HttpResponse } from '#angular/common/http';
import { throwError } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
import { LoadingService } from './loading.service';
#Injectable()
export class SiteHttpInterceptor implements HttpInterceptor {
constructor(private loadingService: LoadingService){}
intercept(request: HttpRequest<any>, httpHandler: HttpHandler): Observable<any> {
/* Start loading here */
this.loadingService.startLoading();
return httpHandler.handle(request).pipe(
tap((event: HttpEvent<any>) => {
/* End loading */
this.loadingService.endLoading();
},
(err: any) => {
/* End loading */
this.loadingService.endLoading();
}),
catchError(err => {
return throwError(err);
})
);
}
}
//loading.service.ts LoadingService base on Ionic framework, you can instead it
import { Injectable } from '#angular/core';
import { LoadingController } from '#ionic/angular';
#Injectable({
providedIn: 'root'
})
export class LoadingService {
private loaders = [];
//sometimes, the request so quickly then close event earlier than open loading bar
private badLoaders = 0;
constructor(
private loadingController: LoadingController
) {
}
async startLoading() {
if (this.badLoaders > 0) {
this.badLoaders --;
} else {
await this.loadingController.create({
message: 'Loading ...',
}).then(loader => {
this.loaders.push(loader);
loader.present().then(() => {
//if it is bad loader, close
if (this.badLoaders > 0) {
this.badLoaders --;
this.endLoading();
}
});
});
}
}
endLoading() {
let loader = this.loaders.pop();
if (loader) {
loader.dismiss();
} else {
// it is mean close event earlier
this.badLoaders ++;
}
}
}
Use it then you not need manage loader handle each request method.
Put your spinner at the main component.. in most cases its the AppComponent
Then put a these on your shared service
private LoadingStatus = new Subject<boolean>();
// Observable string streams
IsLoading$ = this.LoadingStatus.asObservable();
// Service message commands
triggerLoading(status: boolean) {
this.LoadingStatus.next(mission);
}
Then at your sender component call triggerLoading(true) or triggerLoading(false) from the service and subscribe at your main component (AppComponent):
this.shareService.IsLoading$.subscribe( data => progressFlag = data )
Or Add your logic as this:
this.shareService.IsLoading$.subscribe(
data => {
if(data) {
// start loading logic here
} else {
// end loading logic here
}
}
)
Source: Angular - Component Interaction

Angular List Component not updating after List Array sorted in Service

I'm currently learning how to use Angular (version 5+) correctly by making a small project. I've set up some basic routing to load the ContactList component by selecting a navigation tab. The other tab allows one to add a contact using a form which then sends the form data to the Contact Data Service where the contacts are managed in an array.
The ContactList Component is as follows:
import { Component, OnInit, Input, Output,
OnChanges, DoCheck, SimpleChanges} from '#angular/core';
import { ContactDataService } from '../ContactData.service';
// import { FilterPipe } from '../../../src/pipes';
import { Contact } from '../contact.model';
#Component({
selector: 'app-contact-list-component',
templateUrl: './contact-list-component.component.html',
styleUrls: ['./contact-list-component.component.css']
})
export class ContactListComponent implements OnInit, OnChanges, DoCheck {
contactList: Contact[] = [];
searchQuery: string; // alternate subscribed data from contactDataService
contactServObj: ContactDataService;
constructor(contactServObj: ContactDataService) {
this.contactServObj = contactServObj;
}
ngOnChanges(changes: SimpleChanges) {
console.log('ngOnChanges called!');
console.log(changes);
for (const key of Object.keys(changes)) {
console.log(`${key} changed.Current: ${changes[key].currentValue}.Previous: ${changes[key].previousValue}`);
}
}
ngOnInit() {
this.contactList = this.contactServObj.getContacts();
// console.log('ngOnInit called!');
this.contactServObj.queryString.subscribe((query: string) => {
this.searchQuery = query;
});
}
ngDoCheck() {
console.log('ngDoCheck called!');
}
}
The Contact Data Service (where contact array is managed) is as follows:
import { EventEmitter } from '#angular/core';
import { Contact } from './contact.model';
export class ContactDataService {
private contactList: Contact[] = [];
getContacts() {
return this.contactList;
}
sortContactHandler = (value) => {
console.log(value);
const field = value === 'default' ? null : value;
if (this.contactList.length > 1 && field !== null) {
this.contactList.sort(this.compareValues(field, 'asc'));
console.log(this.contactList);
}
}
compareValues = (key, order= 'asc') => {
return function(a, b) {
if (!a.hasOwnProperty(key) ||
!b.hasOwnProperty(key)) {
return 0;
}
const varA = (typeof a[key] === 'string') ?
a[key].toUpperCase() : a[key];
const varB = (typeof b[key] === 'string') ?
b[key].toUpperCase() : b[key];
let comparison = 0;
if (varA > varB) {
comparison = 1;
} else if (varA < varB) {
comparison = -1;
}
return (
(order === 'desc') ?
(comparison * -1) : comparison
);
};
}
addContactHandler(sentContact: Contact) {
let field = '';
const findExistingContact = this.contactList.findIndex((el, i) => {
// return (name === el.name) || (phone === el.phone) || (email === el.email);
if (sentContact.name === el.name) {
field = 'name';
return true;
} else if (sentContact.phone === el.phone) {
field = 'phone';
return true;
} else if (sentContact.email === el.email) {
field = 'email';
return true;
}
return false;
});
console.log(findExistingContact);
if (findExistingContact === -1) {
const newContact: Contact = sentContact;
this.contactList.push(newContact);
} else {
alert(`Contact with field ${field} already exists!`);
}
}
}
The Contact Data Service is injected at the root level - in app.module.ts as shown below:
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { Routes, RouterModule } from '#angular/router';
import { AppComponent } from './app.component';
import { ContactComponent } from './contact-component/contact-component.component';
import { ContactListComponent } from './contact-list-component/contact-list-component.component';
import { AddContactComponent } from './add-contact-component/add-contact-component.component';
import { SearchContactComponent } from './search-contact-component/search-contact-component.component';
import { BackdropComponent } from './backdrop/backdrop.component';
import { ModalComponent } from './modal/modal.component';
import { EditContactComponent } from './edit-contact/edit-contact.component';
import { ContactDataService } from './ContactData.service';
import { ModalShowService } from './modal-show.service';
import { CockpitComponentComponent } from './cockpit-component/cockpit-component.component';
import { FilterContactPipe } from './filter-contact.pipe';
import { SortComponent } from './sort/sort.component';
import { ContactDetailComponent } from './contact-detail/contact-detail.component';
const appRoutes: Routes = [
{ path: '' , component: AddContactComponent },
{ path: 'contactList', component: ContactListComponent },
{ path: 'contactList/:name', component: ContactDetailComponent }
];
#NgModule({
declarations: [
AppComponent,
ContactComponent,
ContactListComponent,
AddContactComponent,
SearchContactComponent,
BackdropComponent,
ModalComponent,
EditContactComponent,
CockpitComponentComponent,
FilterContactPipe,
SortComponent,
ContactDetailComponent
],
imports: [
BrowserModule,
FormsModule,
RouterModule.forRoot(appRoutes)
],
providers: [ContactDataService, ModalShowService],
bootstrap: [AppComponent]
})
export class AppModule { }
Lastly, I have a Sort Component which is just a simple drop-down select box containing the 3 fields to sort contacts on. All it does is send this field to the sortContactHandler function in the Contact Data Service and I see the result of this in the console.log(this.contactList) statement. But this change is not reflected in the ContactList component which renders the array.
Question: What is the best way to get this sorted contactList array to my ContactList component (it only retrieves this array in the ngOnInit() function)?
I was thinking this could be accomplished using life-cycle hooks and so, I have tried to understand them, but maybe I'm doing something wrong. The ngOnChanges() never gets called (not once have I seen a console.log() from within that function) and I'm not sure how to use ngDoCheck(). Also from what I have read about the above life-cycle methods is that they react to changes in input properties of the component, so I'm not sure they can be used in my case.
Here is the issue on StackBlitz
NgOnChanges - reacts to changes in values of properties declared with #Input decorators but you have a normal property in your component class.It wont detect the change.
As far as i understand your scenario, you have both the siblings component on UI, you are changing service class's property value from one of the component and expecting that change in another component but there is no way you are reading that change in component except in OnInit (which gtes executed once when you load the component) , so if your component stays there in the DOM and you are expecting changes to be reflected.
You can go with Observable data stream , so basic idea is to have Observable data in your service class and you are supposed to subscribe to that data at other places (in your component classes) so that whenever you emit any changes on Observable stream , subscribers will get notified.
you can try to have observable 'contactList` in your service as below,
private contactList = new Subject<Contact[]>();
contactList$ = this.contactList.asObservable();
and then you can subscribe to it in your contact List component where you are expecting the changes to get reflected as below.
serviceObj.contactList$.subscribe(
contactList => {
this.contactList= contactList;
})
and to execute the code that you write inside of your subscribe function you need to have this code in your service class
this.contactList.next(contactList)
above line is execution of next method of an observable object,definition of which you have written as a first parameter to subscribe function.
If you can upload your code somewhere , i can have a better look and suggest.
P.S. - even i am new to Angular/RxJs , there could be better technical term that one can use, please feel free to modify.
AFTER LOOKING INTO YOUR CODE
I came up with this easiest fix for you.
add an event in your sort component.
emit the event when you have the contactList updated.
have the code to update the contactList in contact List component.
This is all what you need to add.
contact-list.component.html
<app-sort (contactListUpdate)="fetchUpdatedList()"></app-sort>
contact-list.component.ts
fetchUpdatedList(){
this.contactList = this.contactServObj.getContacts();
}
sort-contact.component.ts
#Output() contactListUpdate:EventEmitter<any>=new EventEmitter<any>();
constructor(private contactServObj: ContactDataService) { }
onSelectChange(event) {
this.contactServObj.sortContactHandler(event.target.value);
this.contactListUpdate.emit();
}
I think its quite self explanatory, you want to execute some code in parent component based on some condition in child component, which is when we can utilize event emitter to emit event from child component, and listen to that event in parent component.
Please try this and let me know if you want any explanation or code to be uploaded.

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.

Categories

Resources