update value from child to parent component in mat card - javascript

I have a angular application and I have two components. ONe where you can update the item. And a other one where the updated value has to be visible directly when the button update is been triggered.
So I made a service like this:
export class ItemListService {
_updateItemChanged = new Subject<string>();
constructor() {}
get refreshNeeded() {
return this._updateItemChanged.next();
}
}
and the value where the value is comming from:
[appSubmitIfValid]="editItemForm" (valid)="save()" i18n>Update</button>
<button *ngIf="!isNew" mat-raised-button color="warn" (click)="openRemoveDialog()" i18n>Remove</button>
save(): void {
const form = this.editItemForm;
const dossierItemDto: DossierItemPostDto = {
title: form.controls.title.value,
itemType: form.controls.itemType.value,
date: (form.controls.date.value as moment.Moment).format('Y-MM-DD'),
body: form.controls.body.value
};
form.disable();
if (!this.isNew) {
this.dossierItemService.updateDossierItemById(this.dossier.id, this.item.id, dossierItemDto)
.subscribe(item => {
this.item = item;
this.sortDossierItems();
form.enable();
form.markAsPristine();
this.itemListService._updateItemChanged.next(this.item.title);
this.errorProcessor.openSuccessSnackBar($localize`Item is saved`);
}, error => this.handleError(error));
} else {
this.dossierItemService.newDossierItem(this.dossier.id, dossierItemDto)
.subscribe(item => {
this.item = item;
this.dossierItems.unshift(item);
this.sortDossierItems();
this.isNew = false;
form.enable();
form.markAsPristine();
this.errorProcessor.openSuccessSnackBar($localize`Item is saved`);
}, error => this.handleError(error));
}
}
and the component that has to been updated(parent):
dossierItems: DossierItemDto[] = [];
ngOnInit(): void {
this.itemlistService._updateItemChanged.subscribe((data) => {
data = this.dossierItems.map(a => a.title) ;
});
But I get now this error:
Type 'string[]' is not assignable to type 'string'.ts(2322)
So what I have to change?
Thank you
oke, and this value has to be updated: the item.title.
<ng-template #itemList let-itemType="itemType">
<mat-card *ngFor="let item of dossierItemsBy(itemType); let i = index" class="dossier-item-view">
<mat-card-header>
<mat-card-title>
<span [innerHTML]="item.title | highlight: searchQuery"></span>
<span class="spacer"></span>
<span><app-attachment-links [attachments]="item.attachments" [dossierId]="dossier.id" ></app-attachment-links></span>
</mat-card-title>
<div class="mat-card-header-text">
<span *ngIf="!createdAtEqualsDate(item)"
>{{ item.date | date: 'shortDate' }}<ng-template i18n>created</ng-template></span
>
<span>{{ item.createdAt | date: 'short' }}</span>
<span *ngIf="item.createdAt !== item.lastModifiedAt"
><ng-template i18n>modified</ng-template> {{ item.lastModifiedAt | date: 'short' }}</span
>
</div>
<span>
<a mat-icon-button [routerLink]="['../', dossier.id, 'item', item.id]" routerLinkActive="active-link"
[routerLinkActiveOptions]="{exact:true}"
i18n-title title="Edit">
<mat-icon>edit</mat-icon>
</a>
</span>
</mat-card-header>
</mat-card>
</ng-template>
dossierItemsBy(itemType: DossierItemTypeDto) {
return this.dossierItems.filter(
i => i.itemType === itemType && (!this.hasSearchQuery || this.itemSearchMatches[i.id].hasMatch)
);
}

You wrote that in subject pass string
_updateItemChanged = new Subject<string>();
And in subscribe you try to assign an array of strings to it
this.itemlistService._updateItemChanged.subscribe((data) => {
data = this.dossierItems.map(a => a.title) ;
});
If you want to update your parent, why not use Output ?

Related

How to display two type data in vuejs

heLLO i have two object in array in my generic component
listData() {
let dataCode = Object.entries(this.data);
let dataList = {};
for (let [id, item] of dataCode) {
if (data.skuId === null) {
dataList[id] = {
name: item.name,
priceProduct: item.price
productId: favoris.id
};
} else {
dataList[id] = {
size: item.attributes.size,
priceSku: item.sellPriceCustomer,
skuId: item.id
};
}
}
return dataList;
}
//result
listData = [{skuid:234, priceSku:56$, size: M}, {productId: 123, name: bed, priceProduct: 34$, skuId: null}]
How to display this array object in one generic component child (i send the props) i add the v-if in all a template i don't no is is the best way to that like this
<span v-if="name !== ''" class="text-bold">{{ name }}</span>
<span v-if="size !== ''"> size: {{ size }}</span>
<span v-if="skuId !== null" class="text-bold">{{ priceSku }} €</span>
<span v-if="productId !== null" class="text-bold"
>{{ priceProduct }} €</span
>
Please help

Angular 11 get any param from object is undefined

Whatever I do, no matter how I change the code, I cannot get hostelid.
The problem with method deleteHostel.
Model:
export class Hostel {
hostelid: string;
name: string;
location: string;
phone?: string;
employees?: Array<Employee>;
}
Service:
export class HostelService {
getHostels(): Observable<Hostel[]>
{
return this.http.get<Hostel[]>(`${this.apiUrl}api/Hostels/`);
}
deleteHostel(id: string): Observable<any>
{
return this.http.delete(`${this.apiUrl}api/Hostels/${id}`);
}
}
Component:
ngOnInit(): void {
this.id = this.route.snapshot.paramMap['id'];
this.isAddMode = !this.id;
this.loadHostels();
if (!this.isAddMode) {
this.hostelService.getHostelById(this.id)
.pipe(first())
.subscribe( hostel => {this.hostel = hostel});
}
else {
this.hostel = new Hostel();
let newid = Guid.New() as string;
this.hostel.hostelid = newid;
}
}
loadHostels(): void
{
this.hostelService.getHostels().subscribe((data:Hostel[])=> {this.hostels = data});
}
deleteHostel(id: number):void{
let hostel = this.hostels[id];
let hostelid = hostel.hostelid;
let res = this.hostelService.deleteHostel(hostelid).subscribe( response => {
this.hostels.forEach((item, index) => {
if(item.hostelid === hostelid){
this.hostels.splice(index,1);
}
}); },
err => {
this.notifSrv.openSnackBar('error');
});
}
html:
<mat-list *ngIf="hostels">
<ng-container *ngFor="let item of hostels; last as last; let index = index;">
<h3 matSubheader>{{item.name}}
<button mat-icon-button color="warn" aria-label="delete" (click)="deleteHostel(index)">
<mat-icon>delete</mat-icon>
</button>
<button mat-icon-button color="accent" aria-label="edit" (click)="updateHostel(index)">
<mat-icon>edit</mat-icon>
</button>
</h3>
<mat-list-item>
<div> <span><span *ngIf="item.phone">{{item.phone}}</span>
{{item.location}}</span>
</div>
</mat-list-item>
<mat-divider *ngIf="!last"></mat-divider>
</ng-container>
</mat-list>
Index return normal data - 0,1,2.
But hostelid always is undefined.
If I change deleteHostel parameter to object param (string), in html
(click)="deleteHostel(item.hostelid)
and in component
deleteHostel(id: string)
id is undefined.
If I change deleteHostel parameter to object , in html
(click)="deleteHostel(item)
and in component
deleteHostel(item: Hostel)
item is undefined.
I googled a lot and tried a lot of options but nothing worked.
Update: console log
If I change deleteHostel parameter to object , in html
(click)="deleteHostel(item)
and in component
deleteHostel(hostel: Hostel)
{
console.log(hostel); <--{hostelId: "4f8be387-bbb0-4bfa-8217-09edca45f7ee", name: "test2", location: "test2", phone: null, employees: null}
console.log(hostel.hostelid); <-- undefined
}
I have vague doubts about hostelId in object, and hostelid in class, how can this be? because the type is the same.
JavaScript property names are case sensitive. Seems like the ID in your model Hostel was defined with the incorrect casing. It should be:
export interface Hostel {
hostelId: string;
// your other properties
}
Don't forget to rename the property in your delete function as well:
deleteHostel(hostel: Hostel) {
console.log(hostel.hostelId);
}

Angular Expression has changed after it was checked

I'm getting the well known error in my Angular app, but not sure why it happens and how to fix it. I was trying a couple of ways including adding setTimeout, delay(0), switching to different hook but any of them seems to work in my case.
Problem description:
I have a list of products and on click single product can be added to the cart with selected products
//product.list.component.ts
addToProductCart(product: IProduct) {
this.productsService.addProductToSelectedProducts(product);
}
The service looks like below:
//product.service.ts
#Injectable({
providedIn: 'root'
})
export class ProductsService {
selectedProducts: BehaviorSubject<IProduct[]> = new BehaviorSubject<IProduct[]>([]);
product = this.selectedProducts.asObservable();
constructor(private http: HttpClient) { }
getProductsList(): Observable<IProduct[]> {
return this.http.get<IProduct[]>(`${environments.environment.baseUrl}/products`);
}
patchProductLikes(id: number, payload: Partial<IProduct>): Observable<number> {
return this.http.patch<number>(`${environments.environment.baseUrl}/products/${id}`, payload);
}
addProductToSelectedProducts(product: IProduct) {
this.selectedProducts.next([...this.selectedProducts.value, product]);
}
clearSelectedProducts(): void {
this.selectedProducts.next([]);
}
removeSelectedProduct(products: IProduct[]): void {
this.selectedProducts.next(products);
}
}
When product is selected on my header the product count is increased and displayed on cart icon:
//header.component.html
<span (click)="openDialog()" #openCartButton>
<mat-icon matBadge="{{selectedProductsCount}}"matBadgePosition="above after">
shopping_cart
</mat-icon>
</span>
//header.component.ts
openDialog() {
this.dialog.open(CartDetailsComponent, {
width: '450px',
height: '650px',
data: {
positionRelativeToElement: this.openCartButton
}
});
}
getSelectedProductsCount(): void {
this.productsService.product.subscribe((products) => {
this.selectedProductsCount = products.length;
});
}
If header cart icon is clicked the dialog with selected product is opened, and if there are no selected products then empty cart placeholder should be displayed:
//cart-details.component.html
<div *ngIf="products.length > 0 else emptyCart">
<h5 mat-dialog-title>Total order</h5>
<div mat-dialog-content class="product" [#loadProducts]="'in'">
<ul>
<li *ngFor="let groupedProducts of selectedProducts | keyvalue" class="product__product-item">
<div *ngFor="let prod of groupedProducts.value | productPipe; let i = index" class="product-details-container">
<div>
<img [src]="prod.image" alt="Product photo" class="product-details-container__product-img">
</div>
<div class="product-info">
<p>{{prod.name}}
<span class="product-info__price">${{prod.price}}</span>
</p>
<p>
{{prod.productMaterial}}
</p>
<p>
{{prod.color}}
</p>
<p #deleteProduct>Amount: {{groupedProducts.value.length}} </p>
<p>Total: ${{prod.price * groupedProducts.value.length}}</p>
<div class="product-actions-container">
<a (click)="deleteProduct(prod)" class="delete-product">Delete</a>
<a (click)="viewProductDetails(prod)" class="view-details">View details</a>
</div>
</div>
</div>
</li>
<span>SUM: ${{totalSum}}</span>
</ul>
</div>
</div>
<ng-template #emptyCart>
<div class="empty-bag-container">
<mat-icon svgIcon="empty-bag" class="empty-bag-container__empty-bag-icon"></mat-icon>
<h4 class="empty-bag-container__empty-bag-heading">
YOUR BAG IS EMPTY
</h4>
<span class="empty-bag-container__empty-bag-details"> Looks like you haven’t made your choice yet.
Check out 100+ styles for everyone!</span>
</div>
</ng-template>
//cart-details.component.ts
export class CartDetailsComponent implements OnInit, OnDestroy {
private positionRelativeToElement: ElementRef;
isOpen = false;
totalSum = 0;
totalPrices: number[] = [];
private destroySubject: Subject<boolean> = new Subject<boolean>();
selectedProductsCount: number;
selectedProducts: Record<string, IProduct[]>;
productSumPrice: number;
products: IProduct[] = [];
constructor(public dialogRef: MatDialogRef<CartDetailsComponent>,
private productsService: ProductsService,
#Inject(MAT_DIALOG_DATA) public data: { positionRelativeToElement: ElementRef }) {
this.positionRelativeToElement = data.positionRelativeToElement;
}
ngOnInit() {
const matDialogConfig = new MatDialogConfig();
const rect: DOMRect = this.positionRelativeToElement.nativeElement.getBoundingClientRect();
matDialogConfig.position = { right: `10px`, top: `${rect.bottom + 2}px` };
this.dialogRef.updatePosition(matDialogConfig.position);
this.getSelectedProducts();
this.calculatePrices();
}
ngOnDestroy() {
this.destroySubject.next(true);
}
close() {
this.dialogRef.close();
}
deleteProduct(product: IProduct) {
const prodId: number = product.id;
this.selectedProducts[prodId] = this.selectedProducts[prodId].slice(0, this.selectedProducts[prodId].length - 1);
const index: number = this.products.map(x => {
return x.id;
}).indexOf(product.id);
this.products.splice(index, 1);
this.productsService.removeSelectedProduct(this.products);
this.calculatePrices();
}
viewProductDetails(product: IProduct): void {
console.log(product);
}
animateCurrentItem(product: IProduct) {
console.log(product, 'animation');
}
calculatePrices() {
if (this.products.length > 0) {
this.totalPrices = [];
Object.values((this.selectedProducts))
.map((prod) => {
if (prod.length > 0) {
(prod as IProduct[]).map((p) => {
this.totalPrices.push(Number(p.price));
});
}
});
if (this.totalPrices.length > 0) {
this.totalSum = this.totalPrices.reduce((prev, cur) => {
return prev + cur;
});
} else {
this.totalSum = 0;
this.productsService.clearSelectedProducts();
}
}
}
getSelectedProducts() {
this.productsService.product
.pipe(
delay(0),
startWith([]),
takeUntil(this.destroySubject),
)
.subscribe((products) => {
if (products.length > 0) {
this.products = products;
this.productSumPrice = _.sumBy(products, (prod) => parseFloat(prod.price));
this.selectedProductsCount = _.sum(Object.values(_.countBy(products, product => product.id)));
this.selectedProducts = _.groupBy(products, 'id');
}
});
}
}
And here the error occurs. If cart is empty (meaning products.length === 0) the <ng-template #emptyCart> is displayed but with the error:
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'loading-background: false'. Current value: 'loading-background: true'.
The error is about loading-background in ngx-ui-loader lib witch I use in app.module:
//app.module
(...)
import { NgxUiLoaderModule, NgxUiLoaderHttpModule, NgxUiLoaderConfig, SPINNER, POSITION, PB_DIRECTION } from 'ngx-ui-loader';
imports: [
...
NgxUiLoaderModule.forRoot(ngxUiLoaderConfig),
NgxUiLoaderHttpModule,
]
Any idea what cause the issue and how to fix it and avoid in the future?
I was traying to reproduce it on stackblitz but with no luck :). Although maybe it will help understand my issue ;P
https://stackblitz.com/edit/angular-h3xyop?file=src%2Fapp%2Fproduct-list%2Fproduct-list.component.ts
This is because of your view changed after rendering. You need to use changeDetectorRef to detechChanges. add in constructor
construct(private ref: changeDetectorRef)
{}
and after change you add
this.ref.detectChanges();
https://angular.io/api/core/ChangeDetectorRef

Displaying div based on dynamic boolean angular

Getting input from a reactive form inside a ngFor ,I have to display "correct" or "incorrect" based on the comparison of the user answer with the exercise.question.answer value.
My idea is to create a boolean reactively but I'm struggling with the execution. I'm not being able to compare index[x] of array a with index [x] of array b every time these arrays are created.
This is the template:
<form
fxLayout="column"
fxLayoutGap="2px"
[formGroup]="exerciseForm"
(ngSubmit)="onSubmit(exerciseForm.value)"
>
<ul *ngFor="let exercise of exercises">
<li>{{ exercise.instruction }}</li>
<ul *ngFor="let question of exercise.questions; let i = index">
<li>
{{ question.prefix }}
<mat-form-field>
<input
name="answer"
type="text"
id="answer"
matInput
[formControlName]="question.id"
/>
</mat-form-field>
{{ question.sufix }} -
{{ question.translation }}
<div *ngIf="isAnswered">
<div *ngIf="isCorrect"><p>Correct</p></div>
<div *ngIf="!isCorrect"><p>Incorrect</p></div>
</div>
</li>
</ul>
</ul>
<button type="submit" mat-raised-button color="primary">Submit</button>
</form>
this is the ts (it contains some of the methods I've been attempting)
export class ExerciseTestsComponent implements OnInit {
exerciseForm: FormGroup;
isAnswered: boolean;
isCorrect: boolean;
exercises: Exercise[] = [
new Exercise("Answer this question", [
new Question(1, "Eu", "maluco", "I am crazy", "sou"),
new Question(2, "Eu", "doidinho", "I am cuckoo", "estou")
])
];
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
this.createGroup();
}
getAnswersArray() {}
createGroup() {
this.exerciseForm = this.fb.group({});
this.exercises[0].questions.forEach(control =>
this.exerciseForm.addControl(control.id.toString(), this.fb.control(""))
);
}
onSubmit(answer: TestAnswer) {
this.isAnswered=true;
//** 1
let answers = [];
let answersInput = [];
this.exercises[0].questions.forEach(pergunta => {
//** 2
answers.push(pergunta.answer);
console.log(answers);
});
//** 3
var bosta = Object;
bosta = this.exerciseForm.value;
console.log(bosta[1]);
if ((answers[1] = bosta[1])) {
console.log("pqp");
}
let incoming = this.exerciseForm.value;
for (var o in incoming) {
answersInput.push(incoming[o]);
console.log(answersInput);
}
answersInput.forEach(a1 =>
answers.forEach(a2 => {
if (a1 === a2) {
console.log("yay");
} else {
console.log("boo");
}
})
);
}
}
//** for every object created, I have to check if answer = */
stackblitz:
https://stackblitz.com/edit/angular-dzzzql
Then when you submit, you can compare both answers
onSubmit(answer: Answer) {
let answers = [];
console.log(this.exercises)
let answersInput = []
this.exercises[0].questions.forEach((pergunta, index) => {
answers.push(pergunta.answer)
console.log(answers)
return answers
})
let i = 0;
for (const field in this.exerciseForm.controls) { // 'field' is a string
console.log(this.exerciseForm.controls[field].value == answers[i]);
i++;
}
}
Working demo in Stackblitz

Angular 5 - How to ignore updating one property of Observable

I'm developing a page to show some videos and user can like them. Videos and Likes are saved in a database and I used two angular services for set and get data. My problem is about setting and revoking likes on videos. after every request for set or revoke like on videos, the page data has been refreshed and loading videos upset the user. services are working fine and data sets to database correctly.
I just need to update like button color and likes count in the page. What is the solution to ignore updating video links that are fixed and never changed?
In below I put part of my codes:
index.component.html :
<mat-card class="example-card" *ngFor="let video of videos">
<mat-card-header>
<mat-card-title>{{ video.title }}</mat-card-title>
<mat-card-subtitle>
{{ video.description }}
</mat-card-subtitle>
</mat-card-header>
<div class="video-container">
<iframe class="video-frame" [src]="video.mediaSource | safe" allowFullScreen="true" webkitallowfullscreen="true" mozallowfullscreen="true" ></iframe>
</div>
<mat-card-actions class="text-center">
<button *ngIf="video.isLiked == true" mat-icon-button color="warn">
<mat-icon (click)="revokeLike(1, video.id)">favorite</mat-icon>
</button>
<button *ngIf="video.isLiked == false" mat-icon-button class="grey_like">
<mat-icon (click)="setLike(1, video.id)">favorite</mat-icon>
</button>
<span>{{ competition.likesCount }} Likes</span>
</mat-card-actions>
</mat-card>
index.component.ts :
export class ShowComponent implements OnInit {
competitions:any;
constructor(private service:VideoService, private http: HttpClient, private likeservice:LikeService) { }
ngOnInit() {
this.getVideos();
}
getVideos() {
this.service.getVideos(1).subscribe(res => {
this.videos = res;
});
}
setLike(iid, video_id) {
this.likeservice.setLike(iid, video_id).subscribe(
() => this.getCompetitions()
);
}
revokeLike(iid, video_id) {
this.likeservice.revokeLike(iid, video_id).subscribe(
() => this.getCompetitions()
);
}
}
videos.service.ts :
getVideos(id): Observable<any> {
const uri = 'http://localhost:4000/videos/iid/' + id;
return this
.http
.get(uri)
.map(res => {
return res;
});
}
like.service.ts :
setLike(iid, competition_id) {
const uri = 'http://localhost:4000/likes/set';
const obj = {
iid: iid,
competition_id: competition_id
};
return this
.http
.post(uri, obj)
.map(res =>
console.log('Done'));
}
revokeLike(iid, competition_id) {
const uri = 'http://localhost:4000/likes/revoke';
const obj = {
iid: iid,
competition_id: competition_id
};
return this
.http
.post(uri, obj)
.map(res =>
console.log('Done'));
}
Based on the information, I would do it like this. The idea is there's no need to requery the backend for the list of videos, since you know what/how changed and you only apply the change if you have an ok response.
Template:
<mat-card class="example-card" *ngFor="let video of videos">
...
<button *ngIf="video.isLiked == true" mat-icon-button color="warn">
<mat-icon (click)="revokeLike(video)">favorite</mat-icon>
</button>
<button *ngIf="video.isLiked == false" mat-icon-button class="grey_like">
<mat-icon (click)="setLike(video)">favorite</mat-icon>
</button>
...
</mat-card>
Controller:
export class ShowComponent implements OnInit {
competitions:any;
constructor(private service:VideoService, private http: HttpClient, private likeservice:LikeService) { }
ngOnInit() {
this.getVideos();
}
getVideos() {
this.service.getVideos(1).subscribe(res => {
this.videos = res;
});
}
setLike(video) {
this.likeservice.setLike(1, video.id).subscribe(
() => video.isLiked = true
);
}
revokeLike(video) {
this.likeservice.revokeLike(1, video.id).subscribe(
() => video.isLiked = false
);
}
}
You can do that in setLike and revokeLike functions, send a video object to function and make isLiked to change accordingly
This makes the *ngIf condition working for you.
HTML:
<mat-card-actions class="text-center">
<button *ngIf="video.isLiked == true" mat-icon-button color="warn">
<mat-icon (click)="revokeLike(1, video)">favorite</mat-icon>
</button>
<button *ngIf="video.isLiked == false" mat-icon-button class="grey_like">
<mat-icon (click)="setLike(1, video)">favorite</mat-icon>
</button>
<span>{{ competition.likesCount }} Likes</span>
</mat-card-actions>
Component:
setLike(iid, video) {
this.likeservice.setLike(iid, video.id).subscribe(
data => {
video.isLiked = true;
}
);
}
revokeLike(iid, video) {
this.likeservice.revokeLike(iid, video.id).subscribe(
data => {
video.isLiked = false;
}
);
}

Categories