I have a form with some input fields.
When I submit the form I want to add a row to my table dataSource with the new data.
I have a component for the form that looks like that:
FORM HTML
<form (submit)="submitForm($event)">
<app-form-element align="center" *ngFor="let el of fields| keyobject" [value]="el.value.value" [type]="el.value.type">
</app-form-element>
<button>Save User</button>
</form>
FORM TS
#Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.css'],
})
export class FormComponent implements OnInit {
fields!: object;
constructor() { }
ngOnInit(): void {
this.newForm();
}
newForm() {
this.fields = [{ value: "Name", type: "text" },
{ value: "Surname", type: "text" },
{ value: "Email", type: "email" }];
}
tbc = new TableComponent;
submitForm(event: any) {
let newUser = new User();
newUser.name = event.target.Name.value;
newUser.surname = event.target.Surname.value;
newUser.email = event.target.Email.value;
this.tbc.addValue(newUser);
event.preventDefault();
}
}
export class User {
name!: string;
surname!: string;
email!: string;
}
TABLE HTML
<table *ngIf="show">
<tr>
<th *ngFor="let column of headers">
{{column}}
</th>
<th>Commands</th>
</tr>
<tr *ngFor="let row of dataSource | keyobject; let i = index">
<td *ngFor="let col of headers">
{{row.value[col]}}
</td>
<td>
<button class="btn btn-default" type="button" (click)="deleteValue(i)">Delete</button>
</tr>
</table>
TABLE TS
export class TableComponent implements OnInit {
headers = ['name', 'surname', 'email'];
dataSource: any = [
{ id: 1, name: "test", surname: 'test', email: "test#gmail.com"},
];
ngOnInit(): void {
}
addValue(user: User) {
let id = this.dataSource.length + 1;
this.dataSource = [...this.dataSource, { id: id, name: user.name, surname: user.surname, email: user.email, save: false }];
this.reload();
}
deleteValue(id: any) {
this.dataSource.splice(id, 1);
this.reload();
}
public show = true;
reload() {
this.show = false;
setTimeout(() => this.show = true);
}
}
When I call the addValue function in the Form.ts it works but the dataSource doesn't get updated.
Debugging the code everything works and it looks like the record is being added to the dataSource but the table dataSource doesn't actually have the new record so it doesn't get displayed.
Notice that my deleteValue is working fine and is deleting the row from the dable and from the dataSource
I'm new to angular so any help is appreciated
I think the problem is that the changes on dataSource array from your child component are not automatically detected on push. You can force the change detection using detectChanges from ChangeDetectorRef :
#Component({
...
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TableComponent implements OnInit {
contructor(
private cdr: ChangeDetectorRef
) {}
dataSource: any = [
{ id: 1, name: "test", surname: 'test', email: "test#gmail.com"},
];
addValue(user: User) {
let id = this.dataSource.length + 1;
this.dataSource.push({ id: id, name: user.name, surname: user.surname, email: user.email});
this.reload();
}
public show = true;
reload() {
// here you can force the change detection
this.cdr.detectChanges();
...
}
}
Even though, the other solution of the changeDetector might work, it's not the best approach to tell angular to refresh. It's better to instead, just put your code in a way that angular will notice that needs to change.
I believe that in angular, a push into the array, it's not detected as a new change.
I think instead of doing the push, you could do the following:
this.dataSource = [...this.dataSource, { id: id, name: user.name, surname: user.surname, email: user.email}]
Basically, you would create a new array that contains the old array + the new data.
Anyways, to be sure this is a correct answer, could you provide the actual code you have, not a small cut of it, with both TS and HTML Templates ?
Related
I have a reusable child table component that loads dynamically based on data from the parent component.
For the first time everything loads well, however when I need to click on one of the sort columns in the table I need to send that sort property again to the parent component to return the result from api and refresh the data in the child component with a new set of data from api.
In the code it looks like this, I missed something in that refresh:
table.component.ts
export class TableComponent implements OnChanges {
#Input() items;
#Input() headers;
#Input('sortColumn') sortColumn;
#Output() sortColumnChange = new EventEmitter<string>();
ngOnChanges(changes: SimpleChanges) {
this.items;
console.log('OnChanges', changes);
}
onSortClick(event, selectedColumn) {
const target = event.currentTarget,
classList = target.classList;
let column = '';
if (classList.contains('sort-icon-asc')) {
classList.remove('sort-icon-asc');
classList.add('sort-icon-desc');
column = `${selectedColumn} DESC`;
this.sortColumn = column;
this.sortColumnChange.emit(column);
} else {
classList.add('sort-icon-asc');
classList.remove('sort-icon-desc');
column = `${selectedColumn} ASC`;
this.sortColumn = column;
this.sortColumnChange.emit(column);
}
}
}
table.component.html
<table>
<thead>
<td (click)="onSortClick($event, header.value)" *ngFor="let header of headers" class="sort-icon-asc">{{ header.name }}</td>
</thead>
<tbody>
<tr *ngFor="let item of items">
<td *ngFor="let value of item | objectValues">
{{ value }}
</td>
</tr>
</tbody>
</table>
users.component.ts
export class UsersComponent implements OnInit {
observablesDispose$: Subject<void> = new Subject();
sortColumn = 'userId ASC';
items: [];
usersTableHeaders = [
{
value: 'userId',
name: this.translateService.instant('ADMIN.USERS_TABLE.USER_ID')
},
{
value: 'name',
name: this.translateService.instant('ADMIN.USERS_TABLE.NAME')
},
{
value: 'role',
name: this.translateService.instant('ADMIN.USERS_TABLE.ROLE')
},
{
value: 'email',
name: this.translateService.instant('ADMIN.USERS_TABLE.EMAIL')
},
{
value: 'status',
name: this.translateService.instant('ADMIN.USERS_TABLE.STATUS')
}
];
constructor(
private readonly usersService: UsersService,
private readonly translateService: TranslateService
) {}
ngOnInit(): void {
this.getUsers();
}
getUsers(): void {
this.usersService
.getUsers(this.sortColumn)
.pipe(takeUntil(this.observablesDispose$))
.subscribe((users) => {
this.items = users.resultList.map((tableColumn) => ({
userId: tableColumn.userId,
name: tableColumn.displayName,
role: tableColumn.role,
email: tableColumn.email,
status: tableColumn.status
}));
});
}
ngOnDestroy(): void {
this.observablesDispose$.next();
this.observablesDispose$.complete();
}
}
users.component.html
<div class="row">
<div class="table-section">
<app-table
[headers]="usersTableHeaders"
[items]="items"
[(sortColumn)]="sortColumn">
</app-table>
</div>
</div>
EDIT
users.component.ts
export class UsersComponent implements OnInit {
observablesDispose$: Subject<void> = new Subject();
sortColumn = 'userId ASC';
items: [];
usersTableHeaders = [
{
value: 'userId',
name: this.translateService.instant('ADMIN.USERS_TABLE.USER_ID')
},
{
value: 'name',
name: this.translateService.instant('ADMIN.USERS_TABLE.NAME')
},
{
value: 'role',
name: this.translateService.instant('ADMIN.USERS_TABLE.ROLE')
},
{
value: 'email',
name: this.translateService.instant('ADMIN.USERS_TABLE.EMAIL')
},
{
value: 'status',
name: this.translateService.instant('ADMIN.USERS_TABLE.STATUS')
}
];
constructor(
private readonly usersService: UsersService,
private readonly translateService: TranslateService
) {}
ngOnInit(): void {
this.getUsers();
}
getUsers(): void {
this.usersService
.getUsers(this.sortColumn)
.pipe(takeUntil(this.observablesDispose$))
.subscribe((users) => {
this.items = users.resultList.map((tableColumn) => ({
userId: tableColumn.userId,
name: tableColumn.displayName,
role: tableColumn.role,
email: tableColumn.email,
status: tableColumn.status
}));
});
}
updateSort(newColumn: string): void {
this.sortColumn = newColumn;
this.getUsers();
}
ngOnDestroy(): void {
this.observablesDispose$.next();
this.observablesDispose$.complete();
}
}
users.component.html
<div class="row">
<div class="table-section">
<app-table
[headers]="usersTableHeaders"
[items]="items"
[(sortColumn)]="sortColumn"
(sortColumnChange)="updateSort($event)"
>
</app-table>
</div>
</div>
In your way of handle sortColumn change you cannot control change of it to update users list. In your component you should do like:
users.component.html
<div class="row">
<div class="table-section">
<app-table
[headers]="usersTableHeaders"
[items]="items"
[sortColumn]="sortColumn"
(sortColumnChange)="updateSort($event)">
</app-table>
</div>
</div>
users.component.ts
...
updateSort(newColumn: string): void {
this.sortColumn = newColumn;
getUsers();
}
I am new to Angular and I am facing issue in rendering data in UI from an api call. I want to show the data received as response in the parent and show it in a component called webex-uptime-chart.
The file with API call is as shown below:
public uptimeChartConfig: Array<{ [key: string]: string | any }>;
this.uptimeChartConfig = [
{
rpcMethod: 'getNodeStatus',
node: this.NodeId,
duration: '10 mins'
},
];
// API call to get the Uptime Chart data
this.uptimeChartConfig
.filter(config => config.rpcMethod)
.map(config => {
return this.rpcService
.invoke({
method: 'getNodeStatus',
args: ['2d945891-be9b-46a8-973e-3f343a8999ad'],
})
.then((data: any) => {
if (data && data.response) {
const labels: Array<string> = data.response.map(value =>
this.datePipe.transform(value.epochtime * 1000, 'shortTime')
);
const nodeList = {};
data.response.forEach(node => {
if (nodeList[node.nodeId]) {
nodeList[node.nodeId] = [...nodeList[node.nodeId], node.uptime];
} else {
nodeList[node.nodeId] = [node.uptime];
}
});
this.lineChartData[config.rpcMethod] = {
labels: labels,
dataSets: nodeList,
};
} else {
this.lineChartData[config.rpcMethod] = {
lables: [],
dataSets: [],
};
}
});
The response looks as shown below:
The parent component's html where the webex-uptime-chart is called looks as shown below:
<webex-uptime-chart
*ngFor="let config of uptimeChartConfig"
[config]="config"
[incomingData]="lineChartData[config.rpcMethod]">
</webex-uptime-chart>
The webex-uptime-chart.ts component file is:
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'webex-uptime-chart',
templateUrl: './uptime-chart.component.html',
styleUrls: ['./uptime-chart.component.scss']
})
export class UptimeChartComponent implements OnInit {
#Input() chartData: any[];
#Input() public config;
#Input() public incomingData: any;
public labels: Array<string> = [];
public dataSets: any = {};
constructor() { }
ngOnInit() {
this.labels = this.incomingData.labels;
this.dataSets = this.incomingData.dataSets;
}
}
The webex-uptime-chart.html file is:
<div class="uptime-container">
<ul *ngFor="let data of chartData">
<li [ngClass]="data.status === 'down' ? 'my-class red-circle' : 'my-class green-circle '">
<span>{{ config.node }}</span>
</li>
<p class="right-text">{{ config.duration }}</p>
<hr />
</ul>
</div>
I get the below error while trying to run :
I don't know how to proceed.
incomingData is asynchronous. As a result it is initially provided as undefined to the child component until the promise then callback was executed. But this change is not registered within child component, since you only read incomingData within ngOnInit.
You could use ngOnChanges instead of ngOnInit.
ngOnChanges(changes: SimpleChanges) {
if (changes['incomingData'] && !!changes['incomingData'].previousValue) {
this.labels = changes['incomingData'].currentValue.labels;
this.dataSets = changes['incomingData'].currentValue.dataSets;
}
}
Apologies for not being able to title my question properly.
Let me explain my issue properly.
I have 2 Components say A and B.
In B I have a function saveIndCustData which emits and saves data.
export class CustomerformComponent implements OnInit {
#Output()
savedIndCustomer: EventEmitter<any> = new EventEmitter<any>();
saveIndCustData() {
const savedIndCustomer = {
prefix: this.prefix,
nameType: this.indCustNameType,
firstName: this.firstName,
middleNAme: this.middleName,
lastName: this.lastName,
gender: this.gender,
dateOfBirth: this.parseDate(this.dateOfBirth.toString()),
citizenship: this.citizenship
};
this.savedIndCustomer.emit(savedIndCustomer);
this.snackbar.open('Customer Info Saved,Click on Next', 'Close', {
duration: 5000
});
}
}
I am now calling the function from component A.
import { CustomerformComponent } from './forms/customerform/customerform.component';
constructor(private custComp: CustomerformComponent) {}
saveCustomerForm(): void {
this.custComp.saveIndCustData();
}
I emit the data into a service class
#Output()
savedIndCustomer: EventEmitter<any> = new EventEmitter<any>();
Service Class
public addDynamiIndCustomerComponent() {
const factory = this.factoryResolver.resolveComponentFactory(CustomerformComponent);
const component = factory.create(this.rootViewContainer.parentInjector);
component.instance.savedIndCustomer.subscribe(data => {
console.log(data);
// Insert Individual Customer Type
this.custFullDetails.customerType = 'individual';
this.custFullDetails.individualCustomer.dateOfBirth = data.dateOfBirth;
this.custFullDetails.individualCustomer.citizenship = data.citizenship;
this.custFullDetails.individualCustomer.gender = data.gender;
this.custFullDetails.individualCustomer.individualName.push({
prefix: data.prefix,
firstName: data.firstName,
middleName: data.middleName,
lastName: data.lastName,
agreementId: data.agreementId,
nameType: data.nameType
});
console.log(this.custFullDetails.individualCustomer);
});
this.rootViewContainer.insert(component.hostView);
}
My issue is if I invoke the saveIndCustData function from component B it pushes data into array const savedIndCustomer{ ... } and calls the service class.
However when I invoke the same function from component A it doesn't invoke the const savedIndCustomer{ ... } method inside saveIndCustData() function and service class method does not save data in array but it simply shows the snakbar.
What is the issue?
Suppose you put the component B inside the html of component A, so you should make a reference for the component B like this
A.component.html:
...
<B #bcmp></B>
...
and inject it in A.component.ts using #ViewChild like this
A.component.ts:
#Component({
selector: 'A',
templateUrl: './A.component.html',
styleUrls: ['./A.component.scss']
})
export class AComponent implements OnInit {
#ViewChild("bcmp") bcmp : B
ngOnInit(): void {
// by this way you can use any method existant in B component
this.bcmp.saveIndCustData();
}
}
I'm using Spring Boot, Angular CLI and mySQL.
I have an Employee than can have one Marital Status, and one M Status can be in N Employees.
In localHost:8080 I get the right array json:
[{"id":1,"firstName":"Name","lastName":"surname1","emailAddress":"test#test1.com","status":{"statusId":1,"nameStatus":"Single"}
In my angular table(localHost:4200), instead I get every data but in Status column I get "[object Object]".
In have a service for each one.
When I do a registration I have a dropDown w/ all status so I get them.
This is my HTML table:
<table class="table table-bordered">
<thead>
<tr>
<th *ngFor="let col of columns">{{col}}
</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let employee of employees | paginate: {itemsPerPage: pageSize,
currentPage: page,
totalItems: employees.length} | filterAll: searchString : field">
<td *ngFor="let col of columns">{{employee[col]}}</td>
<td>
<button [ngClass]="getClassCondition(act.actionType)" *ngFor="let act of actions"
(click)="actionFunc(act, employee)">{{act.label}}</button>
</td>
</tr>
</tbody>
</table>
Here I have an ngFor that gets All employees.
Now I share also my services and my componets.ts:
status.service.ts:
#Injectable({
providedIn: 'root'
})
export class StatusService {
private baseUrl = 'http://localhost:8080/api';
constructor(
private http: HttpClient) { }
getStatus(): Observable<Status[]> {
return this.http.get<Status[]>(`${this.baseUrl}` + '/status');
}
}
employee.service.ts
const httpOptions = {
headers: new HttpHeaders({'Content-Type': 'application/json'})
};
#Injectable({
providedIn: 'root'
})
export class EmployeeService {
columns = COLUMNS;
actions = ACTIONS;
private baseUrl = 'http://localhost:8080/api/employees';
constructor(private http: HttpClient) {
}
getColumns() {
return this.columns;
}
getActions() {
return this.actions;
}
getMetadata() {
return this.metadata;
}
/** GET Employees from the server */
getEmployees(): Observable<Employee[]> {
return this.http.get<Employee[]>(this.baseUrl);
}
getEmployee(id: number): Observable<Employee> {
const url = this.baseUrl + '/' + id;
return this.http.get<Employee>(url);
}
/** PUT: update the employee on the server */
updateEmployee(employee: Employee): Observable<any> {
return this.http.put(`${this.baseUrl}/${employee.id}`, employee, httpOptions);
}
deleteEmployee(id: number): Observable<any> {
return this.http.delete(`${this.baseUrl}/${id}`, {responseType: 'text'});
}
}
Here I have also a const w/ COLUMNS name.
employee.ts
export const COLUMNS = ['id', 'firstName', 'lastName', 'emailAddress', 'status'];
export class Employee {
id: number;
firstName: string;
lastName: string;
emailAddress: string;
status: string;
}
status.ts
export class Status {
statusId: number;
nameStatus: string;
}
What do I have to do to get my status.Name?
Is there something specific?
If you need more documentation ask me.
I suspect that col return status and employee[col] return {"statusId":1,"nameStatus":"Single"}.
So the error seems correct.
You can do a workaround here if you just want to display the nameStatus :
<td *ngFor="let col of columns">{{employee[col]?.nameStatus ? employee[col]?.nameStatus : employee[col]}}</td>
You have to be more specific, how you display the data or map the data to the structure you want
1) instead of <td *ngFor="let col of columns">{{employee[col]}}</td>
you can do
<td>{{employee.firstName}}</td>
<td>{{employee.lastName}}</td>
...
<td>{{employee.status.nameStatus}}</td>
2) before binding the data, map it to the structure you want (in your controller/*.ts file).
Take a look at Array map( for that.
Your code doesn't seem to work because the controller never assigns the employee method
I have to modify the following code with an implementation of an Array of actions (bottom page).
I saw lots of websites by I wasn't able to find something than can be used for my code.
I will have to change my html , my tableService, my component.ts and oviously my actionConfiguration.
At the moment this is my HTML:
<div class="container">
<table class="table">
<tr>
<th *ngFor="let col of columns" (click)="sortTable(col)">{{col}}</th>
<th>Actions</th>
</tr>
<tr *ngFor="let user of users | paginate: {itemsPerPage: 5,
currentPage: page,
totalItems: users.length } ; let i = index">
<td *ngFor="let col of columns">{{user[col]}}</td>
<td>
<button [ngClass]="getClassCondition(act)" *ngFor="let act of actions" (click)="actionFunc(act,i)">{{act}}</button>
</td>
</tr>
</table>
</div>
<div>
<pagination-controls (pageChange)="page = $event"></pagination-controls>
</div>
This is my component.ts:
#Component({
selector: 'app-dynamic-table',
templateUrl: './dynamic-table.component.html',
styleUrls: ['./dynamic-table.component.css']
})
export class DynamicTableComponent implements OnInit {
#Input()
users = [];
#Input()
columns: string[];
#Input()
actions: string[];
#Input()
class;
direction = false;
page: any;
constructor() {
}
sortTable(param) {
/*done*/
}
actionFunc(i, index) {
if (i === 'deleteUser') {
if (confirm('Are you sure you want to delete this item?') === true) {
this.users.splice(index, 1);
}
}
if (i === 'editUser') {
/*...*/
}
}
getClassCondition(act) {
return act === 'deleteUser' ? this.class = 'btn btn-danger' : 'btn btn-primary' ;
}
ngOnInit(): void {
}
}
This is my tableService.ts
import { USERS } from './mock-data';
#Injectable()
export class TableService {
constructor() { }
static getUsers(): Observable<any[]> {
return Observable.of(USERS).delay(100);
}
static getColumns(): string[] {
return ['id', 'firstName', 'lastName', 'age'];
}
static getActions(): string[] {
return ['deleteUser', 'editUser'];
}
}
Here's the new Task, I have to create an Array of Actions so I will be able to use it in different components but I have no idea how to do it.
I have to start from something like this, it's just an example (not complete because I don't know what to insert exactly):
actionConfig.ts
export const ACTIONS = [
{
label: 'Remove',
actionType: 'deleteUser',
},
{
label: 'Edit',
actionType: 'editUser',
},
];
A sample of Enum and a table to show data on iterating on them:
StackBlitz
You also might want to read typescript-enums-explained
Basically, the TypeScript enums are compiled to something as shown below for reverse lookup. Thats why I have added the foreach loop in constructor and created another list.
export enum Fruits {
APPLE = 'Apple',
MANGO = 'Mango',
BANANA = 'Banana',
}
is compiled to
var Fruit;
(function (Fruit) {
Fruit[Fruit["APPLE"] = 'Apple'] = "APPLE";
Fruit[Fruit["MANGO"] = 'Mango'] = "MANGO";
Fruit[Fruit["BANANA"] = 'Banana'] = "BANANA";
})(Fruit || (Fruit = {}));
UPDATE
HTML
<button [ngClass]="getClassCondition(act.actionType)" *ngFor="let act of actions"
(click)="actionFunc(act, user)">{{act.label}}</button>
COMPONENTS.TS
actionFunc(action, element: any) {
if (action.actionType === 'DELETE') {
if (confirm('Are you sure you want to delete this item?') === true) {
/*...*/
}
}
if (action.actionType === 'GO_TO') {
/*...*/
}
}
actionsConfig.ts
export const ACTIONS = [
{
label: 'Delete',
actionType: 'DELETE',
deleteApi: 'api/USERS'
},
{
label: 'Edit',
actionType: 'GO_TO',
getUrl: row => '/detail/' + row.id,
},
];