The app is written using Ionic. I have a shopping cart where i'm adding products. The product is an object with a note and quantity property. After i use the this.store.dispatch(new PushProductAction({ product })); the note property is concatenated with itself, i.e. i'm adding 3 products to cart with a note 'cold', in the cart this product will have the note property equal to 'coldcoldcold'.
app-state.ts
export class AppState {
categories: Category[];
shopcart: ShopCart;
profile: Profile;
userdata: UserData;
}
product-info.ts
constructor(
private events: Events,
public store: Store<AppState>,
}
pushToCart(product: Product) {
product.quantity = product.quantity ? ++product.quantity : 1;
product.withHalf = this.halfProductStatus;
this.store.dispatch(new PushProductAction({ product }));
}
After the product is added :
goToConfirm(event, desktop = false) {
event.preventDefault();
event.stopPropagation();
jQuery.fancybox.close();
jQuery('.fancybox-close-small').trigger('click');
for (let index = 0; index < this.count; index++) {
this.pushToCart(this.product);
}
this.fixDesign.CreateToast('Product has been added to cart successfuly', 'successToast');
}
actions.ts
export class PushProductAction implements Action {
readonly type = ActionTypes.PUSH_CART;
constructor(public payload: { product: Product; half?: boolean }) {
console.log('PushProductAction');
}
}
P.S. I'm a beginner in angular, if you need more code snippets please let me know.
Related
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[] = [];
...
}
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.
I have this array of recipes where each object is a specific recipe and each recipe has an array of ingredients and each ingredient it's an object made of _id name quantity. My problem added below if you guys have any idea why is this happening please let me know. I am struggling for 3 days...any advice would be really appreciated. Thanks a lot!
(3) [{…}, {…}, {…}]
0:
ingredients: Array(4)
0: {_id: "5f6628d0029e87e02c79ce0a", name: "chia", quantity: 10}
1: {_id: "5f6628d0029e87e02c79ce0b", name: "apple", quantity: 15}
2: {_id: "5f6628d0029e87e02c79ce0c", name: "honey", quantity: 30}
3: {_id: "5f6628d0029e87e02c79ce0d", name: "almond flour", quantity: 35}
length: 4
__proto__: Array(0)
name: "Coconut Chia Pudding"
__v: 0
_id: "5f6628d0029e87e02c79ce09"
__proto__: Object
1: {_id: "5f6628d0029e87e02c79ce0e", name: "Peanut Butter Cookies", ingredients: Array(4), __v: 0}
2: {_id: "5f6628d0029e87e02c79ce13", name: "Caprese Avocado Bowls", ingredients: Array(3), __v: 0}
length: 3
__proto__: Array(0)
What I have in UI it's a list with the above recipes which a user can tick and untick and after a recipe has been ticked its ingredients are showed into a list.
HTML
<ion-content>
<ion-grid>
<ion-row>
<ion-col>
<ion-list>
<ion-item
*ngFor="let recipe of loadedRecipes; let lastRecipe = last"
[ngClass]="{ 'last-recipe': lastRecipe }"
>
<ion-checkbox
(ionChange)="onCheckRecipe($event)"
value="{{recipe.name}}"
></ion-checkbox>
<ion-label>{{recipe.name}}</ion-label>
<ion-button
[routerLink]="['/','recipes','recipe-details', recipe._id]"
>></ion-button
>
</ion-item>
</ion-list>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<h6 class="ion-padding-start" *ngIf="groceryList.length > 0">
Grocery List
</h6>
<ion-list *ngIf="groceryList.length > 0">
<ion-item *ngFor="let ingredient of groceryList">
<ion-label>{{ingredient.name}}</ion-label>
<ion-note slot="end">{{ingredient.quantity}} g</ion-note>
</ion-item>
</ion-list>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>
What I want to achieve (and I've done it below, but I have a bug) is when the user ticks a recipe its ingredients to be added into an array called groceryList and when unticks the recipe to remove the ingredients from my groceryList array. Also, if I have ticked 1 recipe and if the next one I am going to tick has the same ingredient as the one ticked before, just to increment that common ingredient quantity that already exists, and NOT to add it twice and if I want to untick a recipe to remove the uncommon ingredients and subtract the quantity of the common ingredient. I already managed to do it, BUT I have a big problem and I don't know where is coming from. at some point in UI if I tick and untick recipes and I tick the ones that have the same ingredient one after another it removes the common ingredient even though I still have a ticked recipe that has that ingredient. Again, if you guys have any idea why is this happening please let me know I would appreciate any advice you have
My TS
import { Component, OnInit } from "#angular/core";
import { Subscription } from "rxjs";
import { RecipesService } from "src/app/services/recipes.service";
#Component({
selector: "app-recipes",
templateUrl: "./recipes.page.html",
styleUrls: ["./recipes.page.scss"],
})
export class RecipesPage implements OnInit {
loadedRecipes: any;
private _recipesSub: Subscription;
constructor(private recipesService: RecipesService) {}
groceryList = [];
ngOnInit() {
this._recipesSub = this.recipesService.recipes.subscribe((receivedData) => {
this.loadedRecipes = receivedData;
});
}
onCheckRecipe(e) {
if (e.detail.checked === true) {
for (let recipe of this.loadedRecipes) {
console.log(this.loadedRecipes);
if (recipe.name === e.detail.value) {
for (let eachIngredient of recipe.ingredients) {
let matchedIng = this.groceryList.find(function (foundIng) {
return foundIng.name === eachIngredient.name;
});
if (matchedIng) {
matchedIng.quantity =
matchedIng.quantity + eachIngredient.quantity;
} else {
this.groceryList.push(eachIngredient);
}
}
}
}
} else {
for (let recipe of this.loadedRecipes) {
if (recipe.name === e.detail.value) {
for (let eachIngredient of recipe.ingredients) {
let matched = this.groceryList.find(function (foundIngre) {
return foundIngre.name === eachIngredient.name;
});
if (
matched.name === eachIngredient.name &&
matched._id === eachIngredient._id
) {
let index = this.groceryList.findIndex(
(x) => x._id === matched._id
);
this.groceryList.splice(index, 1);
} else {
matched.quantity = matched.quantity - eachIngredient.quantity;
}
}
}
}
}
}
ionViewWillEnter() {
this.recipesService.fetchRecipes().subscribe();
}
ngOnDestroy() {
if (this._recipesSub) this._recipesSub.unsubscribe();
}
}
The problem lies in the flow of your if statements. In the "onRemove" section of your code, you are saying "If the ingredient is in the list, remove it from the list. If not, decrement its quantity." That second part doesn't make any sense and more importantly, you'll never reach it because the ingredient should always be in the list.
for (let eachIngredient of recipe.ingredients) {
let matched = this.groceryList.find(function(foundIngre) {
return foundIngre.name === eachIngredient.name;
});
if (
matched.name === eachIngredient.name &&
matched._id === eachIngredient._id
) {
let index = this.groceryList.findIndex(
(x) => x._id === matched._id
);
// Problem e ca eachIngredient.quantity se schimba
this.groceryList.splice(index, 1);
} else {
matched.quantity = matched.quantity - eachIngredient.quantity;
}
}
Based on what you've said, what you want to do is:
subtract the quantity which was attributed to the removed recipe
if the new quantity is zero, remove the ingredient from the list (though you could also leave it and ignore ingredients with zero quantity)
Try this instead:
for (let eachIngredient of recipe.ingredients) {
// I am assuming that ids are unique so I am not checking foundIngre.name at all,
// since I assume that ingredients with the same name must also have the same name
// I am also using findIndex first so that you don't need a second find when removing
const matchIndex = this.groceryList.findIndex(
(foundIngre) => foundIngre._id === eachIngredient._id
);
if ( matchIndex ) { // this should always be true
const matched = this.groceryList[matchIndex];
// preserve the entry if there is still some quantity
if ( matched.quantity > eachIngredient.quantity ) {
matched.quantity = matched.quantity - eachIngredient.quantity; // can use -= to shorten
}
// remove from the list only if there is no quantity remaining
else {
this.groceryList.splice(matchIndex, 1);
}
}
}
EDIT:
Trying to update and remove items in an array is an unnecessary pain. The reworked version of your code stores the _groceryList in a keyed dictionary instead. I initially intended to key by ingredient id, but after reviewing your demo I see that I was incorrect in my assumption that the same ingredient in multiple recipes would share the same id. So instead I am keying by ingredient name. This way you can write to _groceryList[name] and it doesn't matter whether it previously existed or not.
The class has a public getter groceryList which converts the private _groceryList dictionary into an array.
I've also tried to remove unnecessary code duplication across scenario branches by using a generic toggleIngredient function which uses the boolean checked to control whether it is adding or subtracting by multiplying by plus or minus one.
import { Component } from "#angular/core";
import { Platform } from "#ionic/angular";
import { SplashScreen } from "#ionic-native/splash-screen/ngx";
import { StatusBar } from "#ionic-native/status-bar/ngx";
import { Subscription } from "rxjs";
export interface Ingredient {
_id: string;
name: string;
quantity: number;
}
export interface Recipe {
_id: string;
name: string;
ingredients: Ingredient[];
}
#Component({
selector: "app-root",
templateUrl: "app.component.html"
})
export class AppComponent {
private _recipesSub: Subscription;
constructor(
private platform: Platform,
private splashScreen: SplashScreen,
private statusBar: StatusBar,
) {
this.initializeApp();
}
initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleDefault();
this.splashScreen.hide();
});
}
private loadedRecipes: Recipe[] = [/*...*/]
// store the groceryList in a dictionary keyed by name
private _groceryList: Record<string, Ingredient> = {};
// getter returns the groceryList in array format, ignoring 0 quantities
get groceryList(): Ingredient[] {
return Object.values(this._groceryList).filter( ing => ing.quantity > 0 );
}
// get the current quantity for an ingredient by name, or 0 if not listed
currentQuantity( name: string ): number {
const ingredient = this._groceryList[name];
return ingredient ? ingredient.quantity : 0;
}
// update the quantity for an ingredient when checked or unchecked
// will add new ingredients, but never removes old ones
toggleIngredient( ingredient: Ingredient, checked: boolean ): void {
// add to or remove from quantity depending on the value of checked
const quantity = this.currentQuantity(ingredient.name) + (checked ? 1 : -1 ) * ingredient.quantity;
// replace the object in the grocery list dictionary
this._groceryList[ingredient.name] = {
...ingredient,
quantity
}
}
onCheckRecipe(e) { // you'll want to add a type for e here
for (let recipe of this.loadedRecipes) {
// find the matching recipe
if (recipe.name === e.detail.value) {
// loop through the recipe ingredients
for (let eachIngredient of recipe.ingredients) {
this.toggleIngredient(eachIngredient, e.detail.checked)
}
}
}
}
}
I think the issue seems to be with this section of the code
if (
matched.name === eachIngredient.name &&
matched._id === eachIngredient._id
) {
let index = this.groceryList.findIndex(
(x) => x._id === matched._id
);
// Problem e ca eachIngredient.quantity se schimba
this.groceryList.splice(index, 1);
} else {
matched.quantity = matched.quantity - eachIngredient.quantity;
}
The if statement should be checking on the quanity instead of validating the name and id again , something like
if(matched.quantity <= eachIngredient.quantity){
// splice the item and remove it.
}
else
{
// decrease the quantity
}
A small suggestion to finding the matched ingredient. First use findIndex() to retrieve matchedIndex, and use grocerylist[matchedIndex] to retrieve the item, to avoid iterating through the grocerylist again to find the index for splicing.
I am creating a shopping cart in Angular 4 and want to check if a new product prod yet exists in the cartProducts array.
Here's my Component:
Component
import { Component, OnInit } from '#angular/core';
import { Router } from "#angular/router";
import { ProductsService } from '../service/products.service';
#Component({
selector: 'app-store',
templateUrl: './store.component.html',
styleUrls: ['./store.component.css']
})
export class StoreComponent implements OnInit {
itemCount: number;
cartProducts: any = [];
productsList = [];
constructor( private _products: ProductsService ) { }
ngOnInit() {
this.itemCount = this.cartProducts.length;
this._products.product.subscribe(res => this.cartProducts = res);
this._products.updateProducts(this.cartProducts);
this._products.getProducts().subscribe(data => this.productsList = data);
}
addToCart(prod){
this.cartProducts.hasOwnProperty(prod.id) ? console.log("Added yet!") : this.cartProducts.push(prod);
console.log(this.cartProducts)
}
}
My addToCart function which is fired by click works fine, but only from second time.
1 click - we add a product in the empty cartProducts array, the product is added
2 click - although the product is added, it is added again and there are two same products in the array now. I've got the array with the two same products.
3 click - console shows "Added yet!", now it recognizes that the product is in the array yet.
UPD
The product is an object of type:
{
"id" : "1",
"title" : "Title 1",
"color" : "white"
}
How to fix the issue?
hasOwnProperty is for checking if a key exists in an object, you're using it for an array. Use this instead:
addToCart(prod){
this.cartProducts.indexOf(prod) > -1 ? console.log("Added yet!") : this.cartProducts.push(prod);
console.log(this.cartProducts)
}
try this :
let idx = this.cartProducts.findIndex(elem => {
return prod === elem
})
if (idx !== -1) {
console.log("Added yet!")
} else {
this.cartProducts.push(prod);
}
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