I'm struggling with the Angular mat-table library. I got an app with a Node.js backend and an Angular frontend. The node app provides data from a MySQL database in JSON.
Now I want to display this data in a mat-table. I have logged the data in the console, which allows me to see that the data is actually retrieved but just not displayed.
However, the HTML table is empty:
This is my Angular component:
component.html
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8 demo-table">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef>ID</th>
<td mat-cell *matCellDef="let element">{{element.id}}</td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Name</th>
<td mat-cell *matCellDef="let element">{{element.name}}</td>
</ng-container>
<ng-container matColumnDef="pop">
<th mat-header-cell *matHeaderCellDef>Population</th>
<td mat-cell *matCellDef="let element">{{element.population}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr
mat-row
(click)="clickedRows.add(row)"
[class.demo-row-is-clicked]="clickedRows.has(row)"
*matRowDef="let row; columns: displayedColumns;"
></tr>
</table>
component.ts
import {Component, OnInit,ViewChild} from '#angular/core';
import { Player } from '../player';
import { PlayerService } from '../player.service';
import { MatTableDataSource } from '#angular/material/table';
import {MatPaginator} from '#angular/material/paginator';
import {MatSort, SortDirection} from '#angular/material/sort';
/**
* #title Binding event handlers and properties to the table rows.
*/
#Component({
selector: 'app-players',
styleUrls: ['players.component.css'],
templateUrl: 'players.component.html',
})
export class PlayersComponent implements OnInit {
displayedColumns: string[] = ['id', 'name', 'pop'];
dataSource = new MatTableDataSource<Player>();
#ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
#ViewChild(MatSort, { static: true }) sort!: MatSort;
constructor(private playerService:PlayerService) { }
ngOnInit(): void {
this.getPlayers();
}
getPlayers() {
this.playerService.getPlayers().subscribe(players => {
console.log(players);
this.dataSource.data = players;
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
});
}
clickedRows = new Set<Player>();
}
player.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable, of } from 'rxjs';
import { Player } from './player';
#Injectable({
providedIn: 'root'
})
export class PlayerService {
constructor(private http: HttpClient) { }
rootURL = '/api';
getPlayers(): Observable<Player[]> {
return this.http.get<Player[]>(this.rootURL+ '/players');
}
}
Any ideas on this?
EDIT:
Could it have something to do with how the array comes back from the API? In Node.js it is retrieved with sequelize and maybe it is the response?
// Get all Players
exports.findAll = (req, res) => {
Player.findAll().then((players) => {
// Send all players as response
res.status(200).json({
status: true,
data: players,
});
});
};
Issue
The data returned based on the screenshot and Node.js API is not Player array, but it is an object with status and data properties.
{
"status": 200,
"data": [...]
}
This line expected that HTTP GET to receive Player array which is conflict with your data.
this.http.get<Player[]>(this.rootURL+ '/players');
Hence, it returns Observable with an empty array and your <mat-table> will not show the data.
Solution
Transform the data to Player array with map rxjs operator.
import { map } from 'rxjs';
getPlayers(): Observable<Player[]> {
return this.http
.get(this.rootURL+ '/players')
.pipe(map((response: any) => response.data as Player[]));
}
Sample Solution on StackBlitz
Create a instance of MatTableDataSource and assign to data Source.
Try this option
this.dataSource = new MatTableDataSource(players);
this.dataSource.sort = this.sort;
Ref: Using HTTP GET request to fetch data in an Angular Material Table
Related
I have a factory smart contract that deploys another smart contract.
In the factory contract I have this function
address[] public migrations;
...
function getMigrations() public view returns (address[] memory) {
return migrations;
}
after the migration is pushed in this array, I want to display a list of addresses on the first page but I really can't.
This is what I've tried:
import React from "react";
import web3 from "./web3";
import factory from "./factory";
import AccordionExampleStyled from "./components/Acordeon";
import { Card, Button } from "semantic-ui-react";
import 'semantic-ui-css/semantic.min.css'
class App extends React.Component {
static async getInitialProps() {
const migration = await factory.methods.getMigrations().call();
return { migration };
};
renderCampaigns() {
const items = this.props.migration.map((address) => {
return {
header: address,
description: (
<Button>See migration</Button>
),
fluid: true,
};
});
return <Card.Group items={items} />;
}
render() {
return (
<div>
<center>
<h2>MigrationHelper by DRIVENecosystem</h2>
<p>
<p><AccordionExampleStyled /></p>
<p>List of all migrations </p>
<p></p>
{this.renderCampaigns()}
</p>
</center>
</div>
);
}
};
export default App;
I got this error: Cannot read properties of undefined (reading 'map')
P.S. I am a newbie in React.js and web3
Edit: I also tried that option, but nothing
import React from "react";
import web3 from "./web3";
import factory from "./factory";
import AccordionExampleStyled from "./components/Acordeon";
import 'semantic-ui-css/semantic.min.css'
class App extends React.Component {
static async getInitialProps() {
const migrations = await factory.methods.getMigrations().call();
return { migrations };
}
renderMigrations(){
const items = this.props.migrations.map((migrations) => {return{migrations}});
return <li>{items}</li>;
}
render() {
return (
<div>
<center>
<h2>MigrationHelper by DRIVENecosystem</h2>
<p>
<p><AccordionExampleStyled /></p>
<p>List of all migrations </p>
<p>{this.props.renderMigrations}</p>
</p>
</center>
</div>
);
}
};
export default App;
The problem
migrations is not present on the first render. so mapping through it, cause the error.
The Solution
Just need to check the migrations data has arrived and has content before mapping it:
class App extends React.Component {
static async getInitialProps() {
const migrations = await factory.methods.getMigrations().call();
return { migrations };
}
const renderMigrations = () => {
return this.props.migrations.map((migration) => {
return(
<div>
<li>{migration.name}</li>)
<p>{migration.detail}</li>)
</div>
)
})
}
render() {
const items = this.props.migration;
return (
<div>
<center>
<h2>MigrationHelper by DRIVENecosystem</h2>
<p>
<p><AccordionExampleStyled /></p>
<p>List of all migrations </p>
<p>
{
items?.length > 0 && renderMigrations
}
</p>
</p>
</center>
</div>
);
}
};
export default App;
In the renderMigrations method, I used e sample to show the migration detail with li and p elements, since the migrations array does not exist in your question, I just take an example. You should change it with your requirement in the application.
I am using this wrapper for the azure maps library. I am currently implementing a symbol layer and using one of the default markers works well, but I am not able to add my own marker. I tried to add a custom marker like in my mapReady function, but the response is always undefined and the image is not added.
this is my component:
import {Component, Input, OnInit} from '#angular/core';
import * as atlas from 'azure-maps-control';
#Component({
selector: 'app-map',
templateUrl: './map.component.html',
styleUrls: ['./map.component.scss']
})
export class MapComponent implements OnInit {
private markerImagePath = 'assets/images/map-marker.png';
public dataSource: atlas.source.DataSource;
markerDescription: 'marker';
public options: atlas.IconOptions = {
image: this.markerDescription
};
points = [
[52.52437, 13.41053],
[51.50853, -0.12574]
];
ngOnInit() { }
mapReady(map: atlas.Map) {
map.imageSprite.add(this.markerDescription, this.markerImagePath).then(r => {
console.log(r);
console.log(map.imageSprite.getImageIds());
this.dataSource = new atlas.source.DataSource('markers');
this.points.forEach(p => {
const point = new atlas.Shape(new atlas.data.Point([p[1], p[0]]));
this.dataSource.add([point]);
});
});
}
}
this is my html:
<section>
<div class="row">
<div class="col-12 map-dimensions my-2 mx-auto" azure-map zoom="2"
[dataSources]="[dataSource]" (onReady)="mapReady($event.map)">
<map-symbol-layer dataSourceId="markers"
[iconOptions]="options"></map-symbol-layer>
</div>
</div>
</section>
I suspect, that I access the map data wrongly... Do any of you guys know, how I can add a custom image to the imageSprites in order for me to use it as a marker in the symbol layer?
Your code looks fine. imageSprite.add returns a Promise<void>, so your console.log will always log undefined. Could your icon be the issue ? I have been trying a similar solution and all works fine on my side :
import { Component } from '#angular/core';
import * as atlas from 'azure-maps-control';
#Component({
selector: 'app-root',
template: '<azure-map zoom="2" [dataSources]="[dataSource]" (onReady)="mapReady($event.map)">' +
'<map-symbol-layer [id]="blueLayerId" dataSourceId="blue" [iconOptions]="blueIconOptions"></map-symbol-layer>' +
'</azure-map>',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
public dataSource: atlas.source.DataSource;
public blueLayerId: string = "blueLayer";
public blueIconOptions: atlas.IconOptions = {
image: 'campground'
};
mapReady(map: atlas.Map) {
map.imageSprite.add('campground', 'assets/campground.png').then(() => {
this.dataSource = new atlas.source.DataSource('blue');
for (let i = 0; i < 10; i++) {
const point = new atlas.Shape(new atlas.data.Point([i * 5, i * 5]));
this.dataSource.add([point]);
}
});
}
}
I'm trying to do a crud with angular and node.
My rest API is completed (made with node and mysql);
Trying to display my JSON data at the HTML template, but I'm not being successful...
Thank you if you could help me :)
My service:
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { map } from "rxjs/operators";
import { Produto} from './produto';
import { catchError } from 'rxjs/operators';
import { Observable, of } from 'rxjs';
#Injectable()
export class ProductService {
constructor(private _http : Http){ }
getProdutos(): Observable<Produto[]>{
return this._http
.get("http://localhost:8080/produtos").pipe(
map(res => res.json()));
}
}
Html:
<tr>
<th>Product</th>
<th>Price</th>
<th>Description</th>
<th>Category</th>
<th>Actions</th>
</tr>
<!-- Use *ngFor directive to loop throught our list of products. -->
<tr *ngFor="let produto of produtos">
<td>{{produto.nome}}</td>
<td>{{produto.descricao}}</td>
<td>{{produto.valor}}</td>
<td>{{produto.src}}</td>
<td>
Component
import { Component, OnInit, Input, Output, EventEmitter } from
'#angular/core';
import { ProductService } from '../product.service';
import { Observable } from 'rxjs';
import { Produto } from '../produto';
#Component({
selector: 'app-read-products',
templateUrl: './read-products.component.html',
styleUrls: ['./read-products.component.css'],
providers: [ProductService]
})
export class ReadProductsComponent implements OnInit {
produtos: Produto[];
constructor(private productService: ProductService){}
ngOnInit(){
this.productService.getProdutos()
.subscribe(produtos =>
this.produtos=produtos['records']
);
}
}
Class:
export class Produto {
constructor(
public produto_id: number,
public nome: string,
public descricao: string,
public valor: number,
public src: string
){}
}
My json response (when goes to the link):
[{"produto_id":10,"nome":"caderno","descricao":"maycon","valor":23.2212,"src":"aasssaa"}]
Have more classes in my project, if someone thinks that the problem is in another, just tell...
OBS: CREATED WITH ANGULAR CLI AND FOLLOWING THIS TUTORIAL
If you get un-named array from backend then you should assign result directly to productos :
this.productService.getProdutos()
.subscribe(produtos =>
this.produtos=produtos
);
If you get your array data here in productos then your HTML ngFor loop will work correctly :
<tr *ngFor="let produto of produtos">
<td>{{produto.nome}}</td>
<td>{{produto.descricao}}</td>
<td>{{produto.valor}}</td>
<td>{{produto.src}}</td>
<td>
</tr>
You should return the changed response inside map function. Like this
return this._http
.get("http://localhost:8080/produtos").pipe(
map(res =>
return res.json()));
}
Do a console.log to check whether you are receiving the data and identify the structure of the data. For the json response you have mentioned , in your component just assign it directly to the array
this.produtos=produtos;
Hope this helps.
I am working on a recipe app. I'm using Yummly API I am getting a response however I am confused how to render the data I get back from the API because the response is a Object with an array of recipes. When I try to render the array I get this error:
Uncaught (in promise) Error: Objects are not valid as a React child (found: object with keys {imageUrlsBySize, sourceDisplayName, ingredients, id, smallImageUrls, recipeName, totalTimeInSeconds, attributes, flavors, rating}). If you meant to render a collection of children, use an array instead.
Link to an image of what the API response looks like:
Object from API
"Matches" is the part I want to render in my component
Action.js
import Axios from 'axios';
import {LOOK_UP_RECIPE`enter code here`} from './types';
const API_ID = '########';
const API_KEY = '######';
const ROOT_LOOK_UP_URL = `http://api.yummly.com/v1/api/recipes?
_app_id=${API_ID}&_app_key=${API_KEY}`
export function lookuprecipesYummly(ingredients) {
const yummlyurl =`${ROOT_LOOK_UP_URL}&q=${ingredients}`;
const request = Axios.get(yummlyurl);
return {
type: LOOK_UP_RECIPE,
payload: request
};
}
Reducer.js
import { LOOK_UP_RECIPE } from '../actions/types'
export default function(state = [], action) {
console.log(action)
switch (action.type){
case LOOK_UP_RECIPE:
return [ action.payload.data, ...state ];
default:
return state;
}
}
Component:
import _ from "lodash";
import React, {Component} from 'react';
import { connect } from 'react-redux';
class RecipeList extends Component {
renderRecipe(recipeData) {
return (
<tr key={0}>
<td key={1}>{recipeData.matches}</td>
</tr>
)
}
render() {
return(
<table>
<thead>
<tr key={1}>
<th>Recipe</th>
</tr>
</thead>
<tbody>
{this.props.recipes.map(this.renderRecipe)}
</tbody>
</table>
)
}
}
function mapStateToProps({recipes}) {
return {
recipes
}
};
export default connect(mapStateToProps)(RecipeList);
You need to use the data for each recipe inside some JSX. Here is an example of how to populate a table row:
import _ from "lodash";
import React, {Component} from 'react';
import { connect } from 'react-redux';
class RecipeList extends Component {
renderRecipe(recipe) {
return (
<tr key={recipe.id}>
<td>{recipe.recipeName}</td>
<td>{recipe.rating}</td>
<td>{recipe.attributes.course[0]}</td>
<td>{recipe.ingredients.join(', ')}</td>
</tr>
)
}
render() {
return(
<table>
<thead>
<tr>
<th>Recipe</th>
<th>Rating</th>
<th>Course</th>
<th>Ingredients</th>
</tr>
</thead>
<tbody>
{this.props.recipes.matches.map(this.renderRecipe)}
</tbody>
</table>
)
}
}
function mapStateToProps({recipes}) {
return { recipes }
};
export default connect(mapStateToProps)(RecipeList);
So I'm trying to perform some action on the parent component of the child component when a click event is fired in the child component. Currently I have a dynamic loader which is able to load different child components. The problem I have is that the #Output() is being emitted but the parent component doesn't seem to have any knowledge when this event is fired. Is there something I am missing?
child2.component.ts
import {Component, Injector, Output, EventEmitter} from '#angular/core';
#Component({
selector: 'hello-world',
template: `
<div>Hello World {{showNum}}</div>
<li (click)="childButtonClicked(false)"> </li>
`,
})
export class HelloWorldComponent {
showNum = 0;
#Output() childEvent = new EventEmitter<boolean>();
constructor(private injector: Injector) {
this.showNum = this.injector.get('showNum');
console.log("HelloWorldComponent");
}
childButtonClicked(agreed: boolean) {
this.childEvent.emit(agreed);
console.log("clicked");
}
}
child1.component.ts
import {Component, Injector, Output, EventEmitter} from '#angular/core';
#Component({
selector: 'world-hello',
template: `
<div>World Hello {{showNum}}</div>
<li (click)="childButtonClicked(false)"> </li>
`,
})
export class WorldHelloComponent {
showNum = 0;
#Output() childEvent = new EventEmitter<boolean>();
constructor(private injector: Injector) {
this.showNum = this.injector.get('showNum');
console.log("WorldHelloComponent");
}
childButtonClicked(agreed: boolean) {
this.childEvent.emit(agreed);
console.log("clicked");
}
}
dynamic.componentloader.ts
import {Component, Input, ViewContainerRef,ComponentRef, ViewChild, ReflectiveInjector, ComponentFactoryResolver} from '#angular/core';
#Component({
selector: 'dynamic-component',// Reference to the components must be here in order to dynamically create them
template: `
<div #dynamicComponentContainer></div>
`,
})
export class DynamicComponent {
currentComponent:any = null;
#ViewChild('dynamicComponentContainer', { read: ViewContainerRef }) dynamicComponentContainer: ViewContainerRef;
// component: Class for the component you want to create
// inputs: An object with key/value pairs mapped to input name/input value
#Input() set componentData(data: {component: any, inputs: any }) {
if (!data) {
return;
}
// Inputs need to be in the following format to be resolved properly
let inputProviders = Object.keys(data.inputs).map((inputName) => {return {provide: inputName, useValue: data.inputs[inputName]};});
let resolvedInputs = ReflectiveInjector.resolve(inputProviders);
// We create an injector out of the data we want to pass down and this components injector
let injector = ReflectiveInjector.fromResolvedProviders(resolvedInputs, this.dynamicComponentContainer.parentInjector);
// We create a factory out of the component we want to create
let factory = this.resolver.resolveComponentFactory(data.component);
// We create the component using the factory and the injector
let component = factory.create(injector);
// We insert the component into the dom container
this.dynamicComponentContainer.insert(component.hostView);
// We can destroy the old component is we like by calling destroy
if (this.currentComponent) {
this.currentComponent.destroy();
}
this.currentComponent = component;
}
constructor(private resolver: ComponentFactoryResolver) {
}
}
main.component.ts
import { Component, OnInit } from '#angular/core';
import { HelloWorldComponent } from '../../views/main/sidebar-views/comps/hello-world.component';
import { WorldHelloComponent } from '../../views/main/sidebar-views/comps/world-hello.component';
#Component({
selector: 'main-component',
template: require('./main.component.html')
})
export class MainComponent {
private pressed: boolean = false;
componentData:any = null;
constructor() { }
createHelloWorldComponent(){
this.componentData = {
component: HelloWorldComponent,
inputs: {
showNum: 9
}
};
}
createWorldHelloComponent(){
this.componentData = {
component: WorldHelloComponent,
inputs: {
showNum: 2
}
};
}
test(){
console.log("some click event");
}
};
main.component.html
<div>
<h2>Lets dynamically create some components!</h2>
<button (click)="createHelloWorldComponent()">Create Hello World</button>
<button (click)="createWorldHelloComponent()">Create World Hello</button>
<dynamic-component [componentData]="componentData" (childEvent)="test()"></dynamic-component>
</div>
Since you are passing a parameter to the EventEmitter, you need to change your event binding on your component selector in your template to this:
<dynamic-component [componentData]="componentData" (childEvent)="test($event)"></dynamic-component>
Also, don't forget to change function signature in your component to accept the parameter:
test(agreed: boolean){
console.log("some click event");
}
More info on official docs.