Angular 5 - How to ignore updating one property of Observable - javascript

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

Related

Javascript $event.stopPropagation() does not work with mat-checkbox inside mat-menu

I am struggeling to keep a material-menu open when selecting from a checkbox within it. Any suggestions would be appreciated. I've attemted solutions including changing to mat-selection-list, put each element of the menu inside a div that has (clicked)="$event.stopPropagation();$event.preventDefault();", and sending the clicked-event from the checkbox to a helper-method that does the same.
example.component.ts
#Component({
selector: 'example',
templateUrl: 'example.component.html',
styleUrls: ['example.compontent.scss'],
})
export class ExampleComponent implements OnInit {
subscriptions: Subscriptions[] = [];
userSettings: ColumnInfo[] = []; // { columnName: string, isSelected: boolean }
constructor() { }
ngOnInit() {
this.setUserConfiguration(); // prefills userSettings
}
async setUserConfiguration(): Promise<void> {
/* There's code here to pre-fill userSettings, as an example try: */
this.userSettings = ['col1', 'col2', 'col3'].foreach(col => {
return { columnName: col, isSelected: false }
});
}
filterColumn(column: string): boolean {
return this.userSettings.findIndex(col => col.columnName === col) !== -1;
}
toggleColumn(column: ColumnInfo, $event: Event): void {
$event.stopPropagation(); $event.preventDefault();
column.isSelected = !column.isSelected;
}
someOtherFunction($event: Event): void {
console.log($event);
}
public resetUserConfiguration(): void {
this.getDefaultColumnConfiguration().then( // you can use setUserConfiguration for test
res => this.userSettings = res as ColumnInfo[]
);
}
}
<ng-container matColumnDef="context" *ngIf="filterColumn('context')">
<mat-header-cell *matHeaderCellDef style="color: rgb(0, 0, 0, 1);">
<div class="ps-context-menu">
<button mat-icon-button [matMenuTriggerFor]="tablemenu"
test-id="open-tablemenu-btn">
<mat-icon fontIcon="more_vert" aria-hidden="true"></mat-icon>
</button>
<mat-menu #tablemenu="matMenu" class="ps-ctx-no-padding filter-menu">
<button mat-menu-item (click)=someOtherFunction($event) test-id="do-something-else">
This button does something else
</button>
<button mat-menu-item test-id="column-properties-btn
[matMenuTriggerFor]="columnproperties">
Column-properties
</button>
<mat-menu #columnproperties class="ps-ctx-no-padding filter-menu"
test-id="column-properties-menu">
<button mat-menu-item test-id="column-filter-btn [matMenuTriggerFor]="filtermenu">
Column filter
</button>
<mat-menu #filtermenu class="ps-ctx-no-padding filter-menu"
test-id="column-filter-menu">
<div (click)="$event.stopPropagation();$event.preventDefault();"
*ngFor="let column of userSettings">
<mat-checkbox [hidden]="column.columnName==='context'"
[checked]="column.isSelected" (click)="toggleColumn(column, $event)">
{{column.columnName}}
</mat-checkbox>
</div>
</mat-menu>
<button mat-menu-item test-id="column-reset-btn"
(click)="resetUserConfiguration()">
Reset column-selection
</button>
</mat-menu>
</mat-menu>
</div>
</mat-header-cell>
</ng-container>
I eventually realized the issue. Each time toggleColumn was called, the DOM which the menu resided upon had to be re-rendered. Therefore the menu "closed".
Setting the menu outside of this DOM solved the issue, there was never a problem using stopPropagation as I first intended.

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

update value from child to parent component in mat card

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 ?

Update parent when variable in child change - Angular

I have a parent which is a form.
This form is composed of two child components:
experiment create (parent)
creation-dataset (child)
creation-metadata (child)
I use a angular component -> mat-accordion to navigate through the two children components.
I use #Input to have the result of what is filled in the children component into the parent.
I want to submit the form only if a file is chosen for both of them. Therefore, I set a variable (in datasetList[i].fileValid) to say whether a file has been selected. Like this I disabled the button if a file is not updated. To disable the button I called the two function:
isDatasetFilesValid()
isMetadataFilesValid()
However, when the variable changed for the second child component it does not updated the disabled button.
This works, only if I press "previous" and "next". The button is not disabled anymore. Like if I needed to reload or refresh the parent. Maybe because of the life cycle ?
Parent Component:
export class ExperimentCreateComponent implements OnInit {
data: any = {};
datasetList: any = [{ fileValid: false }];
metadataList: any = [{ fileValid: false }];
// Functions to navigate through the expansion panels
setStep(index: number) {
this.step = index;
}
nextStep() {
this.step++;
}
prevStep() {
this.step--;
}
isDatasetFilesValid() {
return this.datasetList.findIndex(function(item, i) {
return item.fileValid == false;
});
}
isMetadataFilesValid() {
return this.metadataList.findIndex(function(item, i) {
return item.fileValid == false;
});
}
}
Parent HTML:
<div class="jumbotron">
<div class="container">
<div class="row">
<div class="col-sm-8 offset-sm-2">
<form name="form" (ngSubmit)="f.form.valid" #f="ngForm" novalidate>
<mat-accordion class="headers-align">
<mat-expansion-panel id="datasetUpload" [expanded]="step === 0" (opened)="setStep(1)" hideToggle="true">
<app-creation-dataset [datasetList]="datasetList"></app-creation-dataset>
<mat-action-row>
<button mat-button color="warn" (click)="prevStep()">Previous</button>
<button mat-button color="primary" (click)="nextStep()">Next</button>
</mat-action-row>
</mat-expansion-panel>
<mat-expansion-panel id="metadataUpload" [expanded]="step === 1" (opened)="setStep(2)" hideToggle="true">
<app-creation-metadata [metadataList]="metadataList"></app-creation-metadata>
<mat-action-row>
<button mat-button color="warn" (click)="prevStep()">Previous</button>
<button mat-button color="primary" type="submit" [disabled]="(isMetadataFilesValid() != -1) && (isDatasetFilesValid() != -1)" (click)="createExperiment()">End</button>
</mat-action-row>
</mat-expansion-panel>
</mat-accordion>
</form>
</div>
</div>
</div>
</div>
Child Component:
export class CreationDatasetComponent implements OnInit {
#Input() datasetList: any = [{ fileValid: false }];
fileSelected: File;
constructor(private papa: Papa, private cd: ChangeDetectorRef) {}
ngOnInit() {}
onChange(files: FileList, index: number, dom: any) {
// Option to parse the file with papaparse
let options = {
header: true,
error: (err, file) => {
this.datasetList[index].fileValid = false;
alert(
"Unable to parse CSV file, please verify the file can be accessed and try again. Error reason was: " +
err.code
);
return;
},
complete: (results, file) => {
console.log("Parsed:", results, file);
let filename = file.name;
// Add the dataset to the datasetList
this.datasetList[index].headers = results.meta.fields;
this.datasetList[index].values = results.data;
this.datasetList[index].filename = filename;
this.datasetList[index].is_metadata = false;
this.datasetList[index].fileValid = true;
this.cd.detectChanges();
}
};
this.fileSelected = files[0]; // Get the file
// Call the function to parse the file, option is the callback
this.papa.parse(this.fileSelected, options);
}
// Add a dataset form
addDataset() {
this.datasetList.push({ fileValid: false });
}
// Remove a dataset form
removeDataset(index: number) {
this.datasetList.splice(index, 1);
}
}
Child HTML:
<div *ngFor="let dataset of datasetList; let index = index">
<div id="datasetFiles">
<h6>Select the type of dataset and browse the files:</h6>
<div class="container">
<div class="row justify-content-between">
<div class="col-6 d-flex align-items-center">
<input id="file" #file (change)="onChange(file.files, index, $event.currentTarget)" type="file">
</div>
</div>
</div>
</div>
</div>
<div>
<button mat-icon-button color="primary" (click)="addDataset()">
<mat-icon>add_box</mat-icon>
</button>
</div>
So, to make this answer more clear, read comments on question.
I'm going to past the example for the #Output:
this is the CHILD.COMPONENT.TS
#Component({
selector: 'children',
templateUrl: './children.component.html',
styleUrls: ['./children.component.scss'],
providers: [{...
})
})
export class ChildrenComponent {
#Output() editedEmitter = new EventEmitter<number>();
private variableToPass = 10;
constructor() {}
functionToCall() {
this.editedEmitter.emit(20);
}
this is the PARENT.COMPONENT.HTML
<section>
<children (editedEmitter)="updateValue($event)"></children>
</section>
<!-- in the component you'll do
updateValue(val: number) {
this.variableToUpdate = val;
}
-->
[disabled] requires a condition (true or false), the code you put in: isMetadataFilesValid() != -1 isDatasetFilesValid() != -1 is not a proper condition, they are two. If you want both conditions to be true, use the && notation.
[disabled]="(isMetadataFilesValid() != -1) && (isDatasetFilesValid() != -1)"
Alternatively I suggest moving the condition to the functions themselves so they return a boolean.

Can not read value of undefined, when components share data?

I am not able to populate my form with data that I have received from a method called getbyId() from a service, in my console I see that errors: cannot read truckId of undefined, Every solution i have found is saying my form is rendered faster than the object that i want to get with a getById() method and the solution should be *ngIf ="truck" which should ,make the form wait , but it doesn't solve it just refuses to display the form thats all. IN stackblitz below
https://stackblitz.com/edit/stackoverflow-49847806-kdjhdc?file=app%2Fservices%2Ftruck.service.ts
But In console i can see the URL and when I open it I see that it actually got the details from my back-end
service
getTruckById(id: number): Observable<Truck> {
const url = `${this.baseUrl}/${id}`;
return this.http.get(url, {headers: this.headers})
.pipe(map(this.extractData),
tap(data => console.log(JSON.stringify(data))),
catchError(this.handleError));
}
This is the edit component completely
export class EditTruckComponent implements OnInit {
five : number = 5;
seven: number = 7;
#Input() truck: Truck;
truckIdTOUpdate: number;
processValidation = false;
dates: string;
statusCode: number;
requestProcessing = false;
truckForm = new FormGroup({
truckCode: new FormControl(Validators.maxLength(this.seven), Validators.minLength(this.five)),
date: new FormControl('', Validators.required ),
descriptions: new FormControl(Validators.maxLength(this.seven), Validators.minLength(this.five))
});
constructor( private route: ActivatedRoute, private truckService: TruckService, private router: Router) {
}
ngOnInit() {
this.getTruckDetail();
}
back() {
this.router.navigate(['/trucks'])
}
getTruckDetail() {
const truckId = +this.route.snapshot.paramMap.get('truckId');
this.truckService.getTruckById(truckId)
.subscribe((truck) => this.truck = truck)
console.log("this is the truck" + this.truck);
}
processForm() {
this.processValidation = true;
if (this.truckForm.invalid) {
return; //Validation failed, exit from method.
}
// if we are here then all good
this.preProcessConfigurations()
let truckCode = this.truckForm.get('truckCode').value.trim();
let date = this.truckForm.get('date').value.trim();
let description = this.truckForm.get('descriptions').value.trim();
if (this.truck.truckId == undefined) {
let truck = new Truck(null, truckCode, date , description);
this.truckService.createTruck(truck).subscribe((truck) => {
console.log(truck)
this.router.navigate(['/trucks']);
}, errorCode => this.statusCode = errorCode);
} else {
this.truck = new Truck(this.truck.truckId, truckCode, date, description);
this.truckService.updateTrucks(this.truck).subscribe((truck)=> {
console.log(truck);
this.router.navigate(['/trucks']);
}, errorCode => this.statusCode = errorCode);
}
}
//Perform preliminary processing configurations
preProcessConfigurations() {
this.statusCode = null;
this.requestProcessing = true;
}
}
class="btn btn-light"
title="Save Truck"
data-toggle="tooltip"
data-placement="bottom">
<i class="fa fa-save"></i> <span class="d-none d-sm-inline" *ngIf="truck?.truckId ==undefined">Save</span>
<span class="d-none d-sm-inline" *ngIf="truck?.truckId">Update</span>
</a></li>
</ul>
</nav>
</header>
<section id="department">
<div class="container-fluid">
<div class="row">
<div class="col">
<div class="card">
<div class="card-body">
<form [formGroup]="truckForm" (ngSubmit)="processForm()" id="editFormTruck" >
<!--truck code-->
<div class="form-group">
<label class="form-control-label"
for="truckCode"></label>
<input formControlName="truckCode"
id="truckCode"
class="form-control"
type="text"
name="truckCode"
min="2018-04-11" required
[(ngModel)]="truck?.truckCode"/> Truck Code
<div class="alert alert-danger" role="alert" *ngIf="truckForm.get('truckCode').invalid && processValidation" required="required" [ngClass] ="'error'">
you must have a minimum of 5 chars and maximum of 7
</div>
<div *ngIf="statusCode === 409" [ngClass] = "'success'" class="alert alert-danger" role="alert">
Truck with such Code already exists try another TruckCode.
</div>
</div>
<!--purchasedDate-->
<div class="form-group" >
<label class="form-control-label" *ngIf="truckForm.get('date').invalid && processValidation" [ngClass] ="'error'"
for="purchasedDate">date is required.</label>
<input formControlName="date"
id="purchasedDate"
class="form-control"
type="date"
name="purchasedDate"
[(ngModel)]="truck?.purchasedDate "
/> Purchased Date
</div>
I have posted all because the undefined value starts from my button's save and update when I remove them it goes down to truckCode is undefined
For better understanding this is my truckComponent That navigates to EditTruckComponent
MY truckComponent shows a list of trucks and edit and delete functions
now on my edit function in html i have a i just have a router link
<tr *ngFor="let truck of trucks | async">
<td>{{truck.truckId}}</td>
<a routerLink="/truckProfile/">
<td>{{truck.truckCode}}</td>
</a>
<td>{{truck.purchasedDate | date: 'yyyy-MM-dd'}}</td>
<td>{{truck.descriptions}}</td>
<td class="text-right">
<a class="btn-sm btn-outline-secondary"
ngbTooltip="Edit Truck"
placement="top">
<i class="fa fa-bus"></i> <span
class="d-none d-md-inline" routerLink="/editsTrucks/{{truck.truckId}}" >Edit</span></a>
<span data-toggle="modal" data-target="#deleteDialog">
<a href="#" class="btn-sm btn-outline-secondary"
ngbTooltip="delete truck"
placement="top">
<i class="fa fa-remove"></i>
<span class="d-none d-md-inline" (click)="deleteTruck(truck)">Delete</span></a>
</span>
</td>
So i just do a navigate with router link and on my editTruckCompnent im doing a getById on init
The issue was in my java controller
#GetMapping(value = "/trucks/{truckId}")
#ResponseStatus(HttpStatus.OK)
public final TruckDto getTruckId(#PathVariable(value = "truckId")
final Integer truckId) {
LOGGER.debug("test: truckId({})", truckId);
Truck truck = truckService.getTruckById(truckId);
return mappingService.map(truck, TruckDto.class);
}
when i added this annotation it worked #ResponseBody
So response body tells the controller that the object returned should be serialized into JSON and passed back into the HttpResponse object.
This is why i was getting a HttpResponse Failure
It's a design issue. In my opinion, the right way to do it is :
The EditTruckComponent parent should be smart, do the http call and pass the truck to the EditTruckComponent (you could use async so you don't have to subscribe and unsubscribe manualy)
Here is a clue of how I imagine the parent component :
<app-edit-truck [truck]="truck$ | async"></app-edit-truck>
export class EditTruckParentComponent {
truck$: Observable < Truck > ;
constructor(private truckService: TruckService) {
this.truck$ = this.truckService.getTruckById(truckId);
}
}
Make the EditTruckComponent dumb, no service calls, and only inputs and outputs. Implement OnChanges to handle the truck input changes, and patch the form values.
The edit truck component would look like this :
export class EditTruckParent implements OnChanges {
#Input() truck: Truck;
constructor() {
}
ngOnChanges() {
this.truckForm.patchValue(truck);
}
}

Categories