Mat icon dynamically created based on state - javascript

in my app i display list of appeals, with their status (which is displayed using mat icon). Here is piece of my template
<div class="approve-detail" fxLayout="column" fxLayoutGap="10px">
<p>Created: {{absDetail?.reqDate|date:'d. L. y HH:mm'}}</p>
<ng-container *ngIf="absDetail?.defList">
<mat-list >
<mat-list-item class="def-item" *ngFor="let def of absDetail.defList">
<mat-icon matListIcon *ngIf="def.idReject!=0" class="warn" matTooltip="Rejected">thumb_down</mat-icon >
<mat-icon matListIcon *ngIf="def.idApprove!=0&&def.idReject==0" class="success" matTooltip="Approved">thumb_up</mat-icon>
<mat-icon matListIcon *ngIf="def.idReject==0 && def.idApprove==0" matTooltip="Waiting">help</mat-icon>
<p matLine>{{def.name}}</p>
</mat-list-item>
</mat-list>
<mat-list *ngIf="abs | isDeleteAppealed:absDetail.defList">
<mat-list-item class="def-item" *ngFor="let def of absDetail.defList">
<mat-icon matListIcon *ngIf="def.idCancel<0" color="warn" matTooltip="Rejected">thumb_down</mat-icon >
<mat-icon matListIcon *ngIf="def.idCancel>0" class="success" matTooltip="Approved">thumb_up</mat-icon>
<mat-icon matListIcon *ngIf="def.idCancel==0" matTooltip="waiting">help</mat-icon>
<p matLine>{{def.name}}</p>
</mat-list-item>
</mat-list>
</ng-container>
</div>
It works, but its kinda dirty with all that ng-ifs, is there any more elegant way to do this? I was thinking about pipe, but i need to generate three dynamic properties for each element: class, tooltip and proper icon name, so it doesnt seems like a better solution. Any ideas? What about some kind of directive?

One option you could do is wrap them in a separate component. Use your let def as an #Input() parameter. When it gets set, you can automatically change the value of the class/tooltip/icon. Something like this:
abs-icon.component.ts
imports...
#Component({
selector: 'abs-icon',
templateUrl: './abs-icon.component.html',
styleUrls: ['./abs-icon.component.css']
})
export class AbsIconComponent implements OnInit {
public cls: string;
public tooltip: string;
public icont: string;
private _abs: any;
get abs(): any{
return this._abs;
}
#Input() set abs(value: any) {
this._abs = value;
if (_abs.idReject != 0) {
cls = "warn";
tooltip = "Rejected";
icont = "thumb_down"
}
else if (.......do the rest){
/* some code */
}
}
constructor() { }
ngOnInit(): void { }
}
abs-icon.component.html
<mat-icon matListIcon
class="{{cls}}"
[matTooltip]="tooltip">
{{icon}}
</mat-icon >
using it:
<abs-icon [abs]="def"> </abs-icon>

Related

Angular UI not updating after Observable subscription triggers

I have an angular page that uses an observable parameter from a service. However when this observable updates, the page is not updated to match.
I have tried storing the result of the observable in a flat value, or altering a boolean to adjust the UI, but none seem to have any effect.
Logging the observable confirms that it does update correctly, and on re-navigating to the page the new value is shown.
Other conditional UI updates correctly modify the page, only one below (*ngIf="(entries$ | async), else loading") is causing this issue.
component.ts
export class EncyclopediaHomeComponent implements OnInit {
entries$: Observable<EncyclopediaEntry[]>;
categories$: Observable<string[]>;
entry$: Observable<EncyclopediaEntry>;
entry: EncyclopediaEntry;
isEditing: boolean;
constructor(private route: ActivatedRoute, private encyService: EncyclopediaService) {
this.entries$ = encyService.entries$;
this.categories$ = encyService.categories$;
this.entries$.subscribe(es => {
console.log(es);
});
route.url.subscribe(url => this.isEditing = url.some(x => x.path == 'edit'));
this.entry$ = route.params.pipe(
switchMap(pars => pars.id ? encyService.getEntry(pars.id) : of(null)),
);
this.entry$.subscribe(entry => this.entry = entry);
}
ngOnInit(): void {
}
updateEntry(entry: EncyclopediaEntry) {
this.encyService.updateEntry(entry.id, entry);
}
}
component.html
<div class="encyclopedia-container">
<ng-container *ngIf="(entries$ | async), else loading">
<app-enc-list [entries]="entries$ | async"
[selectedId]="entry ? entry.id : null"></app-enc-list>
<ng-container *ngIf="entry">
<app-enc-info *ngIf="!isEditing, else editTemplate"
[entry]="entry$ | async"></app-enc-info>
<ng-template #editTemplate>
<app-enc-edit [entry]="entry$ | async" [categories]="categories$ | async"
(save)="updateEntry($event)"></app-enc-edit>
</ng-template>
</ng-container>
</ng-container>
<ng-template #loading>
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
<br>
<p>Loading Encyclopedia...</p>
</ng-template>
</div>
edit:
service.ts
export class EncyclopediaService {
entriesSubject = new ReplaySubject<EncyclopediaEntry[]>();
entries$ = this.entriesSubject.asObservable();
private _entries: EncyclopediaEntry[];
constructor(private file: FileService) {
file.readFromFile(this.projectName+'Encyclopedia').subscribe((es: string) => {
this._entries = JSON.parse(es);
this.entriesSubject.next(this._entries);
this.entries$.subscribe(es => this.file.writeToFile(this.projectName+'Encyclopedia', JSON.stringify(es)));
});
}
.
.
.
}
It seems like the component does not see changes.
I do not know why because |async will do this job.
but to fix it you can use ChangeDetector:
constructor(
private route: ActivatedRoute,
private encyService: EncyclopediaService
private changeDetectorRef: ChangeDetectorRef,
) {
this.entries$ = encyService.entries$;
this.categories$ = encyService.categories$;
this.entries$.subscribe(es => {
// setTimeout need to run without troubles with ng changes detector
setTimeout(_=>{this.changeDetectorRef.detectChanges()},0);
...
});
or
you can use markforCheck like it described there.

set dropdown value in other component

i create this dropdown component :
<mat-form-field appearance="outline">
<mat-label>{{formTitle| translate}} *</mat-label>
<mat-select [(value)]="itemId">
<div class="col-lg-12 mt-4 kt-margin-bottom-20-mobile">
<mat-form-field class="mat-form-field-fluid" appearance="outline">
<mat-label>{{'GENERAL.TITLE' | translate}} *</mat-label>
<input (keyup)="getValues($event.target.value)" matInput [placeholder]="'GENERAL.TITLE' | translate">
</mat-form-field>
</div>
<mat-progress-bar *ngIf="loading" class="mb-2" mode="indeterminate"></mat-progress-bar>
<mat-option (click)="emitdata(item.key)" *ngFor="let item of values" [value]="item.key">
{{item.value}}
</mat-option>
</mat-select>
ts:
export class SearchableDropdownComponent implements OnInit {
#Input() url: string;
#Input() formTitle: string;
#Output() selectedId = new EventEmitter<number>();
#Input() itemId: number;
loading = false;
values: KeyValue[];
title: string;
constructor(
private searchService: SearchableDropDownService,
private cdRef: ChangeDetectorRef) {
}
ngOnInit(): void {
this.getValues(null);
}
emitdata(event): void {
console.log(event);
this.selectedId.emit(event);
}
getValues(event): void {
this.cdRef.detectChanges();
this.loading = true;
let model = {} as SendDateModel;
model.page = 1;
model.pageSize = 60;
model.title = event;
this.searchService.getAll(this.url, model).subscribe(data => {
this.values = data['result']['records'];
this.cdRef.detectChanges();
this.loading = false;
});
}
}
and i used it in components :
<div class="col-lg-6 kt-margin-bottom-20-mobile">
<kt-searchable-dropdown [itemId]="304" [formTitle]="'COURSE.COURSE_GROUP'" [url]="url"></kt-searchable-dropdown>
</div>
i pass to dropdown 304 item for pre selected it in dropdown . but it not pre-selected item 304 in dropdown .
how can i set the 304 item selected in dropdown?
As you are taking the ItemId using Input() you can always use ngModel to implement two way data binding.
For e.g. in your case :
<mat-select [(ngModel)]="ItemId">
<mat-option *ngFor="let item of values" [value]="item.key">{{item.value}}</mat-option>
</mat-select>
So, basically it will get binded in two way with variable contain data and will remain selected as you load the component.
You can read more about two way data binding here.
Update
ngModel should work. You have not wrongly implemented but its not correct way also.
Your selected value only will come from [(ngModel)].
so your
[(value)]="itemId"
should be
[(ngModel)]="itemId"
complete code will be :
<mat-select [(ngModel)]="ItemId">
<mat-option *ngFor="let item of values" [value]="item.key">{{item.value}}</mat-option>
</mat-select>
Try not to declare any div or component after mat-select if not necessary or debug one by one if its bugging to your component or not.

Template parse errors angular 8

I'm working with angular 8 i want to display a popup with angular material this is what i did :
In my Html :
<button (click)="myDia.open()">Open it</button>
Result: {{result | json}}
<my-dialog (result)="result = $event" #myDia>
<h1 mat-dialog-title>Dialog Title</h1>
<mat-dialog-content>
Content
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button (click)="myDia.close()">Cancel</button>
<button mat-button (click)="myDia.close({foo:'bar'})">Confirm</button>
</mat-dialog-actions>
</my-dialog>
And this is the Ts file :
#Component({
selector: 'my-dialog',
template: `
<ng-template>
<ng-content></ng-content>
</ng-template>
`
})
export class MyDialogComponent {
#ViewChild(TemplateRef)
private templateRef: TemplateRef<any>;
#Output()
result = new EventEmitter<boolean>();
private dialogRef: MatDialogRef<any>;
constructor(private dialog: MatDialog) {}
open() {
this.dialogRef = this.dialog.open(this.templateRef);
this.dialogRef.afterClosed().subscribe(val => {
this.result.emit(val);
});
}
close(val: any) {
this.dialogRef.close(val);
}
}
I have a problem in this line :
#ViewChild(TemplateRef)
The error is :
Expected 2 arguments, but got 1
An argument for 'opts' was not provided.
I have added in my app.module.ts :
import { MyDialogComponent } from './pages/interfacage/test/test.component';
So how can i solve this problem.
I'am using this tuto path
From Angular 8, you need to add { static: true } in ViewChild
Try:
#ViewChild('TemplateRef', { static: true }) TemplateRef: any;
Now it work in Angular8
#ViewChild(TemplateRef, {static: false})
private templateRef: TemplateRef<any>;
https://stackblitz.com/edit/angular-material2-issue-yqcfpj?file=app/app.component.html

Angular 5 console.log selected nav list item

I am trying to dynamicaly create a sidenav from a remote JSON, which is working fine, it is getting the JSON from the URL and parsing it into a nav list. Now i want to console.log the selected nav list item, which i can't do, i looked up several solutions online but no use. Here is my code:
the service:
import 'rxjs/add/operator/map';
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Router } from '#angular/router';
interface Applications {
AUTO_ID: number;
REGISTARSKI_BROJ: string;
AUTO_TIP: string;
GODINA_PROIZVODNJE: string;
TIP_MOTORA: string;
BROJ_SASIJE: string;
SNAGA_HP: number;
KUPAC_ID: number;
}
#Injectable()
export class MenuService {
objectKeys = Object.keys;
private url = `MY_URL`;
public apps: Applications[];
constructor(public router: Router, protected http: HttpClient) { }
menu(e) {
e.preventDefault();
this.http.get(this.url).subscribe(result => {
this.apps = result as Applications[];
}, error => console.error(error));
}
ispisi(e) {
e.preventDefault();
console.log(this.apps); // WHEN I DO IT LIKE THIS, IT LOGS ALL OF THE OBJECTS TO THE CONSOLE
this.router.navigate(['table']);
}
}
HTML template:
<mat-sidenav-container>
<mat-sidenav #sidenav mode="push">
<mat-toolbar>
Glavni meni
</mat-toolbar>
<mat-nav-list>
<mat-list-item (click)="sidenav.toggle()" [routerLink]="['/']"><i class="material-icons">assessment</i>Dashboard</mat-list-item>
<mat-accordion>
<mat-expansion-panel (click)="menuService.menu($event)">
<mat-expansion-panel-header>
<mat-panel-title>
<i class="material-icons">directions_car</i>Automobili
</mat-panel-title>
</mat-expansion-panel-header>
<mat-nav-list *ngIf="menuService.apps">
<mat-list-item (click)="menuService.ispisi($event)" *ngFor="let app of menuService.apps">
{{app.REGISTARSKI_BROJ}}
</mat-list-item>
</mat-nav-list>
</mat-expansion-panel>
</mat-accordion>
</mat-nav-list>
</mat-sidenav>
<mat-toolbar color="primary">
<button mat-icon-button (click)="sidenav.toggle()">
<mat-icon>menu</mat-icon>
</button> Service stats
<span class="example-spacer"></span>
<button (click)="authService.logOut()" matTooltip="Izloguj se" matTooltipPosition="below" class="user" mat-icon-button>
<i class="material-icons">account_circle</i>
</button>
</mat-toolbar>
</mat-sidenav-container>
i am not sure i get what you are trying to do, but if you want the selected menu you should just change
<mat-list-item (click)="menuService.ispisi($event)" *ngFor="let app of menuService.apps">
{{app.REGISTARSKI_BROJ}}
</mat-list-item>
to something like this
<mat-list-item (click)="menuService.ispisi(app)" *ngFor="let app of menuService.apps">
{{app.REGISTARSKI_BROJ}}
</mat-list-item>
this way you will have access to the selected menu, and you can from there implement your selected logic

event propagation in angular2+

I know this question might sound familiar and repetitive and already answered plenty of times. Please bear with me. I'm posting only because I could not resolve my issue despite trying the solutions expressed in other questions.
I have a component <od-resource-table [..] [..]></od-resource-table> rendered 2 times in the page. It's basically a table and comes with controls to show/hide columns. The problem I'm facing is that when I tried to hide a column in second table it's affecting the first table and not the table I intended. If I remove the first table, then no problems. Please advise. What am I missing Thanks in advance.
Here's the code that emits
onChange(event: MouseEvent, field: string) {
this.onToggleColumnVisibility.next(field)
event.stopPropagation();
}
Here's the gif showcasing the behavior
http://g.recordit.co/AoKbazXdr2.gif
search control component contains column picker controls
#Component({
selector: 'od-search-control',
templateUrl: './search-control.component.html',
styleUrls: ['./search-control.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
})
export class SearchControlComponent {
#Input() searchText: string = '';
#Input() searchPlaceholder: string = 'Search';
#Input() downloadUrl: any = '';
#Input() columnPickerDefs: Array<ISearchControl.ColumnPickerDef> = [];
#Output() onRefresh = new EventEmitter<any>();
#Output() onToggleColumnVisibility = new EventEmitter<any>();
#Output() onTextChange = new EventEmitter<any>();
constructor() { }
onChange(event: MouseEvent, field: string) {
event.stopPropagation();
this.onToggleColumnVisibility.next(field)
}
}
resource table component
<div class="od-search-controls-bar">
<div class="header">
<h3>
<span class="page-title">{{title}}</span>
<span class="estimate">{{(estimate$ | async) | lpad : 2 : '0'}}</span>
</h3>
<div class="search-control-bar-wrapper">
<od-search-control
...
...
...
(onToggleColumnVisibility)="onToggleColumnVisibility($event)"
></od-search-control>
</div>
</div>
</div>
<od-data-table
#resTable
[columnDefs]="columnDefs"
[rowData]="rowData$ | async"
[estimates]="estimate$ | async"
[currentPage]="currentPage$ | async"
[itemsPerPage]="itemsPerPage$ | async"
(onPageChange)="onPageChange($event)"
(sortModel)="onSort($event)"
></od-data-table>
#Component({
selector: "od-resource-table",
templateUrl: "./resource-table.component.html",
styleUrls: ["./resource-table.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ResourceTableComponent implements OnInit, OnDestroy, OnChanges {
#Input() category: string;
...
...
onToggleColumnVisibility(columnField: string) {
let columnIndex = this.columnDefs.findIndex(col => col.field === columnField);
if (columnIndex > -1) {
this.columnDefs[columnIndex].hide = !this.columnDefs[columnIndex].hide;
this.columnDefs = [...this.columnDefs];
}
}
}
Page containing 2 tables
#Component({
selector: 'od-resource-assignment-view',
template: `
<div class="resource-navigation-panel">
<label class="active"
routerLink="/console-settings/resource-groups/"
>Resource Groups</label>
<label class="text-ellipsis"><span class="active">/</span>{{resourceGroupName}}</label>
</div>
<div class="assignment-resource-wrapper">
<od-resource-table #assignedResourcesTable
[resources]="(resourceGroupType$ | async)"
[resourceGroupId]="resourceGroupId"
[enableNodeSelection]="true"
title="Assigned Resources"
category="resource group">
</od-resource-table>
<div class="assign-button-wrapper">
<button type="button" class="btn btn-secondary" (click)="unassignResources()">
DEASSIGN
</button>
</div>
</div>
<div>
<od-resource-table #enterpriseResourcesTable
[resources]="(resourceGroupType$ | async)"
[enableNodeSelection]="true"
[exclude]="(excludeResources$ | async)"
title="Total Resources"
category="enterprise">
</od-resource-table>
<div class="assign-button-wrapper">
<button type="button" class="btn btn-secondary" (click)="assignResources()">
ASSIGN
</button>
</div>
</div>
`,
styleUrls: ['./resource-assignment-view.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
})
export class ResourcesAssignmentViewComponent implements OnInit, OnDestroy {
#Input() resourceGroupId: string;
#Input() resourceGroupName: string;
#ViewChild('assignedResourcesTable') assignedResourcesTable: ResourceTableComponent
#ViewChild('enterpriseResourcesTable') enterpriseResourcesTable: ResourceTableComponent
resourceGroupType$: Observable<string>
excludeResources$: Observable<string[]>
resourceGroup: IResourceGroups.ResourceGroupItem
_subs: Subscription[] = new Array<Subscription>()
constructor(private _store: Store<IResourceGroups.ResourceGroupsState>) { }
ngOnInit() {}
...
...
...
}
That is because both of the tables are using the same instance of onChange and when you perform action, the first table receives the event first and then you execute event.stopPropagation();
within the listener.
Solution:
Try to differentiate the tables in the listener (onChange) or use the separate instance of the listener.

Categories