new BehaviorSubject returning Null Id - javascript

i'am in the process of creating a shoping cart but i have this weird probleme were the Id of the basket always null
basket.ts
import { v4 as uuidv4 } from 'uuid';
export interface IBasket {
id: string;
items: IBasketItem[];
}
export interface IBasketItem {
id: number;
productName: string;
price: number;
quantity: number;
pictureUrl: string;
brand: string;
type: string;
}
export class Basket implements IBasket {
id: string = uuidv4();
items: IBasketItem[] = [];
}
basket service
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { BehaviorSubject, map } from 'rxjs';
import { environment } from 'src/environments/environment';
import { Basket, IBasket, IBasketItem } from '../shared/Models/basket';
import { IProduct } from '../shared/Models/product';
#Injectable({
providedIn: 'root',
})
export class BasketService {
baseUrl = environment.apiUrl;
private basketSource = new BehaviorSubject<IBasket>(null);
basket$ = this.basketSource.asObservable();
constructor(private http: HttpClient) {}
getBasket(id: string) {
return this.http.get<IBasket>(this.baseUrl + 'basket?id=' + id).pipe(
map((basket: IBasket) => {
this.basketSource.next(basket);
})
);
}
setBasket(basket: IBasket) {
return this.http.post<IBasket>(this.baseUrl + 'basket', basket).subscribe(
(response: IBasket) => this.basketSource.next(response),
(error) => {
console.log(error);
}
);
}
getCurrentBasketValue() {
return this.basketSource.getValue();
}
addItemToBasket(item: IProduct, quantity = 1) {
const itemToAdd: IBasketItem = this.mapProductItemToBasketItem(
item,
quantity
);
const basket = this.getCurrentBasketValue() ?? this.createBasket();
basket.items = this.addOrUpdateItem(basket.items, itemToAdd, quantity);
console.log(basket);
// this.setBasket(basket);
}
private addOrUpdateItem(
items: IBasketItem[],
itemToAdd: IBasketItem,
quantity: number
): IBasketItem[] {
const index = items.findIndex((i) => i.id === itemToAdd.id);
if (index === -1) {
itemToAdd.quantity = quantity;
items.push(itemToAdd);
} else {
items[index].quantity += quantity;
}
return items;
}
private createBasket(): IBasket {
const basket = new Basket();
console.log('ezeze');
localStorage.setItem('basket_id', basket.id);
return basket;
}
private mapProductItemToBasketItem(
item: IProduct,
quantity: number
): IBasketItem {
return {
id: item.id,
productName: item.name,
price: item.price,
pictureUrl: item.pictureUrl,
quantity,
brand: item.productBrand,
type: item.productType,
};
}
}
product-item.ts
import { Component, OnInit, Input } from '#angular/core';
import { BasketService } from 'src/app/basket/basket.service';
import { IProduct } from 'src/app/shared/Models/product';
#Component({
selector: 'app-product-item',
templateUrl: './product-item.component.html',
styleUrls: ['./product-item.component.scss'],
})
export class ProductItemComponent implements OnInit {
#Input() product: IProduct;
constructor(private basketService: BasketService) {}
ngOnInit(): void {}
addItemToBasket() {
this.basketService.addItemToBasket(this.product);
}
}
product-item.html
<div class="card h-100 shadow-sm">
<div class="image position-relative" style="cursor: pointer">
<img
src="{{ product.pictureUrl }}"
alt="{{ product.name }}"
srcset=""
class="img-fluid bg-light"
/>
<div class="d-flex align-items-center justify-content-center hover-overlay">
<button
(click)="addItemToBasket()"
type="button"
class="btn btn-secondary fa fa-shopping-cart me-2"
></button>
<button
routerLink="/shop/{{ product.id }}"
type="button"
class="btn btn-secondary"
>
view
</button>
</div>
</div>
<div class="card-body d-flex flex-column">
<a routerLink="/shop/{{ product.id }}">
<h6 class="text-uppercase">{{ product.name }}</h6>
</a>
<span class="mb-2">{{ product.price | currency }}</span>
</div>
</div>
so basically the problem appears when i click on a product the id of of the basket is always null
the items are adding up but the Id is null
ex:{
"id": "null",
"items": [
{
"id": 8788,
"productName": "T Blues",
"price": 20,
"pictureUrl": "*********/images/products/bo.png",
"quantity": 1,
"brand": "Y",
"type": "Z"
}
]
}
any idea how to solve this ?

The BehaviorSubject returns the last item sent to it.
you are initializing it with a null:
(...)
export class BasketService {
baseUrl = environment.apiUrl;
// \/ here
private basketSource = new BehaviorSubject<IBasket>(null);
so the first time you subscribe to its value, it will yield null
I don't see a call to getBasket on your component, it might not have been called... or the service might not have responded in time to provide the next value into basketSource
also BehaviorSubject's are better used as Observables, to avoid issues with its value.
you should remove the function
getCurrentBasketValue() {
return this.basketSource.getValue();
}
make the prop basketSource public and get its value filtering out nulls when needed as the following:
basketService.basketSource.pipe(filter(x=>x), take(1)).subscribe(source => {
// your code here,
})

Related

Empty response when trying to fetch mock data from Service

I want to fetch and display data from Array of Objects.
I have created the parameterized routes.
1. app-routing.module.ts
const routes: Routes = [
{
path: 'all-trades',
component: AllTradesComponent,
},
{
path: 'crop/:name', component: CropComponent
}]
2. Crop.ts
export class Crop {
name: string;
checked: boolean;
subCategory: Subcategory[];
}
export class Subcategory {
id: number;
name: string;
isActive: boolean;
}
3. CropData.ts
Here is my Array of object, I want to access subCategory and display the name on webpage.
for example: When user click on Rice then its should get the result like 'Basmati', 'Ammamore'
OR
When user click on Wheat then its should get the result like 'Durum', 'Emmer'
OR
When user click on Barley then its should get the result like 'Hulless Barley', 'Barley Flakes'
import { Crop } from './Crop';
export const CROP: Crop[] = [
{
name: 'Rice',
checked: true,
subCategory: [
{
id: 1,
name: 'Basmati',
isActive: true,
},
{
id: 2,
name: 'Ammamore',
isActive: true,
},
],
},
{
name: 'Wheat',
checked: true,
subCategory: [
{
id: 1,
name: 'Durum',
isActive: true,
},
{
id: 2,
name: 'Emmer',
isActive: true,
},
],
}, {
name: 'Barley',
checked: true,
subCategory: [
{
id: 1,
name: 'Hulless Barley',
isActive: true,
},
{
id: 2,
name: 'Barley Flakes',
isActive: true,
},
],
}
]
4.1 crop.service.ts
// First I tried this logic
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
import { skipWhile } from 'rxjs/operators';
import { Crop } from '../shared/Crop';
import { CROP } from '../shared/cropdata';
#Injectable({
providedIn: 'root'
})
export class CropService {
constructor() { }
CropData: Crop
getCrop(name: string): Crop {
return this.CropData.filter((crop) => (crop.name === name))[0];
}
}
4.2 crop.service.ts
// Then I tried this logic
export class CropService {
private selectedCrop= new BehaviorSubject<Crop>(null);
setCrop(crop:Crop){
this.selectedCrop.next(crop);
}
getCrop(){
this.selectedCrop.asObservable().pipe(skipWhile(val=> val === null));
}
}
I failed in both the cases.
5.1 all-trades.components.ts
// First tried using function
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute, Router } from '#angular/router';
import { Crop } from 'src/app/shared/Crop';
import { CropService } from '../crop.service';
#Component({
selector: 'app-all-trades',
templateUrl: './all-trades.component.html',
styleUrls: ['./all-trades.component.css'],
})
export class AllTradesComponent implements OnInit {
constructor(private service: CropService, private router: Router) { }
// Here I tried to make use of function but still its doesnot giving me the desire result
onSelect(selectedCrop:Crop){
this.service.setCrop(selectedCrop);
this.router.navigateByUrl(`crop/${crop.name}`);
}
onChange(event, index, item) {
item.checked = !item.checked;
console.log(index, event, item);
}
ngOnInit(): void { }
}
5.1 all-trades-component.html
<app-header></app-header>
<div
fxLayout="row"
fxLayout.lt-md="column"
fxLayoutAlign="space-between start"
fxLayoutAlign.lt-md="start stretch"
>
<div class="container-outer" fxFlex="20">
<div class="filters">
<section class="example-section">
<span class="example-list-section">
<h1>Select Crop</h1>
</span>
<span class="example-list-section">
<ul>
<li *ngFor="let crop of crops">
<mat-checkbox
[checked]="crop.checked"
(change)="onChange($event, i, crop)"
>
{{ crop.name }}
</mat-checkbox>
</li>
</ul>
</span>
</section>
<div class="content container-outer" fxFlex="80">
<mat-card
class="crop-card"
style="min-width: 17%"
*ngFor="let crop of crops"
[hidden]="!crop.checked"
>
<!-- here i call the function -->
<a (click)="onSelect(crop)" routerLinkActive="router-link-active">
<mat-card-header>
<img
mat-card-avatar
class="example-header-image"
src="/assets/icons/crops/{{ crop.name }}.PNG"
alt="crop-image"
/>
<mat-card-title>{{ crop.name }}</mat-card-title>
<mat-card-subtitle>100 Kgs</mat-card-subtitle>
</mat-card-header>
</a>
<mat-card-content>
<p>PRICE</p>
</mat-card-content>
</mat-card>
</div>
</div>
<app-footer></app-footer>
crop-componet.ts
import { Component, OnInit } from '#angular/core';
import { Subscription } from 'rxjs';
import { Crop } from 'src/app/shared/Crop';
#Component({
selector: 'app-crop',
templateUrl: './crop.component.html',
styleUrls: ['./crop.component.css']
})
export class CropComponent implements OnInit {
service: any;
crop: any;
route: any;
cropservice: any;
sub: Subscription;
constructor() { }
ngOnInit(): void {
// let name = this.route.snapshot.params['name'];
// this.crop = this.cropservice.getCrop(name);
this.sub = this.route.paramMap.subscribe(params => {
let name = params.get("name")
this.crop = this.cropservice.getCrop(name)
})
}
}
7. crop-component.html
<div *ngFor="let category of crop.subCategory">{{category.id}}</div>
This is my eniter code I dont know where I am going wrong please help in fetching data from arrays of object.
[![enter image description here][1]][1]
This is my all-trades.component.html output
When I click Rice I get this as output (Url get change )
[![enter image description here][2]][2]
When I click Wheat I get this
[![enter image description here][3]][3]
And so on....
I just want to display the name of subCategory Array.
Please give me the solution.
[1]: https://i.stack.imgur.com/kxdyj.png
[2]: https://i.stack.imgur.com/OOAtc.png
[3]: https://i.stack.imgur.com/PVcfT.png
On your 4.1 you seem to forget to assign your mock data into your variable
....
import { CROP } from '../shared/cropdata';
#Injectable({
providedIn: 'root'
})
export class CropService {
constructor() { }
CropData: Crop[] = CROP; // Assign the value
getCrop(name: string): Crop {
return this.CropData.filter((crop) => (crop.name === name))[0];
}
}
On your 4.2, you forgot to assign your mock data as well in your BehaviorSubject if you end up using this method. BehaviorSubjects are known to emit initial data
...
import { CROP } from '../shared/cropdata';
export class CropService {
private selectedCrop = new BehaviorSubject<Crop[]>(CROP); // Pass CROP mock data
setCrop(crop: Crop[]) {
this.selectedCrop.next(crop);
}
getCrop() {
this.selectedCrop.asObservable().pipe(skipWhile(val=> val === null));
}
}
Have created a Stackblitz Demo for your reference. You can check the console for the response

angular ctx.cart.getQuantity is not a function

I am implementing a simple Fruit Shop web application using Angular in which I am experiencing a strange exception on which I have spent lots of time, but have NOT found any solution yet.
The overall application's scenario is as follows:
In the root route (page) I am loading a bunch of fruits (each fruit is shown like Bootstrap Card), among which the end-user can choose and add them to his/her Cart (through Add to Card, - and + buttons); and then he/she can go to his/her Cart to checkout and payment process.
My important implemented components are as follows:
products (which is loaded as main component in app component and plays the main page role of the application)
productCard (which plays the Bootstrap Card role and is loaded in products component using an *ngFor
productQuantity (which provides the Add to Card, - and + buttons inside the productCard component. I have implemented this feature as a component, as I want to use this feature in the Cart page, allowing the end-use to add or subtract his/her choices there, too)
I have also implemented some simple models to play the entity roles as follows:
cart.ts (The most important properties and methods of this class are CartItem[] array to persist the items chosen by the end-user inside his/her cart, and also getQuantity(product: Product) method that gets a product and returns a number which refers to the total number of that product inside the end-user's cart at the time)
cart-item.ts (The most important properties of this class are Quantity and Product properties)
product.ts (The most important properties of this class are Name and Fee properties.
Here are the codes be more clear for you:
Products Component Class:
import { Component, OnInit, OnDestroy } from '#angular/core';
import { ProductService } from '../product.service';
import { Product } from '../models/product';
import { Subscription } from 'rxjs';
import { AuthService } from '../auth.service';
import { Cart } from '../models/cart';
import { ShoppingCartService } from '../shopping-cart.service';
#Component({
selector: 'app-products',
templateUrl: './products.component.html',
styleUrls: ['./products.component.css']
})
export class ProductsComponent implements OnInit {
products: Product[];
filteredProducts: Product[];
productsSubscription: Subscription;
cartSubscription: Subscription;
cart: Cart;
constructor(
private productService: ProductService,
public auth: AuthService,
private cartService: ShoppingCartService) {
}
filter(query: string) {
this.filteredProducts = (query) ?
this.products.filter(p => p.Name.toLowerCase().includes(query.toLowerCase())) :
this.products;
}
async ngOnInit() {
if (this.auth.isLoggedIn()) {
(await this.cartService.getCart())
.subscribe(cart => {
this.cart = cart;
this.productService.getAll().then(v =>
v.subscribe(products => {
this.filteredProducts = this.products = products
}));
});
}
}
}
Products Component Markup:
<h4>Welcome to Fruit Shop. <ng-container *ngIf="!auth.isLoggedIn()"> To purchase fruits you must <span>log in first</span>.</ng-container></h4>
<p>
<input type="text" #query (keyup)="filter(query.value)"
placeholder="Type here to search among products..." class="form-control">
</p>
<div class="row">
<ng-container *ngFor="let p of filteredProducts">
<div class="col">
<product-card [product]="p" [cart]="cart"></product-card>
</div>
</ng-container>
</div>
ProductCard Component Class:
import { Component, OnInit, Input } from '#angular/core';
import { Product } from '../models/product';
import { ShoppingCartService } from '../shopping-cart.service';
import { AuthService } from '../auth.service';
import { Cart } from '../models/cart';
#Component({
selector: 'product-card',
templateUrl: './product-card.component.html',
styleUrls: ['./product-card.component.css']
})
export class ProductCardComponent {
#Input('product') product: Product;
#Input('cart') cart: Cart;
constructor(public cartService: ShoppingCartService, public auth: AuthService) {
}
async addToCart() {
this.cartService.addToCart(this.product).subscribe(
res => this.cartService.getCart().then(
res => {
res.subscribe(c => this.cart = c)
}).then(value => this.cartService.totalCartItems += 1)
);
}
}
ProductCard Component Markup:
<div class="card" style="width: 15rem;">
<img class="card-img-top" [src]="product.imageUrl" alt="Card image cap">
<div class="card-body">
<h5 class="card-title">{{ product.Name }}</h5>
<span class="card-text">{{ product.Fee | currency }}</span>
<button
*ngIf="cart && cart.getQuantity(product) > 0"
(click)="cartService.deleteFromCart(product)"
type="button" class="btn btn-danger btn-sm">Delete</button>
</div>
<div class="card-footer">
<button *ngIf="!auth.isLoggedIn() || (cart && cart.getQuantity(product)) === 0; else updateQuantity"
[ngClass]="!auth.isLoggedIn() ? 'button-not-allowd' : 'button-cursor'"
[disabled]="!auth.isLoggedIn()"
(click)="addToCart() " class="btn btn-primary btn-block ">Add to Cart</button>
<ng-template #updateQuantity>
<product-quantity [product]="product" [cart]="cart"></product-quantity>
</ng-template>
</div>
</div>
ProductQuantity Component Class:
import { Component, OnInit, Input } from '#angular/core';
import { Product } from '../models/product';
import { Cart } from '../models/cart';
import { ShoppingCartService } from '../shopping-cart.service';
#Component({
selector: 'product-quantity',
templateUrl: './product-quantity.component.html',
styleUrls: ['./product-quantity.component.css']
})
export class ProductQuantityComponent {
#Input('product') product: Product;
#Input('cart') cart: Cart;
constructor(private cartService: ShoppingCartService) {
}
async addToCart() {
this.cartService.addToCart(this.product).subscribe(
res => this.cartService.getCart().then(
res => {
res.subscribe(c => this.cart = c)
}).then(value => this.cartService.totalCartItems += 1)
);
}
subtractFromCart() {
this.cartService.subtractFromCart(this.product).subscribe(
res => this.cartService.getCart().then(
res => res.subscribe(c => this.cart = c)).then(value => this.cartService.totalCartItems -= 1)
);
}
deleteFromCart() {
this.cartService.deleteFromCart(this.product).subscribe(
res => this.cartService.getCart().then(
res => res.subscribe(c => this.cart = c))
.then(value => this.cartService.totalCartItems -= 1)
);
}
}
ProdcutQuantity Component Markup:
<div class="row no-gutters">
<div class="col-2 ">
<button (click)="cart.getQuantity(product) > 1 ? subtractFromCart() : deleteFromCart()"
class="btn btn-secondary btn-block ">-</button>
</div>
<div class="col text-center quantity-text">
{{ cart.getQuantity(product) }} in cart
</div>
<div class="col-2 ">
<button (click)="addToCart()" class="btn btn-secondary btn-block ">+</button>
</div>
</div>
Cart Service which is a service and is used to communicate to the server side of the application about cart related things:
import { Injectable } from '#angular/core';
import { Product } from './models/product';
import { AuthService } from './auth.service';
import { Router } from '#angular/router';
import { Cart } from './models/cart';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable, of } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class ShoppingCartService {
totalCartItems: number = 0;
constructor(private auth: AuthService, private router: Router, private http: HttpClient) {
if (auth.isLoggedIn()) {
this.getCart().then(resp =>
resp.subscribe(cart => cart.CartItems.forEach(item => this.totalCartItems += item.Quantity)))
}
}
addToCart(product: Product) {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('token')
})
};
return this.http.post(
`http://localhost/FruitShop/api/v1/CartItems/AddItemByItemId/?productId=${product.Id}`,
{},
httpOptions);
}
subtractFromCart(product: Product) {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('token')
})
};
return this.http.post(
`http://localhost/FruitShop/api/v1/CartItems/SubstractItemByItemId/?productId=${product.Id}`,
{},
httpOptions);
}
deleteFromCart(product: Product) {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('token')
})
};
return this.http.post(
`http://localhost/FruitShop/api/v1/CartItems/DeleteItemByItemId/?productId=${product.Id}`,
{},
httpOptions);
}
async getCart(): Promise<Observable<Cart>> {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('token')
})
};
return this.http.get<Cart>('http://localhost/FruitShop/api/v1/Cart/GetCart', httpOptions);
}
}
cart.ts which is my cart model:
import { CartItem } from './cart-item';
import { Product } from './product';
export class Cart {
Id: number;
IsActive: Boolean;
IsDeleted: Boolean;
CreatedAt: Date;
User_Id: number;
Coupons_Id: number;
CartItems: CartItem[];
getQuantity(product: Product): number {
let count: number = 0;
this.CartItems
.filter(ci => ci.Products_Id == product.Id)
.forEach(i => {
count += i.Quantity;
});
return count;
}
}
cart-item.ts which is my CartItem model:
import { Product } from './product';
export class CartItem {
constructor(
public Id: number,
public Quantity: number,
public ModifiedAt: Date,
public IsDeleted: Boolean,
public Products_Id: number,
public Carts_Id: number,
public Products: Product
)
{}
get totalPrice() {
return this.Quantity * this.Products.Fee;
}
}
product.ts which is my Product model:
export class Product {
constructor(public Id: number, public Name: string, public Fee: number, public imageUrl: string){
}
}
Here is the exception that I don't know how to solve it, after searching the web and google a lot:
core.js:6185 ERROR TypeError: ctx.cart.getQuantity is not a function
at ProductCardComponent_Template (product-card.component.html:7)
at executeTemplate (core.js:11949)
at refreshView (core.js:11796)
at refreshComponent (core.js:13229)
at refreshChildComponents (core.js:11527)
at refreshView (core.js:11848)
at refreshDynamicEmbeddedViews (core.js:13154)
at refreshView (core.js:11819)
at refreshComponent (core.js:13229)
at refreshChildComponents (core.js:11527)
I guess the problem is that cart isn't instantly available and comes from a subscription in Products Component Class. It means at the beginning in a moment of time it is undefined and getQuantity isn't a function.
Try the next thing to solve it: add ngIf on the div.row
Products Component Markup:
<h4>Welcome to Fruit Shop. <ng-container *ngIf="!auth.isLoggedIn()"> To purchase fruits you must <span>log in first</span>.</ng-container></h4>
<p>
<input type="text" #query (keyup)="filter(query.value)"
placeholder="Type here to search among products..." class="form-control">
</p>
<div class="row" *ngIf="cart">
<ng-container *ngFor="let p of filteredProducts">
<div class="col">
<product-card [product]="p" [cart]="cart"></product-card>
</div>
</ng-container>
</div>
now the product cart will be rendered only when the cart exists that shouldn't cause the error.

reloading page makes variable undefined in Angular

i have a deckbuilder, a decklist component that shows the list of decks, and when i click on a deck the proper deck-details component is loaded using the deck's id with routing. This is the deck-details part where i use route.
ngOnInit(){
this.decks = this.deckService.getDecks(); // this just returns the array of decks
this.id = this.route.snapshot.params["id"];
this.paramsSubscription = this.route.params.subscribe((params: Params) => {
this.id = params["id"];
console.log(this.id);
this.deck = this.decks.find((deck) => deck.id === this.id);
console.log("deck-detail deck", this.deck);
}
and this is the app-routing.module
const appRoutes: Routes = [
{ path: '', component: CardListComponent },
{ path: 'decks', component: DeckListComponent, children: [
{ path: ':id/:deckName', component: DeckDetailsComponent }
] },
{ path: 'createDeck', component: CreateDeckComponent },
];
#NgModule({
imports: [RouterModule.forRoot(appRoutes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
so the url become something like this when i click on a deck: localhost:4200/decks/id/deckName.
If page is reloaded when the url is like this, deck-detail component is not reloaded with the page, and i get this error
TypeError: Cannot read property 'deckName' of undefined
at DeckDetailsComponent_Template
What i'm doing wrong? there is a component lifecycle problem? how can i avoid decks to be undefined when i reload the page?
this is the whole deck-details component
import { Card } from "./../../card/card.model";
import { Deck } from "./../../deck/deck.model";
import { Component, OnInit, Input } from "#angular/core";
import { DeckService } from "src/app/deck/deck.service";
import { ActivatedRoute, Params, Router } from "#angular/router";
import { Subscription } from "rxjs";
import { DataStorageService } from "src/app/shared/data-storage.service";
import { CardService } from "src/app/card/card.service";
import { FormControl, FormGroup } from "#angular/forms";
#Component({
selector: "app-deck-details",
templateUrl: "./deck-details.component.html",
styleUrls: ["./deck-details.component.scss"],
})
export class DeckDetailsComponent implements OnInit {
//#Input() deck: Deck;
paramsSubscription: Subscription;
id: number;
decks: Deck[];
deck: Deck;
cards: Card[];
selectedClassCards: Card[];
classes = [
"Priest",
"Mage",
"Shaman",
"Rogue",
"Warrior",
"Warlock",
"Druid",
"Paladin",
];
addCardForm: FormGroup;
constructor(
private deckService: DeckService,
private cardService: CardService,
private route: ActivatedRoute,
private dataStorageService: DataStorageService
) {}
ngOnInit() {
this.addCardForm = new FormGroup({
deckCards: new FormControl(),
});
this.cards = this.cardService.getCards();
this.decks = this.deckService.getDecks();
this.id = this.route.snapshot.params["id"];
this.paramsSubscription = this.route.params.subscribe((params: Params) => {
this.id = params["id"];
this.deck = this.decks.find((deck) => deck.id === this.id);
console.log("deck-detail deck", this.deck);
this.selectedClassCards = this.cards.filter(
(card) => card.hero === this.deck.deckClass || card.hero === "Neutral"
);
});
}
onDeleteCard(i) {
this.deckService.deleteCard(this.deck, this.deck.deckCards[i]);
//this.deckService.cardsChanged.next(this.deck.deckCards.slice());
console.log(this.deck);
}
onAddCard() {
this.deckService.addCard(this.deck, this.addCardForm.value.deckCards);
console.log(this.deck);
}
onCardsEdit() {
this.dataStorageService.storeCards(
this.decks.indexOf(this.deck),
this.deck.deckCards
);
}
ngOnDestroy() {
this.paramsSubscription.unsubscribe();
}
}
and this is the DeckService
import { DataStorageService } from './../shared/data-storage.service';
import { Deck } from './deck.model';
import { Card } from '../card/card.model';
import {EventEmitter, Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Subject } from 'rxjs';
#Injectable()
export class DeckService {
deckSelected = new EventEmitter<Deck>();
decksChanged = new Subject<Deck[]>();
cardsChanged = new Subject<Card[]>();
decks: Deck[] = [];
remainingDecks: Deck[];
constructor(/* private http: HttpClient, */ /* private dataStorageService: DataStorageService */) {}
setDecks(decks: Deck[]) {
this.decks = decks;
this.decksChanged.next(this.decks.slice());
}
getDecks() {
if (this.decks) {
return this.decks.slice();
}
}
addCard(deck: Deck, card: Card){
deck.deckCards.push(card);
}
deleteCard(deck: Deck, card: Card){
deck.deckCards.splice( deck.deckCards.indexOf(card), 1);
}
addNewDeck(deck: Deck){
this.decks.push(deck);
this.decksChanged.next(this.decks.slice());
};
deleteDeck(id: number) {
this.remainingDecks = this.decks.filter(
deck => deck.id !== id
)
this.decks = this.remainingDecks;
this.decksChanged.next(this.decks.slice());
}
}
and this is where i store and fetch decks with fire base and httpClient
import { DeckService } from "./../deck/deck.service";
import { CardService } from "./../card/card.service";
import { Deck } from "./../deck/deck.model";
import { Card } from "./../card/card.model";
import { Injectable } from "#angular/core";
import { HttpClient } from "#angular/common/http";
import { map, tap } from "rxjs/operators";
#Injectable({ providedIn: "root" })
export class DataStorageService {
constructor(private http: HttpClient, private deckService: DeckService) {}
storeDecks() {
const decks = this.deckService.getDecks();
this.http
.put("https://ang-cards.firebaseio.com/decks.json", decks)
.subscribe((response) => {
console.log(response);
console.log("stored");
});
}
fetchDecks() {
return this.http
.get<Deck[]>("https://ang-cards.firebaseio.com/decks.json")
.subscribe((decks) => {
decks
? this.deckService.setDecks(decks)
: this.deckService.setDecks([]);
console.log("fetching", decks);
});
}
storeCards(i: number, cards: Card[]){
this.http
.put("https://ang-cards.firebaseio.com/decks/" + i + "/deckCards.json", cards)
.subscribe((response) => {
console.log(response);
console.log("cards stored");
});
}
}
for info this is the deck-detail component html template
<div class="container">
<div class="row">
<div class="deck-details " style="border:solid black">
<!-- <img
[src]="'../../assets/img/' + deck.deckClass + '.png'"
alt="{{ deck.deckName }}"
class="img-responsive"
style="height: 200px;"
/> -->
<h1 contenteditable="true">{{ deck.deckName }} : {{ deck.deckClass }}</h1>
<div *ngFor="let card of deck.deckCards; let i = index" >
<span
[ngClass]="{
common: card.rarity == 'common',
rare: card.rarity == 'rare',
epic: card.rarity == 'epic',
legendary: card.rarity == 'legendary'
}"
>
{{ card.name }}
</span>
<h4 class="inlineh4">: {{ card.manaCost }} mana</h4>
<button class="btn btn-danger" (click) = "onDeleteCard(i)" >delete</button>
<!-- <img [src]="card.imagePath" alt="" style="height: 20px;" /> -->
</div>
<hr>
<label for="deckCards">Add a Card</label>
<form [formGroup]="addCardForm">
<select formControlName="deckCards" id="deckCards" name="deckCards">
<option *ngFor="let card of selectedClassCards" [ngValue]="card">
{{ card.name }}
</option>
</select>
<button (click)="onAddCard()" type="button" class="btn btn-primary">
Add Card
</button>
</form>
<hr>
<button class="btn btn-primary"(click) = "onCardsEdit()" >Save changes</button>
</div>
</div>
</div>

Form control missing randomly for reactive form with filter pipe

Below is a simple reactive form with the filter of an array of checkboxes.
As soon as page render getting error
Cannot find control with path: 'accountsArray -> 555'
However, the filter is working perfectly, but while removing any character from filter throws an error
Cannot find control with path: 'accountsArray -> 123'
Form control not found based on search.
Below is length code, but that will help you to understand clearly.
Component:
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormArray, FormGroup, FormControl } from '#angular/forms';
import { SubAccount } from './account-model';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
searchForm: FormGroup;
searchTerm = '';
formUpdated = false;
accounts = [
new SubAccount('123'),
new SubAccount('555'),
new SubAccount('123555')
];
subAccount = [];
constructor(private fb: FormBuilder) { }
get accountsArray(): FormArray {
return this.searchForm.get('accountsArray') as FormArray;
}
addAccount(theAccount: SubAccount) {
this.accountsArray.push(this.fb.group({
account: theAccount
}));
}
ngOnInit() {
this.formUpdated = false;
this.searchForm = this.fb.group({
accountSearch: '',
accountsArray: this.fb.array([new FormControl('')])
});
this.accounts.forEach((field: any) => {
this.subAccount.push({ key: field.key, value: field.key });
});
const fieldFGs = this.subAccount.map((field) => {
const obj = {};
if (field.value) {
obj[field.value] = true;
} else {
obj[field] = true;
}
return this.fb.group(obj);
});
const fa = this.fb.array(fieldFGs);
this.searchForm.setControl('accountsArray', fa);
this.formUpdated = true;
}
getAccountNumber(account: SubAccount) {
return Object.keys(account)[0];
}
}
View:
<div [formGroup]="searchForm" *ngIf="formUpdated">
<label for="search">Find an account...</label>
<input id="search" formControlName="accountSearch" [(ngModel)]="searchTerm" />
<div formArrayName="accountsArray" *ngIf="formUpdated">
<div *ngFor="let account of accountsArray.controls | filter: 'key' :searchTerm; let ind=index">
<input type="checkbox" id="checkbox_claim_debtor_{{ind}}" formControlName="{{getAccountNumber(account.controls)}}"/>
<span> {{getAccountNumber(account.controls)}} </span>
</div>
</div>
</div>
Pipe:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'filter'
})
export class FilterPipe implements PipeTransform {
transform(items: any[], field: string, value: string): any[] {
if (!value && !items) {
return items;
}
return items.filter((item) => {
const val = Object.keys(item.controls)[0];
if (val && val.toLowerCase().indexOf(value.toLowerCase()) >= 0) {
return true
} else {
return false;
}
});
}
}
Appreciate your help.
Stackblitz link:
https://stackblitz.com/edit/angular-9ouyqr
please check the [formGroupName]="ind" , it is not written while iterating the form array ,formGroupname should be addeed with the form index
<div [formGroup]="searchForm" *ngIf="formUpdated">
<label for="search">Find an account...</label>
<input id="search" formControlName="accountSearch" [(ngModel)]="searchTerm" />
<div formArrayName="accountsArray" *ngIf="formUpdated">
<div [formGroupName]="ind" *ngFor="let account of accountsArray.controls | filter: 'key' :searchTerm; let ind=index"
>
<input type="checkbox" id="checkbox_claim_debtor_{{ind}}" formControlName="{{getAccountNumber(account.controls)}}"/>
<span> {{getAccountNumber(account.controls)}} </span>
</div>
</div>
</div>

Processing a two-dimensional array in Angular 7. ngFor

How to handle a two-dimensional array using ngFor?
I receive here such array
As a result, I need to get the blocks in which the data from the array is displayed in order. That is, in the case of an array that is represented on the screen, there would be 10 blocks.
Example:
<div>
<span>Yandex</span>
<span>Yandex.N.V....</span>
<span>https://en.wikipedia.org/wiki/Yandex</span>
</div>
<div>
<span>Yandex Browser</span>
<span>IPA:...</span>
<span>https://en.wikipedia.org/wiki/Yandex_Browser</span>
</div>
etc.
I do it that way.
<h3>Get Articles</h3>
<div>
<div *ngIf="articles">
<div *ngFor="let article of articles">
<span>{{ article[1] }}</span>
<span>{{ article[2] }}</span>
<span>{{ article[3] }}</span>
</div>
</div>
</div>
I understand that this is wrong, but I can not find my stupid mistake.
The output is either an error or a strange conclusion.
search.component.ts
import { Component, OnInit } from '#angular/core';
import { Article, ArticlesService } from '../../services/articles.service';
#Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.css'],
providers: [ArticlesService]
})
export class SearchComponent implements OnInit {
constructor(private articlesServices: ArticlesService) { }
searchQuery: string;
limit: number;
error: any;
articles: { };
// noinspection JSMethodCanBeStatic
getUrl(searchQuery: string) {
return 'https://en.wikipedia.org/w/api.php?action=opensearch&search='
+ searchQuery + '&limit=10&namespace=0&format=json&origin=*';
}
showArticles() {
this.articlesServices.getArticles(this.getUrl(this.searchQuery))
.subscribe(
(data: Article) => this.articles = Object.values({
title: data[0],
collection: data[1],
description: data[2],
links: data[3]
}),
error => this.error = error
);
console.log(this.articles);
}
ngOnInit() {
}
}
article.component.ts
import { Component, OnInit, Input } from '#angular/core';
import {Article, ArticleInfo, ArticlesService} from '../../services/articles.service';
#Component({
selector: 'app-articles',
templateUrl: './articles.component.html',
styleUrls: ['./articles.component.css'],
})
export class ArticlesComponent implements OnInit {
#Input() articles: Article;
#Input() searchQuery: string;
constructor(private articlesServices: ArticlesService) { }
information: ArticleInfo;
getUrl(searchQuery: string) {
return 'https://ru.wikipedia.org/w/api.php?action=query&list=search&srsearch=' +
searchQuery + '&utf8=&format=json&origin=*';
}
showArticlesInformation() {
this.articlesServices.getArticlesInfo(this.getUrl(this.searchQuery))
.subscribe(
(data: ArticleInfo) => this.information = {
query: data.query.search
}
);
console.log(this.information);
}
ngOnInit() {
}
}
article.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpErrorResponse } from '#angular/common/http';
import { throwError } from 'rxjs';
import { retry, catchError } from 'rxjs/operators';
export interface Article {
title: string;
collection: string[];
description: string[];
links: string[];
}
export interface ArticleInfo {
query: {
search
};
}
#Injectable({
providedIn: 'root'
})
export class ArticlesService {
constructor(private http: HttpClient) { }
getArticles(url) {
return this.http.get(url)
.pipe(
retry(3),
catchError(this.handleError)
);
}
getArticlesInfo(url) {
return this.http.get<ArticleInfo>(url);
}
// noinspection JSMethodCanBeStatic
private handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
console.error('An error occurred:', error.error.message);
} else {
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
}
return throwError(
'Something bad happened; please try again later.');
}
}
Come 2D array
Then it should turn out like this
Try this,
<div>
{{articles[0]}}
</div>
<div *ngFor="let article of articles[1]; let i=index">
<span>
{{article}}
</span>
<span *ngFor="let info1 of articles[2]; let j=index" [hidden]="i!=j">
{{info1}}
</span>
<span *ngFor="let info2 of articles[3]; let k=index" [hidden]="i!=k">
{{info2}}
</span>
</div>
Try storing the result into Observable and into the html file use async pipe.
<div *ngFor="let article of articles | async">
In your search.component.ts
articles : Observable<Article>;
...
this.articles = this.articlesServices.getArticles(this.getUrl(this.searchQuery)).catch(error => this.error = error );

Categories