Delete option for user if he is the owner of the perticular property - html

I want to show delete option for a user if he has created perticular exhibit. I am getting a current user id from getCurrentUser service and i am getting an array of exhibits in which thre is a field "userId".
I am trying to match id of the current user and userId from Exhibits array in such a way that if there is a match, then only user will get delete option for perticular exhibit but I am unable to do it in proper way.
Below is my code:
-------------------------------------------------------------------------------
ngOnInit() {
this.getCurrentUser();
this.getIsSupervisor();
this.spinnerService.show();
let allRoutesOption = Route.emptyRoute();
allRoutesOption.title = 'ALL';
this.routes = [allRoutesOption];
this.getAllExhibits();
this.routeService.getAllRoutes(1, 100)
.then(
data => this.routes = this.routes.concat(data.items)
).catch(
error => console.error(error)
);
this.getPage(1);
}
ngOnDestroy() {
this.spinnerService.hide();
}
getIsSupervisor() {
this.supervisorGuard.isSupervisor().then(
(response: boolean) => {
this.isSupervisor = response;
});
}
getCurrentUser() {
this.userService.getCurrent()
.then(
(response) => {
this.currentUserId = response.id;
this.exhibitService.getAllExhibits(1, this.maxNumberOfMarkers)
.then(
(data) => {
this.allExhibits = data.items;
for (let exhibit of this.allExhibits) {
this.exhibitsUserIds.push(exhibit.userId);
if (this.exhibitsUserIds !== this.currentUserId) {
this.canDelete = false;
} else {
this.canDelete = true;
}
}
}
);
}
);
}
-------------------------------------------------------------------------------
My Html:
----------------------------------------
<md-nav-list>
<md-list-item [routerLink]="['/mobile-content/exhibits/view', exhibit.id]" ng-blur="true" *ngFor="let exhibit of exhibits | paginate: { id: 'server',
itemsPerPage: exhibitsPerPage,
currentPage: currentPage,
totalItems: totalItems }">
<img md-list-avatar *ngIf="previewsLoaded && previews.has(exhibit.id); else exhibitIcon" [src]="previews.get(exhibit.id)"
alt="{{ 'image preview' | translate }}" [ngStyle]="{'width.px': 48, 'height.px': 48}">
<ng-template #exhibitIcon>
<md-icon md-list-icon class="type-icon" [ngStyle]="{'font-size.px': 40, 'height.px': 40, 'width.px': 40}">place</md-icon>
</ng-template>
<h2 md-line>{{ exhibit.name }} ({{ exhibit.status | translate }})
<hip-star-rating class="fix-position" *ngIf="exhibit.ratings" [rating]='exhibit.ratings' [exhibitId]='exhibit.id'></hip-star-rating>
</h2>
<p md-line>{{ exhibit.description }}</p>
<p md-line>
<span class="latitude">{{ exhibit.latitude }}</span>,
<span class="longitude">{{ exhibit.longitude }}</span>
</p>
<p *ngIf="exhibit.tags.length > 0" md-line>
<span *ngFor="let tag of exhibit.tags" class="tag-name">{{ tag }}</span>
</p>
<button md-icon-button click-stop-propagation color="primary" [routerLink]="['/mobile-content/exhibits/edit', exhibit.id]"
title="{{ 'edit' | translate }}">
<md-icon>{{ !inDeletedPage ? 'edit' : 'remove_red_eye'}}</md-icon>
</button>
<div *ngIf="canDelete">
<button md-icon-button click-stop-propagation color="warn" (click)="deleteExhibit(exhibit)" *ngIf="!exhibit.used && !inDeletedPage"
title="{{ 'delete' | translate }}">
<md-icon>delete_forever</md-icon>
</button>
</div>
</md-list-item>
----------------------------------------
Can someone help me to figure it out?

I am new to Angular myself but maybe I can still help. I am noticing a couple things that may be causing issues.
Firstly
When you are iterating across this.allExhibits, I noticed that the this.canDelete is just one value that you keep reassigning after each iteration. By the end it only represents the 'deleteability' of only the last exhibit.
Perhaps you can create some sort of object or array to map against the for..of iteration of this.allExhibits. That way you can store each resolved value of this.canDelete without overwriting it on each iteration.
example.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-example',
templateUrl: './example.component.html'
})
export class ExampleComponent {
currentUser:object = {
name: 'User A',
id: 'A'
};
exhibits:object[] = [
{
title: 'Exhibit A',
id: 'A'
},
{
title: 'Exhibit B',
id: 'B'
},
{
title: 'Exhibit C',
id: 'C'
}
];
constructor() { }
deleteExhibit(index) {
this.exhibits = this.exhibits.filter((_, i) => i != index);
}
}
example.component.html
<div *ngFor="let exhibit of exhibits; let i=index">
<h3>{{exhibit.title}}</h3>
<button *ngIf="exhibit.id == currUser.id" (click)="deleteExhibit(i)">DELETE</button>
<hr/>
</div>
Secondly
I presume the getCurrentUser() is something that happens as the component instantiates. In that case, the *ngIf must await the resolved value of this.canDelete before it can either display or hide the delete button.
Since getCurrentUser() appears to resolve sometime after the component's initial rendering of the view, it maybe be possible that setting the value of this.canDelete is not triggering Angular's change detection.
Perhaps try ChangeDetectorRef.detectChanges() after you resolve the final value of this.canDelete. ChangeDetectorRef is importable from #angular/core and instantiable in the component's constructor: constructor(private changeDetectorRef:ChangeDetectorRef) {}.
Hopefully this helps!

Related

Delete method in Angular is not deleting

I am trying to do a really basic CRUD in my first Angular application and I am having issues with the Delete method. There are no errors, the id parameter is passed correctly throughout the application (checked in the console), but the item is not being deleted. Basically nothing happens.
The "database" is a file named notes.json inside the assets folder of my application. It contains multiple notes like this:
[
{
"id": "id1",
"title": "First note",
"description": "This is the description for the first note",
"categoryId": "1"
}
]
These notes are put in the UI in the note.component.html in mat-cards.
<div *ngFor="let note of notes">
<mat-card class="note">
<mat-card-header>
<mat-card-title>
{{ note.title }}
</mat-card-title>
</mat-card-header>
<mat-card-content>
<p>
{{ note.description }}
</p>
</mat-card-content>
<button><mat-icon (click)="deleteNote(note.id)">delete</mat-icon></button>
</mat-card>
</div>
Then, in note.component.ts, the deleteNote method is called. As I said above, the note.id parameter is passed correctly.
#Component({
selector: 'app-note',
templateUrl: './note.component.html',
styleUrls: ['./note.component.scss'],
})
export class NoteComponent implements OnInit, OnChanges {
#Input() selectedCategoryId: string;
notes: Note[];
constructor(private noteService: NoteService) { }
ngOnInit() {
this.noteService.getNotes().subscribe((result) => this.notes = result);
}
ngOnChanges() {
if (this.selectedCategoryId) {
this.noteService.getFilteredNotes(this.selectedCategoryId).subscribe((result) => this.notes = result);
}
}
deleteNote(id:string){
console.log(id);
this.noteService.deleteNote(id).subscribe(data => {console.log(data);});
}
}
export interface Note {
id: string;
title: string;
description: string;
categoryId: string;
}
The service is, therefore called and performs the following:
#Injectable()
export class NoteService {
readonly baseUrl = "https://localhost:4200";
readonly httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
})
};
deleteNote(id:string)
{
return this.httpClient.delete(this.baseUrl + "/notes/" + id, this.httpOptions).subscribe(data => {console.log(data)});
}
}
Therefore, what is going wrong? My gut feeling tells me that I am using the subscribe method wrong, but I tried doing it differently and still getting the same result.
So the problem is that you send the request to remove that note from the backend, however in the frontend your list is never updated.
deleteNote(id:string){
console.log(id);
let indexToBeRemoved = this.notes.findIndex( (note) => {note.id === id});
if (indexToBeRemoved != -1){
this.notes.splice(indexToBeRemoved, 1);
}
this.noteService.deleteNote(id).subscribe(data => {console.log(data);});
}
}
If you want to be 100% safe, check the response from the backend that shows the delete was successful and then proceed with removing the element from the frontend

How to disable all buttons coming from ngFor except the button which has been clicked in angular 8

Please open it in full browser https://stackblitz.com/edit/angular-skzgno?file=src%2Fapp%2Fapp.component.html , Then click button/image of any li tag,The button will be change to different image as like active .Even if you refresh also this active will not be change since we are adding active=true into localstorage.Now the problem is,on page load when you click button of any li,except that button,buttons of other li should be disable and when we refresh also nothing will be change until you clear localstorage.Please find the code below
app.component.html
<hello name="{{ name }}"></hello>
<p>
Start editing to see some magic happen :)
</p>
<div>
<pre>
</pre>
<ul>
<li *ngFor="let item of statusdata">
<span>{{item.id}}</span>
<span>{{item.name}}</span>
<button style="margin-left:10px" (click)="toggleActive(item, !item.active)">
<img style="width:50px;margin-left:10px" *ngIf="!item?.active || item?.active === false" src ="https://dummyimage.com/qvga" />
<img style="width:50px;margin-left:10px" style="width:50px;margin-left:10px" *ngIf="item?.active === true" src ="https://dummyimage.com/300.png/09f/fff" />
</button>
</li>
</ul>
</div>
app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
statusdata: any;
ngOnInit() {
this.statusdata = [
{ id: 1, name: "Angular 2" },
{ id: 2, name: "Angular 4" },
{ id: 3, name: "Angular 5" },
{ id: 4, name: "Angular 6" },
{ id: 5, name: "Angular 7" }
];
this.statusdata.forEach(item => {
this.getCacheItemStatus(item);
});
}
toggleActive(item, activeStatus = true) {
if(!this.statusdata.some(d => d.active)){
item.active = activeStatus;
localStorage.setItem(`item:${item.id}`, JSON.stringify(item));
}
}
getCacheItemStatus(item) {
const cachedItem = localStorage.getItem(`item:${item.id}`);
if (cachedItem) {
const parse = JSON.parse(cachedItem); // Parse cached version
item.active = parse.active; // If the cached storage item is active
}
}
}
I'm so so sorry for not being able to adapt this to the code in the question. I faced this challenge and did not want to forget sharing.
say we have this in the typescript file;
movies: any[] = [
{ name: 'Wakanda', year: 2010, rating: 4.5 },
{ name: 'Super', year: 2000, rating: 5 },
{ name: 'Deli', year: 2002, rating: 3 },
{ name: 'Fury', year: 2020, rating: 4 },
];
isDisabled: boolean = false;
Then this in the HTML...
<div *ngFor="let movie of movies;index as i">
<div class="wrapper">
<button (click)="disableBtn('btn' + i)" [disabled]="isDisabled && isDisabled !== 'btn'+i"
id=btn{{i}}>Vote</button>
</div>
</div>
The disableBtn function takes the current button and assigns it to the isDisabled variable then [disabled] attribute checks if isDisabled is truthy and if isDisabled is strictly not equal to the current button. This will be true for all other buttons except the one clicked.
The function disables(toggles it) all other buttons except the one clicked.
disableBtn(btn) {
if (this.isDisabled === btn) { //also re-enables all the buttons
this.isDisabled = null;
return;
}
this.isDisabled = btn;
}
Have you tried the [disabled] property?
<button (click)="toggleActive(item, !item.active)" [disabled]="shouldDisable(item)">
shouldDisable(item): boolean {
return !item.active && this.statusdata.some((status) => status.active);
}

Translations reflect after click on combo, where combo options are filled dynamically in ngOnInit in Angular 6

I have an autocomplete drop-down to which I bind list of ViewOption (my class)
class ViewOption {
name: string;
ordinal: number;
label: string
}
I create list of viewoption in ngOnInit by calling getViewOptionList,
getViewOptionList(): ViewOptions {
const viewoptions = new ViewOptions();
for (const enumMember in ViewOptionEnum) {
if (ViewOptionEnum.hasOwnProperty(enumMember)) {
const viewoption = new ViewOption();
const ordinalValue = parseInt(enumMember, 10);
if (ordinalValue >= 0) {
viewoption.oridinal = ordinalValue;
viewoption.label = ViewOptionEnum[enumMember].toLowerCase();
this.translate.get(viewoption.label).subscribe(msg => viewoption.name = msg);
viewoptions.push(viewoption);
}
}
}
return viewoptions;
}
<lis-auto-complete-dropdown placeholder="select view" automationid="worklistMaster_ddl_selectView"
[options]="ViewOptions" [(selectedItem)]="selectedViewOption"
(itemSelected)="selectViewOption($event)">
<ng-template let-item="item">
<span title="{{ item }}">
<span>{{ item.name }}</span>
</span>
</ng-template>
</lis-auto-complete-dropdown>
Translation reflect only when user clicks on autocomple dropdown on UI. How to make it fill before user taking any action?

How to dynamically change true/false statement inside json object in React.js

I'm trying to add and remove product when clicking a button, and each button is in different component and the data that I'm getting from is in storeData component where inside there is an object with a true/false status if the status is true the product should display in Cart component if false it will remove the product.
now in ProductList component when I click the add button the status is changing to true, but it's not changing the actual status in storeData component so the result when i go to Cart component nothing is displayed
I know I'm doing this the wrong way, so how can I perform this add and remove operation, i'm new in React.js so please any help would really be appreciated.
ProductList component
import itemlist from "../storeData/storeData";
import { Link } from "react-router-dom";
class ProductList extends Component {
state = {
items: itemlist.items,
addToCart: null
};
addItem(id) {
let itemArray = [];
itemlist.cartItems.filter(target => {
return id === target.id ? itemArray.push(target) : null;
});
const addToCart = itemArray[0];
addToCart.status = false;
this.setState({ addToCart });
}
render() {
return (
<div className="list-wrap">
{this.state.items.map(item => {
return (
<div key={item.id}>
<Link to={{ pathname: "/productdetail", itemdetail: item }}>
<img alt="item img" src={item.posterUrl} />
</Link>
<h2>{item.title}</h2>
<h3>${item.price}</h3>
<button onClick={() => this.addItem(item.id)}>Add to Cart</button>
</div>
);
})}
</div>
);
}
}
export default ProductList;
Cart component
import itemlist from "../storeData/storeData";
class Cart extends Component {
state = {
cart: itemlist.cartItems,
remove: null
};
removeItem(id) {
let itemArray = [];
itemlist.cartItems.filter(target => {
return id === target.id ? itemArray.push(target) : null;
});
let remove = itemArray[0];
remove.status = false;
this.setState({ remove });
}
render() {
return (
<div>
{this.state.cart.map(itm => {
return itm.status === false ? null : (
<div key={itm.id} className="cart-layout">
<img alt="img" src={itm.posterUrl} />
<h4>{itm.title}</h4>
<h4>{itm.price}</h4>
<button onClick={() => this.removeItem(itm.id)}>Remove</button>
</div>
);
})}
</div>
);
}
}
storeData component
let itemlist = {
items: [
{
id: 1,
title: "name 1",
price: "232",
posterUrl:
"https://images-na.ssl-images-amazon.com/images/M/MV5BMjIxNTU4MzY4MF5BMl5BanBnXkFtZTgwMzM4ODI3MjE#._V1_SX300.jpg"
},
{
id: 2,
title: "name 2",
price: "65",
posterUrl:
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTY5NTc2NjYwOV5BMl5BanBnXkFtZTcwMzk5OTY0MQ##._V1_SX300.jpg"
},
],
cartItems: [
{
id: 1,
status: false,
title: "name 1",
price: "232",
posterUrl:
"https://images-na.ssl-images-amazon.com/images/M/MV5BMjIxNTU4MzY4MF5BMl5BanBnXkFtZTgwMzM4ODI3MjE#._V1_SX300.jpg"
},
{
id: 2,
status: false,
title: "name 2",
price: "65",
posterUrl:
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTY5NTc2NjYwOV5BMl5BanBnXkFtZTcwMzk5OTY0MQ##._V1_SX300.jpg"
},
]
};
I don't think you are using filter correctly here, in either component. You are confusing the filter test with the action of composing your array. All you need with the filter is a test that will return a boolean and that will construct the array for you.
Try changing:
let itemArray = [];
itemlist.cartItems.filter(target => {
return id === target.id ? itemArray.push(target) : null;
});
To
const itemArray = itemlist.cartItems.filter(target => id === target.id);
And similarly in the cart component.
For more detail on filter see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

ngFor is not showing any data

I'm trying to bind some data to the template with Angular 6, so this is the ngOnInit:
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
this.id = +params['id'];
});
let url = this.baseUrl + `/items/${this.id}`;
this.httpClient.get(url).subscribe((data : Array<any>)=> {
this.ItemToEdit = data;
});
}
Here is the template:
<div *ngFor="let item of ItemToEdit?.results">
<div class="col-sm-8 float-left">
<mat-form-field> <input matInput
class="form-control" placeholder="Item name"
formControlName="name" id="ItemName" value='{{ item.name }}'>
</mat-form-field>
</div>
</div>
The variable ItemToEdit is like following:
{
"results": [
{
"alias": [
"first"
],
"apps": [],
"description": "this is an item",
"id": 1,
"name": "item342"
}
]
}
So, the problem is when I open the template (via some button), the HTML that has the ngFor directive does not show (the div that has the ngFor becomes blank). I don't know how to show the data ... the variable ItemToEdit has only one value (length = 1) but as it has a nested array called results I show data through the for loop. When I try to do something like this inside typescript:
this.loadedName = this.ItemToEdit.results[0].name;
I get an error saying ItemToEdit.results not defined
Any ideas?
EDIT
Found out that my code works but only after refreshing the page...
The server intermittently returns responses like this instead of the expected results:
{ "error": "(_mysql_exceptions.OperationalError) (2006, 'MySQL server has gone away') [SQL: 'SELECT .... }
{ "error": "(_mysql_exceptions.ProgrammingError) (2014, \"Commands out of sync; you can't run this command now\") [SQL: 'SELECT ......" }
================ EDIT 2 ===============
I was getting the Mysql exception because I'm making another httpRequest inside ngOnInit to fill a select field from database ....
After deleting that request, my code works fine.
Your existing ngOnInit method has problems:
It reads this.id before this.id has an opportunity to be set.
It doesn't update this.ItemToEdit when this.id changes.
this.id might not match the actual ID of the currently loaded data.
The following suggested implementation solves these problems by:
waiting until the ID is retrieved before making HTTP requests,
only updating this.id when data is available in this.ItemToEdit, and
cancelling any pending HTTP request if the ID changes before it's fulfulled.
ngOnInit() {
let itemToEditID;
const httpObservable = this.route.params.pipe(
map(params => +params['id']),
tap(newID => itemToEditID = newID),
switchMap(newID => this.httpClient.get(`${this.baseUrl}/items/${newID}`))
);
this.sub = httpObservable.subscribe(data => {
this.id = itemToEditID;
this.ItemToEdit = data;
});
}
Placing <pre>{{ ItemToEdit | json }}</pre> somewhere in your template allowed you to inspect the server's actual output, which allowed you to identify frequent errors coming from the server.
The good news about those errors is that they're caused by the server and not your code. The bad news is that you'll need to determine what's wrong with the server and how to fix that—but that's a topic for another question.
Depending on your needs, you may wish to output the server error on your page when one occurs:
<div *ngIf="ItemToEdit?.error" class="danger">
<h2>An error occurred while trying to retrieve item {{ id }}</h2>
<p>{{ error }}</p>
</div>
<div *ngFor="let item of ItemToEdit?.results">
<!-- ... -->
</div>
try to keep all your process in async
<div *ngFor="let item of (itemToEdit$ | async)">
<div class="col-sm-8 float-left">
<mat-form-field> <input matInput
class="form-control" placeholder="Item name"
formControlName="name" id="ItemName" value='{{ item.name }}'>
</mat-form-field>
</div>
</div>
in your component:
private itemToEdit$: Observable<any>;
....
ngOnInit() {
this.route.params.subscribe(params => {
this.id = +params['id'];
let url = this.baseUrl + `/items/${this.id}`;
this.itemToEdit$ = this.httpClient.get(url).pipe(
map(data => data['results'])
);
});
}
test: any = [];
ngOnInit() {
this.service.test().subscribe(res => {
this.test = res.results;
});
Html:
<p *ngFor="let test of this.test">
{{test.name}}
</p>
if you need to bind it to input field just assign it with: [value]="test?.name"
service:
test(): Observable<any>{
return this.http.get(url);
}
The preferred solution, would be to use async as defined in the answer by antoine-clavijo.
Alternatively, if your component (or parents) are defined using ChangeDetectionStrategy.OnPush, for example:
#Component({
selector: 'app-component',
templateUrl: './config.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
... you can tell Angular that the component needs to be updated, like this:
constructor(
private ref: ChangeDetectorRef,
private httpClient: HttpClient) {}
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
this.id = +params['id'];
});
const url = this.baseUrl + `/items/${this.id}`;
this.httpClient.get(url).subscribe((data: Array<any>)=> {
this.ItemToEdit = data;
this.ref.detectChanges();
});
}