I'm trying to create a Angular Material Table that displays dynamic data coming from an API endpoint but only the Table Header populates the screen without any data in it.
And it also does not throw any error...
Whenever I put hardcoded data, it works. But that's not what I want.
Here it is my table-component.ts file
import { Component, OnInit } from '#angular/core';
import { ApiService } from '../api.service'
import { MatTableDataSource } from '#angular/material/table';
const COUNTRY_DATA = []
#Component({
selector: 'app-table',
templateUrl: './table.component.html',
styleUrls: ['./table.component.scss']
})
export class TableComponent implements OnInit {
countryJSON;
displayedColumns: string[] = ['name', 'cases', 'deaths', 'recov', 'permill'];
dataSource = new MatTableDataSource(COUNTRY_DATA);
constructor(private apiService: ApiService) {
}
ngOnInit(): void {
this.apiService.getNews().subscribe((dataJSON) => {
this.countryJSON = dataJSON
for (let countryObject of this.countryJSON) {
COUNTRY_DATA.push
({
name: countryObject.country_name,
cases: countryObject.total_cases,
deaths: countryObject.total_deaths,
recov: countryObject.total_recov,
permill: countryObject.case_per_mill
})
}
console.log(COUNTRY_DATA)
})
}
}
As you can see, I'm printing to the console what does COUNTRY_DATA has in it, and I get the expected data: An array of objects
But they don't populate the Table... And it looks like this:
Well, the main problem with your code is that you don't push your array in the datasource, instead, you're instantiating an empty instance.
if you called dataSource = new MatTableDataSource(COUNTRY_DATA); at the position of console.log(COUNTRY_DATA) it should work.
A better approach when receiving an observable response is to use map instead of a loop, here is an answer for mapping
How to map a response from http.get to a new instance of a typed object in Angular 2
P.S. It will much better if you used an interface to introduce your object, and mapped the result in your ApiService and just set it as the value of your datasource in the component.
Related
I'm trying to build a list of cards which may contain different components; So for example I have the following array of objects:
{
title: 'Title',
descrption: 'Description',
template: 'table',
},
{
title: 'Title',
descrption: 'Description',
template: 'chart',
}
I get this array as a response from a service, then I need to match each of thos objects to a component based on the template property, so for example, the first item should match to the TableComponent and the second one to the ChartComponent;
I'm trying to follow the Angular Docs regarding Dynamic Component Loading, but I'm not sure how tell the method how to match each object in the array to a specific component.
In my parent component I have made an anchor point where the components should load with a directive:
<ng-template appCheckpointHost></ng-template>
And I'm trying to use the ComponentFactoryResolver as it shows in the example.
loadComponent() {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(ChartCheckpointComponent);
const viewContainerRef = this.checkHost.viewContainerRef;
}
The example shows a scenario in which the "service" runs every three seconds, gets a random item, and shows it; but what I'm trying to do instead is to fetch all the items when the parent component loads, and render each item with its respective component.
Any ideas to get this to work?
You can create a dictionary like:
const nameToComponentMap = {
table: TableComponent,
chart: ChartComponent
};
And then just use this dictionary to determine which component should be rendered depending on the template property of particular item in your items array:
const componentTypeToRender = nameToComponentMap[item.template];
this.componentFactoryResolver.resolveComponentFactory(componentTypeToRender);
You can view my blog here
First I will need to create a directive to reference to our template instance in view
import { Directive, ViewContainerRef } from "#angular/core";
#Directive({
selector: "[dynamic-ref]"
})
export class DynamicDirective {
constructor(public viewContainerRef: ViewContainerRef) {}
}
Then we simply put the directive inside the view like this
<ng-template dynamic-ref></ng-template>
We put the directive dynamic-ref to ng-content so that we can let Angular know where the component will be render
Next I will create a service to generate the component and destroy it
import {
ComponentFactoryResolver,
Injectable,
ComponentRef
} from "#angular/core";
#Injectable()
export class ComponentFactoryService {
private componentRef: ComponentRef<any>;
constructor(private componentFactoryResolver: ComponentFactoryResolver) {}
createComponent(
componentInstance: any,
viewContainer: any
): ComponentRef<any> {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(
componentInstance
);
const viewContainerRef = viewContainer.viewContainerRef;
viewContainerRef.clear();
this.componentRef = viewContainerRef.createComponent(componentFactory);
return this.componentRef;
}
destroyComponent() {
if (this.componentRef) {
this.componentRef.destroy();
}
}
}
Finally in our component we can call the service like this
#ViewChild(DynamicDirective) dynamic: DynamicDirective;
constructor(
private componentFactoryService: ComponentFactoryService
) {
}
ngOnInit(){
const dynamiCreateComponent = this.componentFactoryService.createComponent(TestComponent, this.dynamic);
(<TestComponent>dynamiCreateComponent.instance).data = 1;
(<TestComponent>dynamiCreateComponent.instance).eventOutput.subscribe(x => console.log(x));
}
ngOnDestroy(){
this.componentFactoryService.destroyComponent();
}
/////////////////////////////////
export class TestComponent {
#Input() data;
#Output() eventOutput: EventEmitter<any> = new EventEmitter<any>();
onBtnClick() {
this.eventOutput.emit("Button is click");
}
}
Am working on a Single page Application built using Angular 8 on the frontend and Laravel on the backend. It is a CRUD application, on the delete functionality, it is working well by deleting the user of the specific id on the database. After the user of the specific id is deleted, am fetching all the products from the database but I want to update the data on the U.I afresh with the new data (excluding the deleted resource).
Kindly assist?
Show.component.ts file
import { Component, OnInit , ViewChild, ElementRef} from '#angular/core';
import { SharedService } from 'src/app/Services/shared.service';
import { AuthService } from 'src/app/Services/auth.service';
import { Router } from '#angular/router';
import { Observable } from 'rxjs';
import { SnotifyService } from 'ng-snotify';
#Component({
selector: 'app-show',
templateUrl: './show.component.html',
styleUrls: ['./show.component.css']
})
export class ShowComponent implements OnInit {
public userData : any[];
public error = null;
constructor(
private Shared : SharedService,
private Auth:AuthService,
private router: Router,
private Notify:SnotifyService
) { }
//Update the data when the DOM loads
ngOnInit() {
this.Shared.checkAll$.subscribe(message => this.userData = message);
}
//Method called when the delete button is triggered from the html
//Inside it we submit the data to the backend via a service and get
//the response
deleteUser(id:number){
return this.Auth.delete(id).subscribe(
data => this.handleDeleteResponse(data),
error => this.handleDeleteError(error)
);
}
//data below contains data from the backend after successful deletion
handleDeleteResponse(data:any){
this.Notify.success(`Successfully Deleted in our records`, {timeout:4000});
}
handleDeleteError(error:any){
console.log(error);
}
}
In you’re handleDeleteResponse method, there is a data if the data is the userData this.userData = data or it’s simple delete the user id from the array in you’re Js in the subscription of your delete method.
Like:
this.userData = this.userData.filter(user => user.id !== idToDelete )
Method 1:
Define a Subject in your service and subscribe to that subject in the service to receive the data. In the component, change the lifecycle hook to 'onChanges'. As soon as the data in the Subject is received/updated (with the deleted records) ngChanges shall reflect it in the DOM.
Method 2:
Track the records on the front-end in the form of list and when the service gives the response of delete as success then delete that very record in the list using ID or any other unique identifier. In this case you need not to populate all the records again.
export class MyComponent implements OnInit, OnChanges {
ngOnChanges() {
// code here
}
ngOnInit() {
// code here
}
}
I have calendar component with data property decorated as #Input():
import { Component, OnInit, Input, OnChanges, SimpleChanges } from '#angular/core';
#Component({
selector: 'app-calendar',
templateUrl: './calendar.component.html',
styleUrls: ['./calendar.component.css']
})
export class CalendarComponent implements OnInit, OnChanges {
#Input() data: CalendarDay[];
constructor() {
this.data = [];
}
ngOnInit() {
this.initDays();
}
ngOnChanges(changes: SimpleChanges) {
console.log(this.data);
console.log(changes.data);
}
}
I pass data in from another component like that:
<app-calendar [data]="this.calendarData"></app-calendar>
And passed data gets rendered by *ngFor in the calendar component (it renders perfectly and everything works just fine):
<div *ngFor="let item of data">{{item.date}}</div>
I want to parse this data first before rendering it into view and whenever i try to console.log data property within the calendar component i get strange array, its shows as empty, i can 'open' it from browser console:
.
And when i try to log value like that:
console.log(this.data[0])
or
console.log(changes.data.currentValue[0])
i get undefined value.
Delete this.data = [] from constructor, avoid change anything when you use dependecy injecton.
And use set and get for each Input() that you want to use in your template, it's a best practice.
private _data: CalendarDay[];
#Input() set data(data: CalendarDay[]) {
if(data) {
this._data = data;
}
}
get data(): CalendarDay[] {
return this._data;
}
And in your HTML you should pass it with:
<app-calendar [data]="calendarData"></app-calendar>
In calendar component you can use with
<div *ngFor="let item of data">{{item.date}}</div>
Following what is documented here: Dynamic Component Loader.
I want to know how is it possible to handle the data inside this HeroJobAdComponent class:
import { Component, Input } from '#angular/core';
import { AdComponent } from './ad.component';
#Component({
template: `
<div class="job-ad">
<h4>{{data.headline}}</h4>
{{data.body}}
</div>
`
})
export class HeroJobAdComponent implements AdComponent {
#Input() data: any;
}
As you can see, data is the object holding the data received. I want to be able to define a constructor for my HeroJobAdComponent class but if I do, the object data is undefined inside my constructor. I tried using ngOnChange instead which supposedly executes once input is changed from undefined to defined but it also did not execute at all.
Can someone please explain first why is the object undefined even though the data is defined in my main component calling it, and what's the workaround for this issue?
This is the constructor I am using:
constructor()
{
this.values = this.data.values;
this.spec_name = this.data.spec_name;
}
if you want to use any operation when you receive data in your component , you can use setter
export class HeroJobAdComponent implements AdComponent {
_data;
#Input() set data (data: any){
//operation on data goes here
this._data=data
};
get data() {
return this._data;
}
}
I'm using Angular Material Data Table in my project. The table is rendering with data
My problem is that I can't update automatically the view when I add new data to the database, every time I should refresh my page.
According to Cdk-table and after reading this tutorial I tried to add live data streaming that to table:
Here's my logique :
import { Component, OnInit } from "#angular/core";
import { MatTableDataSource } from "#angular/material";
import { AjoutprojService } from "../ajoutproj.service";
import { NouveauProjet } from "../models/nouveau-projet";
import { Observable } from "rxjs/Observable";
import 'rxjs/add/observable/merge';
import { DataSource } from "#angular/cdk/collections";
#Component({
selector: "app-liste-projets",
templateUrl: "./liste-projets.component.html",
styleUrls: ["./liste-projets.component.css"]
})
export class ListeProjetsComponent implements OnInit {
constructor( private ajoutProj: AjoutprojService ) {}
nouveauProjet: NouveauProjet[];
nouveauProjet2: NouveauProjet[];
stateExression: string = "inactive";
ngOnInit() {}
displayedColumns = ["Nom projet", "Lead Projet", "effectif"];
dataSource = new UserDataSource(this.ajoutProj);
applyFilter(filterValue: string) {
filterValue = filterValue.trim(); // Remove whitespace
filterValue = filterValue.toLowerCase(); // MatTableDataSource defaults to lowercase matches
//this.dataSource.filter = filterValue;
}
}
export class UserDataSource extends DataSource<any> {
constructor(private ajoutProj: AjoutprojService) {
super();
}
/*returns an observable that emits an array of data.
Whenever the data source emits data to this stream, the table will render an update.*/
connect(): Observable<NouveauProjet[]> {
return this.ajoutProj.getAllProj();
}
disconnect() {}
}
Here's my service
getAllProj(): Observable<NouveauProjet[]> {
return this.http.get<NouveauProjet[]>(
"http://127.0.0.1:8081/api/proj/projets"
);
}
ajoutProj.getAllProj() service is getting right data. but view is not live updating.
HttpClient doesn't stream. You're getting your data only once.
First you'd need a realtime database / backend solution, then you need to connect to that via websocket and listen to changes in the database.
Some frameworks / libraries that I like and package both the client- and serverside of the equation, and make the whole thing a lot easier:
Fireloop - built on top of Loopback 3 on nodejs, provides Angular SDK creation, ie. same models and APIs on client as on server. Typescript, Observables all the way. It's just awesome.
Firebase - "backendless", totally different way of thinking about a "server" from any REST scheme you might be used to.
Meteor - a monolithic framework, probably also very far from what you're used to.
Of course there's always another (very inefficient) way: Poll your DB every X seconds for changes.
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/switchMap';
export class UserDataSource extends DataSource<any> {
constructor(private ajoutProj: AjoutprojService) {
super();
}
connect(): Observable<NouveauProjet[]> {
const initialDelay = 0; // Time to wait before first poll, after the table has connected to this DataSource
const period = 10000; // Polling period in milliseconds
return Observable.timer(initialDelay, period)
.switchMap(() => this.ajoutProj.getAllProj());
}
disconnect() {}
}