I'm trying to do something similar to this application:
https://stackblitz.com/edit/angular-table-sort-and-filter?file=src%2Fapp%2Fapp.component.ts
This is my application :
https://stackblitz.com/edit/angular-table-sort-and-filter-w9hrc3?file=src%2Fapp%2Fapp.component.html
HTML:
<thead class="thead-light">
<tr>
<th
*ngFor="let item of list.table.headings; let i = index"
scope="col"
sortable="{{ item.content }}"
(sort)="onSort($event, i)"
>
{{ item.content }}
</th>
</tr>
</thead>
Directive:
export type SortColumn = string | '';
export type SortDirection = 'asc' | 'desc' | '';
const rotate: { [key: string]: SortDirection } = {
asc: 'desc',
desc: '',
'': 'asc',
};
export const compare = (
v1: string | number | boolean | Date,
v2: string | number | boolean | Date
) => (v1 < v2 ? -1 : v1 > v2 ? 1 : 0);
export interface SortEvent {
column: SortColumn;
direction: SortDirection;
}
#Directive({
selector: 'th[sortable]',
host: {
'[class.asc]': 'direction === "asc"',
'[class.desc]': 'direction === "desc"',
'(click)': 'rotate()',
},
})
export class SortableHeaderDirective {
#Input() sortable: SortColumn = '';
#Input() direction: SortDirection = '';
#Output() sort = new EventEmitter<SortEvent>();
rotate() {
this.direction = rotate[this.direction];
this.sort.emit({ column: this.sortable, direction: this.direction });
}
}
An up and a down icon is appearing when the user clicks on the header of the table in the original code (first link). But in my code, it didn't appear. I don't know what I'm doing wrong.
If you inspect the rendered <th> element:
<th _ngcontent-hpt-c62="" scope="col" ng-reflect-sortable="Tercih"> Tercih </th>
There is no sortable attribute, while your SortableHeaderDirective is applied to <th> element with sortable attribute: 'th[sortable]'.
With sortable="{{ item.content }}" or [sortable]="item.content" is used as property binding.
You can perform either of these to add the sortable attribute:
Add sortable without string interpolation.
Add [attr.sortable]="item.content".
<th
*ngFor="let item of list.table.headings; let i = index"
scope="col"
sortable="{{ item.content }}"
(sort)="onSort($event, i)"
sortable
>
{{ item.content }}
</th>
Demo # StackBlitz
HTML:
<div class="row">
<div
*ngFor="let item of this.list.table.headings; let i = index"
class="table-view__item__col {{ item.class }}"
>
<div scope="col" (sort)="onSort($event, i, this.table)" sortable="{{ item.content }}">
{{ item.content }}
</div>
</div>
</div>
Directive:
#Directive({
selector: 'div[sortable]',
host: {
'[class.sorting]': 'true',
'[class.sorting-desc]': 'direction === "asc"',
'[class.sorting-asc]': 'direction === "desc"',
'(click)': 'rotate()',
},
})
Related
preview image
https://angular-table-tree-example-md58kf.stackblitz.io
Good morning, could someone help me to know how I can obtain the selected rows with the radio button, the table is with a tree, I attach the code in the following link:
https://stackblitz.com/edit/angular-table-tree-example-md58kf?file=app/table-basic-example.ts
ps: i am using google translate
this is the code i tried to do
HTML
<table mat-table [dataSource]="dataSourceTree" class="mat-elevation-z8">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>
<span [style.paddingLeft.px]="40"> Name </span>
</th>
<td mat-cell *matCellDef="let data">
<div
*ngIf="data.group"
[style.marginLeft.px]="data.level * 20"
style="display: inline"
>
<mat-radio-button
class="example-radio-button"
[checked]="data.selected"
[name]="data.group"
[value]="data"
>
</mat-radio-button>
</div>
{{data.name}}
</td>
</ng-container>
<ng-container matColumnDef="count">
<th mat-header-cell *matHeaderCellDef>Code</th>
<td mat-cell *matCellDef="let data">
<div
*ngIf="data.group"
[style.marginLeft.px]="data.level * 20"
style="display: inline"
></div>
{{data.count}}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<button (click)="getSelected()">show items selected</button>
TYPESCRIPT
import { FlatTreeControl } from '#angular/cdk/tree';
import { Component } from '#angular/core';
import {
MatTreeFlatDataSource,
MatTreeFlattener,
} from '#angular/material/tree';
interface FoodNode {
name: string;
count?: number;
children?: FoodNode[];
group?: number;
selected?: boolean;
expandable?: boolean;
level?: number;
}
const TREE_DATA: FoodNode[] = [
{
name: 'Math',
count: 11,
children: [
{ name: 'MAT1A', count: 10, group: 1 },
{ name: 'MAT1B', count: 20, group: 1 },
{ name: 'MAT1C', count: 30, group: 1 },
],
},
{
name: 'Physical',
count: 22,
children: [
{ name: 'FIS1A', count: 40, group: 2 },
{ name: 'FIS1B', count: 50, group: 2 },
{ name: 'FIS1C', count: 60, group: 2 },
],
},
];
/**
* #title Basic use of `<table mat-table>`
*/
#Component({
selector: 'table-basic-example',
styleUrls: ['table-basic-example.css'],
templateUrl: 'table-basic-example.html',
})
export class TableBasicExample {
displayedColumns: string[] = ['name', 'count'];
private transformer = (node: FoodNode, level: number) => {
return {
expandable: !!node.children && node.children.length > 0,
name: node.name,
count: node.count,
level: level,
group: node.group,
FoodNode: node.children,
selected: node.selected,
};
};
treeControl = new FlatTreeControl<FoodNode>(
(node) => node.level,
(node) => node.expandable
);
treeFlattener = new MatTreeFlattener(
this.transformer,
(node) => node.level,
(node) => node.expandable,
(node) => node.children
);
dataSourceTree = new MatTreeFlatDataSource(
this.treeControl,
this.treeFlattener
);
constructor() {
this.dataSourceTree.data = TREE_DATA;
this.treeControl.expandAll();
}
hasChild = (_: number, node: FoodNode) => node.expandable;
getSelected() {
let result = [];
this.dataSourceTree.data.forEach((node) => {
result = result.concat(
this.treeControl.getDescendants(node).filter((x) => x.selected)
);
console.log(node);
});
console.log(result);
}
}
You can add mat-radio-group for each element in the array TREE_DATA, then use [(ngModel)] to bind the selected item to a member in that array as following:
<mat-radio-group [(ngModel)]="data.selectedItem">
Here is a full solution for inspiration:
https://stackblitz.com/edit/angular-ivy-ycnz1d?file=src/app/app.component.html
To make a demo of the problem I have a table where I show a list of details and several mat-select that when selecting an option the "size" of this marked option is loaded in the td.
The problem is that it does not load the "size" in the same row where an option is selected. How I can get this? The size is shown in the td but in order from top to bottom.
For example if there is no option marked and I select an option from row 3. The "size" of this option would be loaded in the first row and if I mark an option in any other row the data is loaded following the order of the rows.
I have the demo in stackblitz: DemoStackblitz
HTML
<table class="table">
<tr>
<th>Titles</th>
<th>Size</th>
<th>Detail</th>
<th>Duration</th>
</tr>
<tr *ngFor="let row of dataDetails let i = index">
<td>
<mat-form-field appearance="outline">
<mat-select [formControl]="dataCtrl" placeholder="Select" [compareWith]="compareItems" #singleSelect>
<mat-option>
<ngx-mat-select-search [formControl]="dataFilterCtrl"></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let op of filteredData | async" [value]="op.id" (click)="onSelect(op)"
[disabled]="op.condition===1">
{{op.title}}
</mat-option>
</mat-select>
</mat-form-field>
</td>
<td>{{OptionSelected[i]?.size}}</td>
<td>{{row.detail}}</td>
<td>{{row.duration}}</td>
</tr>
</table>
TS
dataTitles: any = DataTitles;
dataDetails: any = DataDetails;
dataCtrl: FormControl = new FormControl();
dataFilterCtrl: FormControl = new FormControl();
filteredData: ReplaySubject<any> = new ReplaySubject<any>(1);
#ViewChild('singleSelect', { static: true }) singleSelect: MatSelect;
_onDestroy = new Subject<void>();
ngOnInit() {
this.dataCtrl.setValue(this.dataDetails[0]);
this.filteredData.next(this.dataTitles.slice());
this.dataFilterCtrl.valueChanges
.pipe(takeUntil(this._onDestroy))
.subscribe(() => {
this.filterData();
});
}
compareItems(i1: any, i2: any) {
return i1 && i2 && i1.key === i2.key;
}
ngOnDestroy() {
this._onDestroy.next();
this._onDestroy.complete();
}
OptionSelected: any = [];
onSelect(option: any) {
var objIndex = this.dataTitles.findIndex(x => x.id == option.id);
if (this.dataTitles[objIndex].title !== 'None') {
this.dataTitles[objIndex].condition = 1;
}
if (this.OptionSelected.length === 0) {
this.OptionSelected.push({ id: option.id, size: option.size });
} else {
let check = this.OptionSelected.map((item: any) => item.id).includes(
option.id
);
if (check === false) {
this.OptionSelected.push({ id: option.id, size: option.size });
}
}
console.log(this.OptionSelected);
}
filterData() {
if (!this.dataTitles) {
return;
}
let search = this.dataFilterCtrl.value;
if (!search) {
this.filteredData.next(this.dataTitles.slice());
return;
} else {
search = search.toLowerCase();
}
this.filteredData.next(
this.dataTitles.filter(
(x: any) => x.title.toLowerCase().indexOf(search) > -1
)
);
}
Issue
You´r pushing the current selected value into the OptionSelected during the call of
onSelect. This causes the mismatch of selected row data and which row actualy shows the data.
Pseudo code example:
Select TitleA in Row 4
Push new Data into OptionSelected
OptionSelected[0]?.size displays data in the first row
Solution
You are already tracking the value of the current selected value with the template variable #singleSelect over here:
<mat-select ... #singleSelect>
The <mat-select> component has a value input.
#Input()
value: any Value of the select control.
So you can display the current value by simply using the template ref:
<td>{{singleSelect.value}}</td>
Stackblitz example
I'm new to angular. I've been trying to sort columns but I keep on getting this error:
Argument of type 'Event' is not assignable to parameter of type 'SortEvent'.
Type 'Event' is missing the following properties from type 'SortEvent': column, direction - ngtsc(2345).
Any suggestions on how to make this work?
s-product-management.component.html:
<form>
<div class="form-group form-inline">
Full text search: <input class="form-control ml-2" type="text" name="searchTerm" [(ngModel)]="service.searchTerm"/>
<span class="ml-3" *ngIf="service.loading$ | async">Loading...</span>
</div>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col" sortable="name" (sort)="onSort($event)">Country</th>
<th scope="col" sortable="area" (sort)="onSort($event)">Area</th>
<th scope="col" sortable="population" (sort)="onSort($event)">Population</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let product of products$ | async">
<th scope="row">{{ product.id }}</th>
<td>
<img [src]="'https://upload.wikimedia.org/wikipedia/commons/' + product.flag" class="mr-2" style="width: 20px">
<ngb-highlight [result]="product.name" [term]="service.searchTerm"></ngb-highlight>
</td>
<td><ngb-highlight [result]="product.area | number" [term]="service.searchTerm"></ngb-highlight>
</td>
<td><ngb-highlight [result]="product.population | number" [term]="service.searchTerm"></ngb-highlight></td>
</tr>
</tbody>
</table>
<div class="d-flex justify-content-between p-2">
<ngb-pagination [collectionSize]="(total$ | async)!" [(page)]="service.page" [pageSize]="service.pageSize">
</ngb-pagination>
<select class="custom-select" style="width: auto" name="pageSize" [(ngModel)]="service.pageSize">
<option [ngValue]="2">2 items per page</option>
<option [ngValue]="4">4 items per page</option>
<option [ngValue]="6">6 items per page</option>
</select>
</div>
</form>
s-product-management.component.ts:
#Component(
{selector: 'app-s-product-management', templateUrl: './s-product-management.component.html', providers: [ProductService, DecimalPipe]})
export class SProductManagementComponent {
products$: Observable<Product[]>;
total$: Observable<number>;
#ViewChildren(NgbdSortableHeader) headers: QueryList<NgbdSortableHeader>;
constructor(public service: ProductService) {
this.products$ = service.products$;
this.total$ = service.total$;
this.headers = new QueryList();
}
onSort({column, direction}: SortEvent) {
// resetting other headers
this.headers.forEach(header => {
if (header.sortable !== column) {
header.direction = '';
}
});
this.service.sortColumn = column;
this.service.sortDirection = direction;
}
}
sortable.directive.ts:
export type SortColumn = keyof Product | '';
export type SortDirection = 'asc' | 'desc' | '';
const rotate: {[key: string]: SortDirection} = { 'asc': 'desc', 'desc': '', '': 'asc' };
export interface SortEvent {
column: SortColumn;
direction: SortDirection;
}
#Directive({
selector: 'th[sortable]',
host: {
'[class.asc]': 'direction === "asc"',
'[class.desc]': 'direction === "desc"',
'(click)': 'rotate()'
}
})
export class NgbdSortableHeader {
#Input() sortable: SortColumn = '';
#Input() direction: SortDirection = '';
#Output() sort = new EventEmitter<SortEvent>();
rotate() {
this.direction = rotate[this.direction];
this.sort.emit({column: this.sortable, direction: this.direction});
}
}
You need add NgbdSortableHeader into declarations for app.module.ts.
So that your NgbdSortableHeader directive is registered and will emit an event with SortEvent object via rotate().
import { NgbdSortableHeader } from './directives/sortable.directive';
#NgModule({
...
declarations: [
...
NgbdSortableHeader
]
})
export class AppModule {}
Sample Solution on StackBlitz
References
Angular Bootstrap Sortable Table Demo
$event is not the same type as SortEvent, as you need. $event will always contain a lot of key-value pairs, unless you are using with anything other than legacy elements.
you need to add import { MatSortModule } from '#angular/material/sort' in app.module.ts
I'm currently using a package inside an Angular project that houses reusable components I've developed for the project. How can I make the column headers/rows on a material table dynamic so that I can simply pass them as an input array when I consume the table inside my Angular project?
Some of these headers are stacked or have unique HTML/CSS, which is where I'm running into the issue. I've tried creating an array inside my component that has a boolean flag stating whether or not a column header/row should house two fields stacked on top of each other.
Below is a snippet from my current HTML that isn't dynamic. You can see that both ng-containers are different. How can i write this in such a way that when i consume my table inside my project, i can simply pass an array of columns/rows as an input?
<ng-container matColumnDef="from">
<th mat-header-cell *matHeaderCellDef>
<div [ngStyle] = "{'color': pickupHeader}" class = "stackedColumn">
<span (click)="toggleDates($event)">{{ 'shipperFrom' }}</span>
</div>
<div [ngStyle] = "{'color': deliveryHeader}" class = "stackedColumn">
<span (click) = "toggleDates($event)">{{ 'shipperTo' }}</span>
</div>
</th>
<td mat-cell *matCellDef="let element">
<div>
<span class = "location"> <img src="{{ element.Flag }}">{{element.PickupCity}}</span>
</div>
<div>
<span class = "location"><img src="{{ element.Flag }}">{{element.DeliveryCity}}</span>
</div>
</td>
</ng-container>
<ng-container matColumnDef="legs">
<th mat-header-cell *matHeaderCellDef> {{ somethingElse }} </th>
<td mat-cell *matCellDef="let element"> {{element.SomethingElse}} </td>
</ng-container>
Basically I want to do something in my component.ts that looks like this:
data = [{},{},{},{}]
and I want that array of objects to populate the table and know what kind of HTML it should use so that when I import it and consume it in my project this is all I need:
<the-table [dataSource] = "data"></the-table>
Essentially I want to be able to add columns/rows to the table on the fly without having to go back and edit the package.
I've done something similar. Keep in mind I messed with the below a little bit to strip out some project specific details, so let me know if there are issues.
component.html:
<mat-table matSort [dataSource]="dataSource">
<ng-container *ngFor="let column of displayedColumns; index as i" [matColumnDef]="column">
<mat-header-cell *matHeaderCellDef class="col-search" fxLayoutAlign="start start" fxLayout="column">
<span mat-sort-header>{{ getColumnLabel(column) }}</span>
<!--The below is incomplete Just remove if you don't need filtering-->
<input
matInput
autocomplete="off"
id="{{ column + i }}"
(keyup)="myCustomFilterCode()"
placeholder="Filter"
/>
</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row[column] }}</mat-cell>
</ng-container>
<!--This ngStyle is specifically for fixedWidth tables which are meant to be horizontally scrollable-->
<mat-header-row [ngStyle]="{ 'min-width.px': width ? width : null }" *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row [ngStyle]="{ 'min-width.px': width ? width : null }" *matRowDef="let row; columns: displayedColumns"> </mat-row>
</mat-table>
<mat-paginator [pageSizeOptions]="pageSizeOptions" showFirstLastButtons [pageSize]="startingPageSize"></mat-paginator>
component.ts
/**This component will dynamically create a mat-table for a set of data */
#Component({
selector: 'dynamic-mat-table',
templateUrl: './dynamic-mat-table.component.html',
styleUrls: ['./dynamic-mat-table.component.scss'],
providers: [DatePipe]
})
export class DynamicMatTableComponent implements OnInit, OnChanges {
/**The data to generate a table for */
#Input()
tableData: Object[];
/** This will override the column names in the table, the id will be the property name (i.e. propertyID)
* and the value will be the column header text
*
* **This is Only Necessary if you don't like the Automatically created column names**
*/
#Input()
columnLabels: IdValuePair[];
/**List of column property names (i.e. propertyID) to ignore */
#Input()
ignoreColumns: string[];
/**Sets the starting page size for the paginator */
#Input()
startingPageSize: number;
/**Sets the page size options for the paginator */
#Input()
pageSizeOptions: number[];
/**Defaults to false, when true the table will be generated with a width of # of columns * columnWidth,
* otherwise column widths will not be specified (will fill container with equal width columns) */
#Input()
fixedWidth = false;
/**Defaults to 250, Only used when fixedWidth = true if not set this determines the width of each column */
#Input()
columnWidth = 250;
width: number;
dataSource = new MatTableDataSource<Object>();
fullColumnLabels: IdValuePair[] = [];
displayedColumns: string[];
#ViewChild(MatPaginator, { static: true })
paginator: MatPaginator;
#ViewChild(MatSort, { static: true })
sort: MatSort;
constructor(private datePipe: DatePipe) {}
ngOnInit() {}
/**Generate dynamic table whenever inputs change */
ngOnChanges() {
this.initTable();
if (this.tableData && this.tableData.length > 0) {
this.displayedColumns = Object.keys(this.tableData[0]);
this.removeIgnoredColumns();
this.createLabelsForColumns(this.displayedColumns);
this.calculateRowWidth(this.displayedColumns.length);
this.dataSource.data = this.pipeData([...this.tableData]);
}
}
/**Create the labels array for each column */
private createLabelsForColumns(columns: string[]) {
this.fullColumnLabels = this.columnLabels;
if (this.fullColumnLabels === undefined) {
this.fullColumnLabels = [];
}
if (this.tableData && this.tableData.length > 0) {
columns.forEach(x => {
if (!this.fullColumnLabels.some(label => label.id === x)) {
this.fullColumnLabels.push(new IdValuePair(x, _.startCase(x)));
}
});
}
}
/**Remove ignored columns to prevent from being displayed */
private removeIgnoredColumns() {
if (this.ignoreColumns) {
this.displayedColumns = this.displayedColumns.filter(x => !this.ignoreColumns.some(y => y === x));
}
}
/**Calculate the row width by the number of columns */
private calculateRowWidth(columnNumber: number) {
if (this.fixedWidth) {
this.width = columnNumber * this.columnWidth;
}
}
/**Initialize table */
private initTable() {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
/**Cleans up data with pipes if necessary*/
private pipeData(data: Object[]): Object[] {
data = this.pipeDates(data);
return data;
}
/**Pipe dates through a date pipe if the property name contains 'date' and the value looks like a date*/
private pipeDates(data: Object[]): Object[] {
// ISO_8601 is what .net core returns, may need to expand this list
const formats = [moment.ISO_8601];
// Loop through each row of data
data.forEach((row, index) => {
// Loop through each property in each row
Object.keys(data[index]).forEach(propertyName => {
// Get the value of the property
const value = data[index][propertyName];
// If the value matches a format in the format list, then transform it to a short date
if (propertyName.toLowerCase().search('date') !== -1 && moment(value, formats, true).isValid()) {
data[index][propertyName] = this.datePipe.transform(value, 'short');
}
});
});
return data;
}
/**Gets the label for a given column from the property name */
getColumnLabel(column: string): string {
return this.fullColumnLabels.find(x => x.id === column).value;
}
}
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!