I would simply like to delete an item on click, I made a code but I have error, I've been stuck on it for 2 days.
ERROR TypeError: this.addedBook.indexOf is not a function
I have already asked the question on the site we closed it for lack of information yet I am clear and precise
Thank you for your help
service
export class BookService {
url: string = 'http://henri-potier.xebia.fr/books';
public booktype: BookType[];
item: any = [];
constructor(private http: HttpClient) { }
getBookList(): Observable<BookType[]> {
return this.http.get<BookType[]>(this.url);
}
addToBook() {
this.item.push(this.booktype);
}
}
addToBook() here for add book but i dont know how to use it to display added books in my ts file
ts.file
export class PaymentComponent implements OnInit {
addedBook: any = [];
product:any;
constructor(private bookService: BookService) { }
ngOnInit(): void {
this.addedBook = this.bookService.getBookList();
}
delete() {
this.addedBook.splice(this.addedBook.indexOf(this.product), 1);
}
}
html
<div class="product" *ngFor="let book of addedBook | async">
<div class="product-image">
<img [src]="book.cover" alt="book">
</div>
<div class="product-details">
<div class="product-title">{{book.title}}</div>
</div>
<div class="product-price">{{book.price | currency: 'EUR'}}</div>
<div class="product-quantity">
<input type="number" value="1" min="1">
</div>
<div class="product-removal">
<button class="remove-product" (click)="delete()">
Supprimé
</button>
</div>
interface
export interface BookType {
title: string;
price: number;
cover: string;
synopsis: string;
}
I think this.bookService.getBookList() returns Observable so for you case it is not the best solution use async pipe. You should simply subscribe to your server response and than asign it to your variable. and after deleting item only rerender your ngFor.
JS
export class PaymentComponent implements OnInit {
addedBook: any[] = [];
product:any;
constructor(private bookService: BookService) { }
ngOnInit(): void {
// Observable
this.bookService.getBookList().subscribe(response =>{
this.addedBook = response;
});
// Promise
/*
this.bookService.getBookList().then(response=>{
this.addedBook = response;
})*/
}
delete(){
this.addedBook.splice(this.addedBook.indexOf(this.product), 1);
// rerender your array
this.addedBook = [...this.addedBook];
}
}
HTML
<div class="product" *ngFor="let book of addedBook">
<div class="product-image">
<img [src]="book.cover" alt="book">
</div>
<div class="product-details">
<div class="product-title">{{book.title}}</div>
</div>
<div class="product-price">{{book.price | currency: 'EUR'}}</div>
<div class="product-quantity">
<input type="number" value="1" min="1">
</div>
<div class="product-removal">
<button class="remove-product" (click)="delete()">
Supprimé
</button>
</div>
UPDATE
I built a special stackblitz so you can see it in action
here is the link;
you can't use javascript splice on Observable stream, it is not an Array.
to be able to remove an item from a stream you need to combine it (the stream) with another stream (in your case) the id of the item you want to remove.
so first create 2 streams
// the $ sign at the end of the variable name is just an indication that this variable is an observable stream
bookList$: Observable<any[]>; // holds bookList stream
deleteBook$ = new Subject<{ id: string }>(); // holds book id stream
now pass the results you get from your database (which is an observable stream) to bookList$ stream you just created like that
ngOnInit(): void {
this.bookList$ = this.bookService.getBookList().pipe(
delay(0)
);
}
change your html template to that.. and pipe the results from database like that
<div class="product" *ngFor="let book of (bookList$ | sync)">
...
// make sure you include your`remove-product` button inside `*ngFor` loop so you can pass the `book id` you want to remove to the `delete()` function.
<button class="remove-product" (click)="delete(book)">
Supprimé
</button>
</div>
now back to your ts file where we gonna remove the item from the STREAM by modifying the Array and return a new stream.
bookList$: Observable<any[]>; // holds bookList stream
deleteBook$ = new Subject<{ id: string }>(); // holds book id stream
ngOnInit(): void {
this.bookList$ = this.this.bookService.getBookList().pipe(
delay(0)
);
combineLatest([
this.bookList$,
this.deleteBook$
]).pipe(
take1(),
map(([bookList, deleteBook]) => {
if (deleteBook) {
var index = bookList.findIndex((book: any) => book.id === deleteBook.id);
if (index >= 0) {
bookList.splice(index, 1);
}
return bookList;
}
else {
return bookList.concat(deleteBook);
}
})
).subscribe();
}
now all is left to do is remove the item
delete(book: any) {
this.deleteBook$.next({ id: book.id }); pass the book you want to remove to the stream, `combineLatest` will take care of the rest
}
if you make an exit please don't forget me :)
good luck!
From your code, we can see that getBookList() return an Observable. As addedBook is not a array reference it will won't have array methods. That is the cause for your issue.
If you want to do some operations from the service data, subscribe to the observable and store the reference of the value to addedBook.
export class PaymentComponent implements OnInit {
...
ngOnInit(): void {
this.bookService.getBookList().subscribe(
res => { this.addedBook = res }
);
}
...
}
And you need to remove the async keyword from your html
Typescript is mainly used to identify these kind of issues in compile time. The reason it doesn't throw error on compile time is that you've specified addedBook as any. While declaring you declare it as array and onInit you change it to observable, which can be avoided if you've specified type[] ex: string[]
I would suggest something like this
Service file
export class BookService {
url: string = 'http://henri-potier.xebia.fr/books';
//add an observable here
private bookUpdated = new Subject<bookType>();
public booktype: BookType[] = [];//initializa empty array
item: any = [];
constructor(private http: HttpClient) { }
//Ive changet the get method like this
getBookList(){
this.http.get<bookType>(url).subscribe((response) =>{
this.bookType.push(response);//Here you add the server response into the array
//here you can console log to check eg: console.log(this.bookType);
//next you need to use the spread operator
this.bookUpdated.next([...this.bookType]);
});
}
bookUpdateListener() {
return this.bookUpdated.asObservable();//You can subscribe to this in you TS file
}
}
Now in your TS file you should subscribe to the update listener. This is typically done in NgOnInit
Something like this:
export class PaymentComponent implements OnInit {
addedBook: BookType;
product:any;
constructor(private bookService: BookService) { }
ngOnInit(): void {
this.bookService.bookUpdateListener().subscribe((response)=>{
this.addedBook = response;//this will happen every time the service class
//updates the book
});
//Here you can call the get book method
this.bookService.getBookList();
}
delete() {
this.addedBook.splice(this.addedBook.indexOf(this.product), 1);
}
}
Essentially what happens is you are subscribed to when books get changed or updated. Now you can simply use addedBook.title or whatever you want in your HTML.
Related
I'm working on building a set of filters, so I'm just trying to make use of the salesChannels array content in my view, which only gets populated when clicking the button with the test() function. The log in ngOnInit outputs an empty array the first time, but works correctly after pressing the button.
The getOrganisationChannels returns an observable.
What causes this behavior and how do I handle it properly? I tried using an eventEmitter to try and trigger the populating but that doesn't work.
TYPESCRIPT
export class SalesChannelFilterComponent implements OnInit {
constructor(
public organizationService: OrganizationService
) { }
#Input() organizationId: any;
salesChannels: Array<any> = [];
selectedChannels: Array<any> = [];
allSelected: Array<any> = [];
ngOnInit() {
this.getChannels();
console.log(this.salesChannels);
}
getChannels() {
this.organizationService.getOrganizationChannels(this.organizationId).subscribe(
salesChannels => {
this.salesChannels = salesChannels;
})
}
test() {
console.log(this.salesChannels);
}
}
HTML
<div>
{{ salesChannels | json }}
</div>
<button (click)="test()">test</button>
<div *ngFor="let channel of salesChannels; let i = index;" class="checkbox c-checkbox">
<label>
<input type="checkbox">
<span class="fa fa-check"></span>{{channel.name}}
</label>
</div>
This is expected behaviour since you are populating the salesChannel in the subscription of an Observable. It's recommended that you use aysnc pipe to let angular check for changes and update the view accordingly.
Component.ts :
export class SalesChannelFilterComponent implements OnInit {
constructor(
public organizationService: OrganizationService
) { }
#Input() organizationId: any;
salesChannels$!: Observable<Array<any>>;
selectedChannels: Array<any> = [];
allSelected: Array<any> = [];
ngOnInit() {
this.getChannels();
console.log(this.salesChannels);
}
getChannels() {
this.salesChannels$ = this.this.organizationService.getOrganizationChannels(this.organizationId);
}
test() {
console.log(this.salesChannels);
}
}
In your template:
<button (click)="test()">test</button>
<div *ngFor="let channel of salesChannels$ | async; let i = index;" class="checkbox c-checkbox">
<label>
<input type="checkbox">
<span class="fa fa-check"></span>{{channel.name}}
</label>
</div>
More details: https://angular.io/api/common/AsyncPipe
I recommend using AsyncPipe here:
<div>{{ salesChannels | async}}</div>
and in .ts:
salesChannels = this.organizationService.getOrganizationChannels(this.organizationId)
I just try to show the value of a property in the template. But at the moment nothing is shown.
So this is the component:
export class ServerStatusComponent implements OnInit {
snovieCollection: SnovietatusDto = {};
constructor(private snovierStatus: snovieStatusService) {}
ngOnInit(): void {
this.sensorStatus
.getSensorStatuses()
.pipe(
map((data) => {
console.log(data.cameraSensors);
})
)
.subscribe((status) => {
});
}
}
And this is the template:
<p>Camera sensoren</p>
<tr *ngFor="let camera of snovieStatusCollection.key|keyvalue">
test
<h3> {{camera | json}}</h3>
</tr>
So I just want to show in the template the value of key. And the console.log returns this:
0: {key: "T", latestTimestamp: "2021-03-12T10:09:00Z"}
So I don't get any errors. But also nothing is shown.
Two things:
You aren't returning anything from the map. So undefined would be emitted to the subscription. Use tap for side-effects instead.
You aren't assigning the response to this.sensorStatusCollection in the subscription.
export class ServerStatusComponent implements OnInit {
sensorStatusCollection: SensorStatusDto = {};
constructor(private sensorStatus: SensorStatusService) {}
ngOnInit(): void {
this.sensorStatus
.getSensorStatuses()
.pipe(
tap((data) => { // <-- `tap` here
console.log(data.cameraSensors);
})
)
.subscribe((status) => {
this.sensorStatusCollection = status; // <-- assign here
});
}
}
Update: Type
As pointed out by #TotallyNewb in the comments, the type of this.sensorStatusCollection needs to be an array of type SensorStatusDto
export class ServerStatusComponent implements OnInit {
sensorStatusCollection: SensorStatusDto[] = [];
...
}
So im doing the Online Course from Mosh Hamedami. I've been following it more or less but everything is working. I can Add Products to Firebase and Edit/Delete them.
Now I want to implement a "shopping-cart" where I can add a specific CardId and add Items + quantity to that cardId
However I'm facing a problem where i want to add a Product to the firebase shopping-cart. Adding a shopping-cart with an cartId - timestamp works.
Somehow I can't add an item to the cartId.
I got a Problem with the Observable I guess...
the Console Error is :
_core.js:6260 ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'key' of undefined
TypeError: Cannot read property 'key' of undefined_
at ShoppingCartService.<anonymous> (shopping-cart.service.ts:51)
It tells me that my product from 'addToCart' is undefined.
addToCart(product: Product) {
this.shoppingCartService.addToCart(product);
console.log(product);
}
Also my complier tells me that take form .pipe(take(1)) is:
_error TS2684: The 'this' context of type 'void' is not assignable to method's 'this' of type'Observable'.
somehow pipe(first()) works, but is delivering the same problem with the key.
I've tried a lot can't figure out my mistakes. Im new to Angular as well so..I would really appreciate any kind of tipps where I have to search for the Problem.
shopping-cart.service.ts
import {Injectable} from '#angular/core';
import {AngularFireDatabase} from "#angular/fire/database";
import {Product} from "./models/product";
import 'rxjs/add/operator/take';
import {take} from "rxjs-compat/operator/take";
import {pipe} from "rxjs";
import {Observable} from "rxjs";
import {first} from "rxjs/operators";
#Injectable({
providedIn: 'root'
})
export class ShoppingCartService {
constructor(private db: AngularFireDatabase) {
}
//create shopping cart in db + add date(timestamp)
private create() {
return this.db.list("/shopping-carts").push({
dateCreated: new Date().getTime()
});
}
private getCart(cartId: string) {
return this.db.object('/shopping-carts/' + cartId);
}
private getItem(cartId: string, productId: string) {
return this.db.object('/shopping-carts/' + cartId + '/items/' + productId);
}
//getting reference to shopping cart
private async getOrCreateCartId(): Promise<string> {
let cartId = localStorage.getItem('cartId');
if (cartId) return cartId;
// calls this.create and waits for the response
let result = await this.create();
//saves cartId in local storage and returns
localStorage.setItem('cartId', result.key);
return result.key;
}
//add new product to cart and safe localstorage
async addToCart(product: Product) {
const cartId = await this.getOrCreateCartId();
//reference to products in firebase cart
const item$ = this.getItem(cartId, product.key);
item$.snapshotChanges().pipe(take(1)).subscribe((item: any) => {
if (item) {
item$.update({product: product, quantity: (item.quantity || 0) + 1});
}
});
}
}
product.ts
export interface Product {
key : string;
title : string;
price : number;
category : string;
imageURL : string;
}
html which is calling addToCart:
<div class="columns is-multiline is-narrow">
<ng-container *ngFor="let p of filteredProductsByCategory">
<div class="column is-one-third">
<div class="card">
<figure class="image is-square">
<img [src]="p.imageURL" alt="{{p.title}}">
</figure>
</div>
<div class="card-content">
<p class="title"> {{p.title}}</p>
<p class="subtitle">{{p.price | currency: "USD"}}</p>
<div
(click)="addToCart(product)"
class="button is-outlined is-primary"> Add to Cart</div>
</div>
</div>
</ng-container>
</div>
I could also upload my code to GitHub if needed!
Thanks!!
// add new product to cart and safe localstorage
async addToCart(product: Product) { ... }
With a very high probability when calling addToCart the product is simply not set.
You should check the point (*.component.ts or *.component.html?) where the function is called and and product should be set.
edit:
I guess you want to pass p instead of product. Try following:
<div
(click)="addToCart(p)"
class="button is-outlined is-primary"> Add to Cart</div>
I have a page with three components:
1. Products list component which gets some products as input and display them.
2. Filters component which displays some filters list i.e. (size, colour,...) and also display the added filters.
3. Main component which is the root component
Let say a user adds 1 filter which fires a http request to get new filtered products and while the request is pending he removes the added filter which fires another http request to fetch all the products
How to cancel the first request so we don't display the filtered products?
Here is my code:
class FiltersService {
private _filters: any[];
get filters() {
return this._filters;
}
addFilter(filter) {
this._filters.push(filter);
}
removeFilter(filter) {
// Remove filter logic ...
}
}
class DataService_ {
constructor(private http: HttpClient) {
}
getProducts(filters) {
return this.http.post<any[]>('api/get-products', filters)
}
}
#Component({
selector: 'app-main',
template: `
<div>
<app-filters [filtersChanged]="onFiltersChange()"></app-filters>
<app-products-list [products]="products"> </app-products-list>
</div>
`
})
class MainComponent {
products: any[];
constructor(private dataService: DataService_, private filtersService: FiltersService) {
}
ngOnInit() {
this.setProducts()
}
setProducts() {
let filters = this.filtersService.filters;
this.dataService.getProducts(filters)
.subscribe(products => this.products = products)
}
onFiltersChange() {
this.setProducts();
}
}
#Component({
selector: 'app-filters',
template: `
<div>
Filters :
<ul>
<li *ngFor="let filter of filters" (click)="addFilter(filter)"> {{ filter.name }}</li>
</ul>
<hr>
Added Filters:
<ul>
<li *ngFor="let filter of filtersService.filters"> {{ filter.name }} <button (click)="removeFilter(filter)"> Remove</button></li>
</ul>
</div>
`
})
class FiltersComponent {
filters = [{ name: 'L', tag: 'size' }, { name: 'M', tag: 'size' }, { name: 'White', tag: 'colour' }, { name: 'Black', tag: 'colour' }]
#Output() filtersChanged = new EventEmitter()
constructor(public filtersService: FiltersService) {
}
addFilter(filter) {
const isAdded = this.filtersService.filters.find(x => x.name === filter.name);
if (isAdded) return;
this.filtersService.addFilter(filter);
this.filtersChanged.emit()
}
removeFilter(filter) {
this.filtersService.remove(filter);
this.filtersChanged.emit()
}
}
#Component({
selector: 'app-products-list',
template: `
<div>
<h1>Products</h1>
<ul *ngIf="products.length">
<li *ngFor="let product of products">
{{product.name }}
</li>
</ul>
</div>
`
})
class ProductsListComponent {
#Input() products
constructor() {
}
}
Long story short:
Easiest way to handle such situations is by using the switchMap operator. What this does is cancel the internal subscription as soon as a new event comes along.
One implementation would be:
class MainComponent {
products: any[];
private _filters$ = new Subject();
constructor(private dataService: DataService_, private filtersService: FiltersService) {
}
ngOnInit() {
this.setProducts()
}
setProducts() {
this._filters$
.switchMap((filters)=> this.dataService.getProducts(filters)) // or .let(switchMap...) if you are using rxjs >5.5
.subscribe(products => this.products = products);
}
onFiltersChange() {
this._filters$.next(this.filtersService.filters);
}
}
Long story:
What happens here is:
When you change filter the onFilterChange is triggered. You then emit the latest filters (inside this.filtersService.filters) through the _filters$ Subject (a subject is almost identical to an EventEmitter).
Back in time during component initialization the ngOnInit method has called setProducts, which has subscribed to the _filters$ subject for future events (none has happened at this point). When an event arrives on _filters$ then we trigger the getProducts method of dataservice, passing it the filters that where contained in the event. We will be waiting on this line until the http call has completed. As soon as it completes the result of the http call will be assigned to the products of the component.
If while we are waiting for the http response to get back, onFiltersChange is fired again, then a new event will arive at the switchMap and it will cancel the previous http request so that it can handle the new event.
This is a very powerful approach as changing a single operator, you can easily change the behavior of your app. For instance, changing switchMap to concatMap will make the request wait for the previous one to complete (will happen serially). Changing it to flatMap will have the same behaviour as the original code you posted (http requests will happen as soon as filters change, without affecting previous ones, order of responses will not predictable) and so on.
Note : to cancel the request just use unsubscribe.
For exmple
const course$ = this.service$.getCourses(`/api/courses`).subscribe(courses => { console.log(courses) }
setTimeout(() => course$.unsubscribe(),1000) // cancel the request
I need to create new list item(value from api)on button press but don't know how to do it. Any help please?
here is the code:
<ul>
<li *ngFor="let joke of jokes">{{joke.value}}</li>
</ul>
<button (click)="loadMore">more jokes</button>
`,
providers: [RandomService]
})
export class PocetnaComponent {
jokes: Joke[];
constructor(private jokesService: RandomService){
this.jokesService.getRandomJokes().subscribe(jokes => {this.jokes =
[jokes]});
}
loadMore(){
this.jokes.push();
}
}
interface Joke{
id: number;
value: string;
}
here is the service:
#Injectable()
export class RandomService {
constructor(private http: Http){
console.log('working');
}
getRandomJokes(){
return this.http.get('https://api.chucknorris.io/jokes/random')
.map(res => res.json());
}
}
Just push an empty object
this.jokes.push({});
or if its going to be hooked up to a modal
Create a class and push that
Class IJoke {
id: number;
value: string;
constructor(){
}
}
this.jokes.push(new IJoke());
Or if you want to push from an API
#Injectable()
export class RandomService {
constructor(private http: Http){
console.log('working');
}
getRandomJokes(){
return this.http.get('https://api.chucknorris.io/jokes/random')
.map(res => res.json());
}
getNextJoke(){
return this.http.get('https://api.chucknorris.io/jokes/next')
.map(res => res.json());
}
}
Directive
loadMore(){
this.jokesService.getNextJoke().subscribe(joke => {
this.jokes.push(joke);
});
}
I'm not sure if you load some random jokes and you want to load one more, or if you want to keep loading random jokes. If the later, you will want to take out the next function, and instead init your jokes array and keep pushing/applying to it. like so
jokes: Joke[] = new Array();
constructor(private jokesService: RandomService){
this.jokesService.getRandomJokes().subscribe(jokes => {
this.jokes.push(jokes)
});
You have a few problems...
You have this interface:
interface Joke{
id: number;
value: string;
}
what you are receiving is much more properties, so you'd need to pick the properties you want:
getRandomJokes(){
return this.http.get('https://api.chucknorris.io/jokes/random')
.map(res => res.json());
// pick the properties you want/need
.map(joke => <Joke>{id: joke.id, value: joke.value})
}
Then you have problems in the subscribe, you should push the data to your jokes array and not do:
.subscribe(jokes => {this.jokes = [jokes]})
but:
.subscribe(joke => this.jokes.push(joke)}
notice above that I named this (joke => this.jokes.push(joke)) to make it clearer that you are actually just receiving one joke.
Also I would remove the request from the constructor, we have the OnInit hook for this. Also I would apply the request in a separate function, so that it's easy to call when you want to retrieve new jokes and also therefore reuse the function, so something like this:
ngOnInit() {
this.getJoke()
}
getJoke() {
this.jokesService.getRandomJokes()
.subscribe(joke => {
this.jokes.push(joke)
})
}
So then in your template just call getJoke when you want to retrieve a new joke:
<ul>
<li *ngFor="let joke of jokes">{{joke.value}}</li>
</ul>
<button (click)="getJoke()">more jokes</button>
Here's a DEMO