I'm trying to remove a list item with the click button, tried various options but it seems to not work. Hope you can help me out with this.
On click i want to remove a list item from my users array. I will link the Typescript code alongside with the HTML.
//Typescript code
import { UsersService } from './../users.service';
import { Component, EventEmitter, Input, OnInit, Output } from '#angular/core';
import { Iuser } from '../interfaces/iuser';
#Component({
selector: 'tr[app-table-row]',
templateUrl: './table-row.component.html',
styleUrls: ['./table-row.component.css']
})
export class TableRowComponent implements OnInit {
#Input() item!: Iuser;
#Output() userDeleted = new EventEmitter();
removeUser(item: any) {
this.userDeleted.emit(item);
}
constructor() {}
ngOnInit(): void {}
}
<th scope="row">{{item.id}}</th>
<td>{{item.name}}</td>
<td>{{item.lastname}}</td>
<td>{{item.city}}</td>
<td> <button class="btn btn-sm" (click)="removeUser(item)">remove</button></td>
As #Priscila answered, when the button is clicked, you should only emit the action and let the parent component control the respective method i.e. delete or add.
Because that way, it will be easy for the data to be manipulated and handle the component's lifecycle.
Never keep the dead ends running on the app.
Happy Coding :)
Related
I want to add navigation path to all my buttons in the left menu (which is not the main menu).
I am getting the menu items name as #Input. I have created a dictionary for all the items name and their navigation path.
Here is the HTML:
<div class="row-styles" id="elements" *ngFor="let item of elements">
<button *ngIf="(item.action !== NO_ACCESS )" class="inner-children" routerLinkActive="active" id="inner-children"
[routerLink]="">
<span>{{item.resource}}</span>
</button>
</div>
Here is the TS file
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'apm-menu-resource',
templateUrl: './menu-resource.component.html',
styleUrls: ['./menu-resource.component.less']
})
export class MenuResourceComponent implements OnInit {
#Input() public elements = [];
constructor() {
const menupath = new Map<string, string>();
menupath.set('General', '/Adigem/config/general');
menupath.set('Messaging', '/Adigem/config/messaging');
menupath.set('Server', '/Adigem/config/email/server');
menupath.set('Alerting', '/Adigem/config/email/alert');
menupath.set('Network', '/Adigem/config/network');
menupath.set('Inventory', '/Adigem/config/inventory');
menupath.set('External port', '/Adigem/config/snmp/external-port');
menupath.set('Cloud Data', '/Adigem/config/clouddata');
menupath.set('Performance', '/Adigem/config/Performance');
menupath.set('CFG', '/Adigem/config/cfg');
menupath.set('System', '/Adigem/config/system');
console.log(menupath);
}
ngOnInit() {
}
}
I want to know what to add in the router link in the HTML so that it navigates to the proper menu item.
If you have access to the elements array, that's being passed to the component, you could simplify things a lot - you just add the target path to each of the items and your MenuResourceComponent won't have to deal with any path-related logic.
From your snippets I infer that there is a resource property, which is the element's title. If so, the elements array can be modified like this:
elements = [
{resource:'General', path: '/Adigem/config/general'},
{resource:'Messaging', path: '/Adigem/config/messaging'},
...
]
and then in the template:
<div class="row-styles" id="elements" *ngFor="let item of elements">
<button *ngIf="(item.action !== NO_ACCESS )" class="inner-children"
routerLinkActive="active" id="inner-children"
[routerLink]="item.path">
<span>{{item.resource}}</span>
</button>
</div>
However, if you have no other options and need to menupath map, then you can make it a class field:
import { Component, Input } from '#angular/core';
#Component({
selector: 'apm-menu-resource',
templateUrl: './menu-resource.component.html',
styleUrls: ['./menu-resource.component.less']
})
export class MenuResourceComponent{
#Input() public elements = [];
menupath = new Map<string, string>();
constructor() {
this.menupath.set('General', '/Adigem/config/general');
this.menupath.set('Messaging', '/Adigem/config/messaging');
this.menupath.set('Server', '/Adigem/config/email/server');
this.menupath.set('Alerting', '/Adigem/config/email/alert');
this.menupath.set('Network', '/Adigem/config/network');
this.menupath.set('Inventory', '/Adigem/config/inventory');
this.menupath.set('External port', '/Adigem/config/snmp/external-port');
this.menupath.set('Cloud Data', '/Adigem/config/clouddata');
this.menupath.set('Performance', '/Adigem/config/Performance');
this.menupath.set('CFG', '/Adigem/config/cfg');
this.menupath.set('System', '/Adigem/config/system');
console.log(this.menupath);
}
}
and the route binding looks like:
[routerLink]="menupath.get(item.resource)"
I wouldn't encourage the second solution, because you will have to handle the potential case where you receive an item, which is unknown for your menupath map.
Also I have a concern with the NO_ACCESS constant that you use in your template. There is no such property of the component, so this probably breaks the compilation.
I have an Angular 10 application, I am trying to remove the navside component from the login component, so I created a service on the nave side component contains this code :
visible: boolean;
constructor() {
this.visible = true;
}
show() {
this.visible = true;
}
hide() {
this.visible = false;
}
toggle() {
this.visible = !this.visible;
}
doSomethingElseUseful() { }
and inside the naveside component i put :
export class NavsideComponent implements OnInit {
constructor(public sr: ServService ) { }
ngOnInit(): void {
}
and the Html component :
<div *ngIf="sr.visible">
<mat-sidenav-container class="example-container">
<mat-sidenav #sidenav mode="push" class="app-sidenav" opened>
<mat-toolbar class="co">
<span class="toolbar-filler"></span>
.
.
.
.
.
. </div>
but this error was displayed :
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngIf: true'. Current value: 'ngIf: false'.
EDIT 1
Login.component.ts
import { Component, OnInit } from '#angular/core';
import { ServService } from '../../navside/serv.service';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
constructor( private sr :ServService) { }
ngOnInit(): void {
this.sr.hide();
this.sr.doSomethingElseUseful();
}
}
EDIT 2
Stackblitz putting together the snippets above, where the warning can be seen:
https://stackblitz.com/edit/angular-ivy-vbkytu?file=src/app/login/login.component.ts
I have edited your post to include a Stackblitz with a minimal reproducible example of the reported error. I suggest that in your next posts you include a minimal reproducible example, hence it´s more likelly you will have an answer.
About ExpressionChangedAfterItHasBeenCheckedError warning
This post has usefull informations about this warning: Expression ___ has changed after it was checked
In your specific case, the warning ExpressionChangedAfterItHasBeenCheckedError is shown because the value of sr.visible is changed twice during the initialization process:
once during the service SerService creation and
again in loginComponent.ngOnInit() when you call sr.hide();
In the same round of change detection a value that is binded in the view (sr.visible is binded in <div *ngIf="sr.visible">) is not supposed to change values more than once, that's the reason of the warning.
Solving your problem calling cdr.changeDetection() in the right component
You can solve the problem calling cdr.detectChanges() in order to fire a second round of change detections. But for that to work, you need to call it in the component that has the affected binding.
Calling cdr.detectChanges() on LoginComponent has no effect, since the binding of <div *ngIf="sr.visible"> is not in that component. You should call cdr.detectChanges() in the parent component where the binding is.
That said, the following use of cdr.detectChanges() in LoginComponent will NOT solve the problem:
export class LoginComponent {
constructor(private sr: ServService, private cdr: ChangeDetectorRef) {}
ngOnInit() {
this.sr.hide();
this.sr.doSomethingElseUseful();
//code bellow will NOT SOLVE the problem:
this.cdr.detectChanges();
}
}
What will solve the problem is invoking cdr.detectChanges() on the component that has the binding <div *ngIf="sr.visible">.
In my working stackblitz (at the end of this answer), that component is AppComponent:
app.component.html:
<div *ngIf="sr.visible">
Toolbar here
<!-- your mat-side-nav and mat-toolbar here -->
</div>
<app-login></app-login>
So, the cdr.detectChanges() is called in the ngOnInit() of app.component.ts:
export class AppComponent {
constructor(public sr: ServService,
private cdr: ChangeDetectorRef) {}
ngOnInit() {
this.cdr.detectChanges();
}
}
Working version - stackblitz
The working version calling detectChanges in the right component is available in the following Stackblitz:
https://stackblitz.com/edit/angular-ivy-nbb7bz?file=src/app/app.component.ts
This shall solve it:
import { ChangeDetectorRef } from '#angular/core';
constructor(private changeDetector: ChangeDetectorRef) {}
ngAfterViewChecked() {
this.changeDetector.detectChanges();
}
Ok I am still a newbie. I have successfully created a 'dashboard' component that has a left sidebar with links. On the right is where I have content/components displayed that I want to change dynamically depending on what link was clicked on the left sidebar (see bootstrap sample of what this dashboard looks like here, click on the toggle button to view the sidebar: https://blackrockdigital.github.io/startbootstrap-simple-sidebar/).
I have created a DashboardService that has a Subject and an Observable to allow for sibling component communication. This works great since I have a console.log() that shows this communication working (when I click on link on sidebar in SidebarComponent, I console.log() a value 'emitted' by the DashboardService that is being listened to by the SidebarComponent's sibling, DashboardSectionComponent).
The problem that I am having is that the template in DashboardSectionComponent loads the correct component section ONLY on initial load of page - once I click on a link on the side bar the content is blank and nothing is rendered.
Here is the service that allows the componenent communication:
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class DashboardService {
private selectedComponentAlias = new Subject<string>();
constructor() {}
setSelectedComponentAlias(alias: string) {
this.selectedComponentAlias.next(alias);
}
getSelectedComponentAlias(): Observable<string> {
return this.selectedComponentAlias.asObservable();
}
}
Here is the SidebarComponent:
import { Component, OnInit } from '#angular/core';
import { DashboardService } from '../dashboard.service';
#Component({
selector: 'app-sidebar',
templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.css']
})
export class SidebarComponent implements OnInit {
constructor(private dashboardService: DashboardService) { }
ngOnInit() {
}
onShowSection(event) {
event.preventDefault();
const componentAlias = event.target.getAttribute('data-componentAlias');
this.dashboardService.setSelectedComponentAlias(componentAlias);
}
}
here is the DashboardSectionComponent (the one that subscribes to the observable and I want to set property that controls the template views depending on the value that was caught)
import { Component, OnInit, OnDestroy } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
import { DashboardService } from '../dashboard.service';
#Component({
selector: 'app-dashboard-section',
templateUrl: './dashboard-section.component.html',
styleUrls: ['./dashboard-section.component.css']
})
export class DashboardSectionComponent implements OnInit, OnDestroy {
private subscrition: Subscription;
selectedComponentAlias: string = 'user-profile';
constructor(private dashboardService: DashboardService) {
}
ngOnInit() {
this.subscrition = this.dashboardService.getSelectedComponentAlias()
.subscribe((selectedComponentAlias: string) => {
this.selectedComponentAlias = selectedComponentAlias;
console.log('user clicked: ',this.selectedComponentAlias);
});
}
ngOnDestroy() {
this.subscrition.unsubscribe();
}
}
Finally here is the template for DashboardSectionComponent which might have wrong syntax:
<div *ngIf="selectedComponentAlias == 'my-cards'">
<app-cards></app-cards>
</div>
<div *ngIf="selectedComponentAlias == 'user-profile'">
<app-user-profile></app-user-profile>
</div>
<div *ngIf="selectedComponentAlias == 'user-settings'">
<app-user-settings></app-user-settings>
</div>
Again, this works great (selectedComponentAlias is 'user-profile' on page load by default). But it goes blank after I click on a Sidebar link....
Thanks.
this was easy - like #RandyCasburn pointed out, this was a matter of getting the routing working properly.
I have created dynamic component instances by selecting pre-existing components. For example,
#Component({
selector: 'dynamic-component',
template: `<div #container><ng-content></ng-content></div>`
})
export class DynamicComponent {
#ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef;
public addComponent(ngItem: Type<WidgetComponent>,selectedPlugin:Plugin): WidgetComponent {
let factory = this.compFactoryResolver.resolveComponentFactory(ngItem);
const ref = this.container.createComponent(factory);
const newItem: WidgetComponent = ref.instance;
newItem.pluginId = Math.random() + '';
newItem.plugin = selectedPlugin;
this._elements.push(newItem);
return newItem;
}
}
My pre-existed components are ChartWidget and PatientWidget which extended the class WidgetComponent that I wanted to add in the container. For example,
#Component({
selector: 'chart-widget',
templateUrl: 'chart-widget.component.html',
providers: [{provide: WidgetComponent, useExisting: forwardRef(() => ChartWidget) }]
})
export class ChartWidget extends WidgetComponent implements OnInit {
constructor(ngEl: ElementRef, renderer: Renderer) {
super(ngEl, renderer);
}
ngOnInit() {}
close(){
console.log('close');
}
refresh(){
console.log('refresh');
}
...
}
chart-widget.compoment.html (using primeng Panel)
<p-panel [style]="{'margin-bottom':'20px'}">
<p-header>
<div class="ui-helper-clearfix">
<span class="ui-panel-title" style="font-size:14px;display:inline-block;margin-top:2px">Chart Widget</span>
<div class="ui-toolbar-group-right">
<button pButton type="button" icon="fa-window-minimize" (click)="minimize()"</button>
<button pButton type="button" icon="fa-refresh" (click)="refresh()"></button>
<button pButton type="button" icon="fa-expand" (click)="expand()" ></button>
<button pButton type="button" (click)="close()" icon="fa-window-close"></button>
</div>
</div>
</p-header>
some data
</p-panel>
data-widget.compoment.html (same as chart-widget using primeng Panel)
#Component({
selector: 'data-widget',
templateUrl: 'data-widget.component.html',
providers: [{provide: WidgetComponent, useExisting: forwardRef(() =>DataWidget) }]
})
export class DataWidget extends WidgetComponent implements OnInit {
constructor(ngEl: ElementRef, renderer: Renderer) {
super(ngEl, renderer);
}
ngOnInit() {}
close(){
console.log('close');
}
refresh(){
console.log('refresh');
}
...
}
WidgetComponent.ts
#Component({
selector: 'widget',
template: '<ng-content></ng-content>'
})
export class WidgetComponent{
}
Now I added the components by selecting a component from the existed components (e.g. chart-widget and data-widget) in the following way and stored the instances into an array.
#Component({
templateUrl: 'main.component.html',
entryComponents: [ChartWidget, DataWidget],
})
export class MainComponent implements OnInit {
private elements: Array<WidgetComponent>=[];
private WidgetClasses = {
'ChartWidget': ChartWidget,
'DataWidget': DataWidget
}
#ViewChild(DynamicComponent) dynamicComponent: DynamicComponent;
addComponent(): void{
let ref= this.dynamicComponent.addComponent(this.WidgetClasses[this.selectedComponent], this.selectedComponent);
this.elements.push(ref);
this.dynamicComponent.resetContainer();
}
}
Now, I am facing problem to render the components using innerHtml in main.component.html. It render the html but I am not able to use button click event or other event on it. I have also tried to render chart using primeng but its also not working.
main.component.html
<dynamic-component [hidden]="true" ></dynamic-component>
<widget *ngFor="let item of elements">
<div [innerHTML]="item._ngEl.nativeElement.innerHTML | sanitizeHtml">
</div>
</widget>
I have also implemented a sanitizeHtml Pipe but its giving still same result. So, as I understand innerHTML is only showing the html data but I can't use any button event as well as the js chart. I have also tried to show the items like this {{item}} under tag. But it display like a text [object object]. So, could anyone give a solution for it? How can I render the components allowing the button events and js chart? Thanks.
EDIT: See my Plunker here https://plnkr.co/edit/lugU2pPsSBd3XhPHiUP1?p=preview
You can see here, it is possible to add chart or data widget dynamically and I am showing it using innerHTML. So, the button events are not working here. If I coding like {{item}} then it shows [object object] text. You can also see in console the component array data. The main Question is, How can I active the button events on it (e.g. if i click close or refresh button then it will call the related functions)?
I would create structural directive like:
view.directive.ts
import { ViewRef, Directive, Input, ViewContainerRef } from '#angular/core';
#Directive({
selector: '[view]'
})
export class ViewDirective {
constructor(private vcRef: ViewContainerRef) {}
#Input()
set view(view: ViewRef) {
this.vcRef.clear();
this.vcRef.insert(view);
}
ngOnDestroy() {
this.vcRef.clear()
}
}
then
app.component.ts
private elements: Array<{ view: ViewRef, component: WidgetComponent}> = [];
...
addComponent(widget: string ): void{
let component = this.dynamicComponent.addComponent(this.WidgetClasses[widget]);
let view: ViewRef = this.dynamicComponent.container.detach(0);
this.elements.push({view,component});
this.dynamicComponent.resetContainer();
}
and
app.component.html
<widget *ngFor="let item of elements">
<ng-container *view="item.view"></ng-container>
</widget>
So i have just moved view from dynamic component container to desired place.
Plunker Example
I am trying to abstract out a tabular-data display to make it a child component that can be loaded into various parent components. I'm doing this to make the overall app "dryer". Before I was using an observable to subscribe to a service and make API calls and then printing directly to each component view (each of which had the tabular layout). Now I want to make the tabular data area a child component, and just bind the results of the observable for each of the parent components. For whatever reason, this is not working as expected.
Here is what I have in the parent component view:
<div class="page-view">
<div class="page-view-left">
<admin-left-panel></admin-left-panel>
</div>
<div class="page-view-right">
<div class="page-content">
<admin-tabs></admin-tabs>
<table-display [records]="records"></table-display>
</div>
</div>
</div>
And the component file looks like this:
import { API } from './../../../data/api.service';
import { AccountService } from './../../../data/account.service';
import { Component, OnInit, Input } from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { TableDisplayComponent } from './../table-display/table-display.component';
#Component({
selector: 'account-comp',
templateUrl: 'app/views/account/account.component.html',
styleUrls: ['app/styles/app.styles.css']
})
export class AccountComponent extends TabPage implements OnInit {
private section: string;
records = [];
errorMsg: string;
constructor(private accountService: AccountService,
router: Router,
route: ActivatedRoute) {
}
ngOnInit() {
this.accountService.getAccount()
.subscribe(resRecordsData => this.records = resRecordsData,
responseRecordsError => this.errorMsg = responseRecordsError);
}
}
Then, in the child component (the one that contains the table-display view), I am including an #Input() for "records" - which is what the result of my observable is assigned to in the parent component. So in the child (table-display) component, I have this:
import { AccountService } from './../../../data/account.service';
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'table-display',
templateUrl: './table-display.component.html',
styleUrls: ['./table-display.component.less']
})
export class TableDisplayComponent {
#Input() records;
constructor() {
}
}
Lastly, here's some of the relevant code from my table-display view:
<tr *ngFor="let record of records; let i = index;">
<td>{{record.name.first}} {{record.name.last}}</td>
<td>{{record.startDate | date:"MM/dd/yy"}}</td>
<td><a class="bluelink" [routerLink]="['/client', record._id ]">{{record.name.first}} {{record.name.last}}</a></td>
When I use it with this configuration, I get "undefined" errors for the "records" properties I'm pulling in via the API/database. I wasn't getting these errors when I had both the table display and the service call within the same component. So all I've done here is abstract out the table-display so I can use it nested within several parent components, rather than having that same table-display show up in full in every parent component that needs it.
What am I missing here? What looks wrong in this configuration?
You need to protect against record being null until it comes in to your child component (and therefore it's view).
Use Elvis operators to protect your template:
<tr *ngFor="let record of records; let i = index;">
<td>{{record?.name?.first}} {{record?.name?.last}}</td>
<td>{{record?.startDate | date:"MM/dd/yy"}}</td>
<td><a class="bluelink" [routerLink]="['/client', record?._id ]"> {{record?.name?.first}} {{record?.name?.last}}</a></td>
You can also assign your input to an empty array to help with this issue:
#Input() records = [];