[component.ts]
import { Component, style } from "#angular/core";
import { DataService } from '../data.service';
import d3 = require("d3-3");
import { Data } from "../data";
#Component({
selector: 'ts-graph',
templateUrl: './time-seriesG.component.html',
styles: [`
.graph: {padding-left: 20px !important; }
`]
})
export class timeSeriesComponent {
sym: string[] = [];
price: any[] = [];
tStamp: any[] = [];
data: any[]=[];
arr: any;
constructor(private dataService: DataService) { }
getData() {
let i: number;
this.dataService.getData()
.subscribe(data => {
for(i=0;i<Object.keys(data).length;i++){
this.data.push(data[i])
}
});
}
logV() {
this.arr = this.data.map((d,i) => ({
//time: d.time,
//sym: d.sym,
price: d.price,
index: i
}))
this.arr = this.arr.slice(0,25)
console.log(this.arr.slice(0,25))
}
lineGraph() {
var h=400;
var w=350;
var lineFun = d3.svg.line()
.x(function(d){return d.index*5})
.y(function(d){return d.price})
.interpolate('linear');
var svg = d3.select('div')
.append('svg')
.attr('id','LineGraph')
.attr('width',w)
.attr('height',h);
var viz = svg.append('path')
.attr('d',lineFun(this.arr))
.attr('stroke','white')
.attr('stroke-width',2)
.attr('fill','none');
}
ngOnInit() {
this.getData();
}
}
[component.html]
<h2>Time Series Graph</h2>
<div>{{logV()}}</div>
<div>{{lineGraph()}}</div>
I am trying to get a simple line graph of some data coming from a service. for some reason the svg component is created many times.
Also one of my arrays seems to be initiated many times as well.
I would really appreciate any suggestions, but please keep in mind that I do not have a lot of experience using angular.
The array format:
You call lineGraph from your view here <div>{{lineGraph()}}</div>. So everytime a changedetection is triggered, the function is called, resulting in many, many, many appended svgs.
Just call it once in after the data is received and you should only get one svg.
Related
I have a SPA that ultimately lists out a lot of data, but in batches.
I created a component at the bottom of the list, with a 'Visibility' directive so that when it is visible we make a new request to the dataset in a SQL server to get the next batch.
html-tag-for-component
<app-infinity-scroll
[(pageNumber)]="page"
[displayPage]="displayPage"
[authd]="authd"
[done]="done"
[numResults]="displayPage == 'tiles-hub' ? hubs.length : wallets.length"
class="{{scrollVisible ? '' : 'hiddenDisplay'}}"
trackVisibility
></app-infinity-scroll>
component-to-trigger-data-call
import { outputAst } from '#angular/compiler';
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '#angular/core';
import { DbSqlService } from 'services/db-sql.service';
import { TokenAuthService } from 'services/token-auth.service';
import { TrackVisibilityDirective } from 'src/app/directives/track-visibility.directive';
import { SortStyle } from 'src/app/interfaces/mvlot';
import { MatProgressBar } from '#angular/material/progress-bar';
#Component({
selector: 'app-infinity-scroll',
templateUrl: './infinity-scroll.component.html',
styleUrls: ['./infinity-scroll.component.scss']
})
export class InfinityScrollComponent implements OnInit {
#Input() pageNumber: number;
#Input() displayPage: string;
#Input() authd: boolean;
#Input() done: boolean;
#Input() numResults: number;
#Output() pageNumberChange = new EventEmitter<number>();
lastDisplay = '';
loading: boolean = true;
constructor(
private visTrack: TrackVisibilityDirective
, private cdr: ChangeDetectorRef
, private dbApi: DbSqlService
, private authService: TokenAuthService
) {
}
ngOnInit(): void {
this.authService.UserAuthd.subscribe((res) => {
// if (res) {
this.dbApi.initGetWalletsHandler(0, 50, SortStyle.scoreDesc);
this.pageNumber = 1;
// }
})
this.visTrack.visibile.subscribe((val) => {
if (!this.done) {
this.loading = true;
if (val) {
if (this.displayPage == 'tiles') {
this.dbApi.initGetWalletsHandler((this.pageNumber) * 50, 50, SortStyle.default);
this.pageNumber += 1;
}
if (this.displayPage == 'tiles-hub') {
this.dbApi.initGetHubsHandler((this.pageNumber) * 50, 50);
this.pageNumber += 1;
}
}
}
})
}
}
Some functions run, call out to a back-end, respond with data, where a listener is waiting.
this.dbApi.resultObs.subscribe(val => {
if (val.append != true) {
this.results = [];
}
if (val.reset) {
this.page = 1;
}
val.data.data.forEach((b: any) => {
var result: Collection;
var existingResults = this.results.filter(w => w.ownerId == b.ownerId);
if (existingResults.length == 0) {
result = {
ownerId: b.ownerId
, totalScore: b.modifiedLandScore
, filteredCount: b.filteredCount
, totalLots: b.totalLots
, totalPrice: b.totalPrice
, name: ''
, lands: []
, type: CollectionType.b
}
result.bs.push(b);
this.results.push(result);
} else {
result = existingResults[0];
result.bs.push(b);
}
});
this.resultDataSource = new MatTableDataSource(this.results);
this.collectionType = CollectionType.b;
this.uiService.loadingBar(false);
this.done = val.data.data.length == 0;
this.cdr.detectChanges();
})
And, finally this is laid out for the user:
<tr *ngFor="let result of results">
<td>
<display-block
[collection]="b"
[displayVertical]="displayVertical"
[displayCaseCount]="displayCaseCount"
[gridClassName]="gridClassName"
[authd]="authd"
[type]="result.type"
[expanded]="results.length == 1"
[isPhonePortrait]="isPhonePortrait"
></display-block>
</td>
</tr>
Everything works fine on the first grab of data.
And everything appears to work fine on the second pull, but for any of the items appended to the view with the second pull, ChangeDetector just seems to give up. I'll trigger an action, that should modify the view, but nothing happens, unless I manully put in cdr, or I flip to a new window, or something, then they respond.
I'm going to continue trying to find a root cause, but at the moment, I'm out of ideas. There's no prominent error message that would imply something broke. The items fromt the first batch still work. But the ones from the second will appear to lock up. until CDR is forced by an outside event.
I wanted to check here to see if anyone had any ideas on what may be causing this.
Also, here's the declaration code for 'trackVisibility'
import {
Directive,
ElementRef,
EventEmitter,
NgZone,
OnDestroy,
OnInit,
Output,
} from '#angular/core';
#Directive({
selector: '[trackVisibility]',
})
export class TrackVisibilityDirective implements OnInit, OnDestroy {
observer!: IntersectionObserver;
#Output()
visibile = new EventEmitter<boolean>();
constructor(private el: ElementRef<HTMLElement>, private ngZone: NgZone) {}
ngOnInit(): void {
this.ngZone.runOutsideAngular(() => {
this.observer = new IntersectionObserver((entries) => {
entries.forEach((e) => {
this.visibile.emit(e.isIntersecting);
});
});
this.observer.observe(this.el.nativeElement);
});
}
ngOnDestroy(): void {
this.observer.disconnect();
}
}
here is the solution
You used runOutsideAngular function in your Directive.
"Running functions via runOutsideAngular allows you to escape Angular's zone and do work that doesn't trigger Angular change-detection or is subject to Angular's error handling. Any future tasks or microtasks scheduled from within this function will continue executing from outside of the Angular zone."
I also changed some parts of the code for more readability.
I need to create a geo chart and I have installed the chartjs-chart-geo to implement a chart that simple show the total sales per state.
I got this error when trying to fetch the features from the topojson object and I got stack in here. Can any of you please help me ?
Error:
Property 'features' does not exist on type 'Feature<Point, GeoJsonProperties>'.
Here's my unfinish component code:
import { Component, OnInit } from '#angular/core';
import { DashboardService } from '../../../modules/dashboard.service';
import { Chart, registerables } from 'chart.js';
import { ChoroplethController, GeoFeature, ColorScale, ProjectionScale } from 'chartjs-chart-geo';
import * as ChartGeo from 'chartjs-chart-geo'
import ChartDataLabels from 'chartjs-plugin-datalabels';
// register controller in chart.js and ensure the defaults are set
Chart.register(ChoroplethController, GeoFeature, ColorScale, ProjectionScale);
Chart.register(...registerables);
Chart.register(ChartDataLabels);
// Get the topojson file examples
const url = 'https://unpkg.com/world-atlas#2.0.2/countries-50m.json';
// const url = 'https://unpkg.com/us-atlas/states-10m.json';
#Component({
selector: 'quotes-by-state',
templateUrl: './quotes-by-state.component.html',
styleUrls: ['./quotes-by-state.component.css']
})
export class QuotesByStateComponent implements OnInit {
chart:any;
constructor(private dashService: DashboardService) { }
ngOnInit(): void {
// Prepare chart
this.createChart();
}
// Create chart component
createChart() {
// fetch('https://unpkg.com/us-atlas/states-10m.json').then((r) => r.json()).then((us) => {
// const nation = ChartGeo.topojson.feature(us, us.objects.nation).features[0];
// const states = ChartGeo.topojson.feature(us, us.objects.states).features;
fetch(url).then((result) => result.json()).then((datapoint) => {
// Get ChartGeo features from json
const nation = ChartGeo.topojson.feature(datapoint, datapoint.objects.countries).features;
this.chart = new Chart('geo-chart', {
type: 'choropleth',
data: {
labels: ['a', 'b', 'c'],
datasets: [
{
label: 'Contries',
outline: nation,
data: null, //states.map(country => ({feature: country, value: Math.random() * 100})),
},
]
},
options: {
responsive: true,
}
});
});
}
}
I want to display the data from my backend to the front end. It's working but I am making a shopping cart system in Angular using help from a youtube video. I have poor knowledge of Observables and stuff related to it. For the Youtuber, the data is displayed. The only difference between the youtube project and mine is that he is using a fake store API and I am using a database and getting products from the backend.
My cart.component.ts file
import { Component, OnInit } from '#angular/core';
import { CartService } from 'src/services/cart.service';
#Component({
selector: 'app-cart',
templateUrl: './cart.component.html',
styleUrls: ['./cart.component.css']
})
export class CartComponent implements OnInit {
products: any = [];
allProducts: any = 0;
constructor(private cartService: CartService) { }
ngOnInit(): void {
this.cartService.getProductData().subscribe(res => {
this.products = res;
this.allProducts = this.cartService.getTotalAmount();
})
}
removeProduct(item: any) {
this.cartService.removeCartData(item);
}
removeAllProducts() {
this.cartService.removeAllCart();
}
}
My cart.service.ts file
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class CartService {
cartDataList: any = [];
productList = new BehaviorSubject<any>([]);
constructor() { }
// Get cart data
getProductData() {
return this.productList.asObservable();
}
// Set cart data
setProduct(product: any) {
this.cartDataList.push(...product);
this.productList.next(product);
}
// Add products to cart
addToCart(product: any) {
this.cartDataList.push(product);
this.productList.next(this.cartDataList);
this.getTotalAmount();
console.log(this.cartDataList);
}
// Calculate total amount
getTotalAmount() {
let grandTotal = 0;
this.cartDataList.map((a: any) => {
grandTotal += a.total;
});
}
// Remove product one by one
removeCartData(product: any) {
this.cartDataList.map((a: any, index: any) => {
if (product.id === a.id) {
this.cartDataList.splice(index, 1);
}
})
}
// Empties the whole cart
removeAllCart() {
this.cartDataList = [];
this.productList.next(this.cartDataList);
}
}
I know the problem is in getProductData() function but I don't know how to fix it. Also if you need any other file that may help feel free to ask and yes this is my very first post asking a question.
productList = new BehaviorSubject([]);
getProductData$ = this.productList.asObservable();
do not use this.productList.asObservable() in a function declare it as a variable.
I try to make a pie highchart with data from Asp.Net and when i run the program the labels are not distributed individually,i use the same method for data array and i didn't have this issue there.
This is a representation of the graph : https://jsfiddle.net/rk0t4ghc/1/ ,i did the dataLabel array to reproduce the data from Asp.net.
Can you please help me?
import { Component, OnInit, Input } from '#angular/core';
import * as Highcharts from 'highcharts';
import { Order } from 'src/app/shared/order';
import {Customer} from 'src/app/shared/customer';
import {SalesDataService} from '../../services/sales-data.service';
import _ from 'lodash';
#Component({
selector: 'app-pie-chart',
templateUrl: './pie-chart.component.html',
styleUrls: ['./pie-chart.component.css']
})
export class PieChartComponent implements OnInit {
constructor(private _salesDataService:SalesDataService) { }
#Input() inputData:any;
#Input() limit:number;
ngOnInit() {
this.parseChartData(this.inputData,this.limit);
}
parseChartData(res:any,limit? :number){
console.log('response:',res);
const allData=res.slice(0,limit);
console.log('allData(slice):', allData);
Highcharts.chart('container2',{
chart:{
events:{
load(){
const chart=this;
chart.series[0].points.forEach((point)=>
point.update({
name:allData.map(x=>_.values(x)[0])
}),false);
chart.redraw();
}
}
},
tooltip:{
pointFormat: '{name}: <b>{point.percentage:.1f}%</b>'
},
series:[{
type:'pie',
showInLegend:true,
"data":allData.map(x=>_.values(x)[1])
}]
})
}
}
It is happening because you are assigning the entire array as a point name instead of a certain element of the array. (name: array, instead of a name: array[index])
Live demo:
https://jsfiddle.net/BlackLabel/yLfq207n/
load() {
dataLabels=['Name1','Name2','Name3','Name4']
const chart = this;
chart.series[0].points.forEach((point, index) => point.update({
name:dataLabels[index]
}), false);
chart.redraw();
}
I am trying to implement the tab view component of Prime NG. but my tabs are dynamic in nature ie.
So when the container is loaded it sends multiple AJAX requests for data inside the component.(Maybe the component is initialized multiple times?)
Another thing, in one of the components, moving mouse gives Thousands of errors on the console.
ERROR Error: Error trying to diff '[object Object]'. Only arrays and iterables are allowed
ERROR CONTEXT [object Object]
Not sure why. Used the same component in another place and there was no issue.
Even if I remove the dynamic nature of the components and just place 4 static tabs, everything works perfectly.(Right now the same 4 components are coming from server).
Html Template:
<div class="col-md-12 padding0">
<div class="tabViewWrapper">
<p-tabView (onChange)="handleChange($event)">
<p-tabPanel header="{{tab.tabName}}" *ngFor="let tab of tabs" >
<dynamic-component [componentData]="componentData"></dynamic-component>
</p-tabPanel>
</p-tabView>
<div>
</div>
Component:
#Component({
selector: 'tab-view',
templateUrl: './tab-view.component.html',
styleUrls: ['./tab-view.component.scss'],
encapsulation: ViewEncapsulation.None,
entryComponents: [GenericDataTableComponent, SingleEditCategoryExplorerComponent, AssetsDataTableComponent]
})
export class TabViewComponent implements OnInit {
private ngUnsubscribe: Subject<void> = new Subject<void>();
private componentData = null;
private tabs: Array<any>;
private index:number;
private disabledTabs:Array<any>;
private disabledTabsWhenMetaDataClicked:Array<any>;
versionConfig = {
url: AppSettingProperties.DATA_TABLE_VALUES.VERSIONS_URL,
dateLocale: AppSettingProperties.DATA_TABLE_VALUES.LOCALE,
header: AppSettingProperties.DATA_TABLE_VALUES.VERSIONS_HEADER
};
relatedConfig = {
url: AppSettingProperties.BASEURL + AppSettingProperties.DATA_TABLE_VALUES.RELATED_ENDPOINT,
header: AppSettingProperties.DATA_TABLE_VALUES.RELATED_HEADER
};
constructor(private assetDataLoadedService: AssetDataLoadedService, private assetDetailsService: AssetDetailsService, private assetDetailDataModel:AssetDetailDataModel) { }
#ViewChildren(DynamicContainerComponent) dynamicContainers: QueryList<DynamicContainerComponent>;
ngOnInit() {
this.disabledTabs = [];
//Set items to be disabled when Metadata button is clicked
this.disabledTabsWhenMetaDataClicked = [AppSettingProperties.TAB_RELATEDITEMS, AppSettingProperties.TAB_VERSIONS];
//Disable the tabs as per the condistions
this.disableTabsAsPerRequirement();
//Assigning tabs
this.tabs = this.assetDetailsService.systemTabs;
}
getInitialSelected(tab){
return this.selectedTab == this.tabs.indexOf(tab);
}
get selectedTab():number{
return this.index;
}
set selectedTab(val:number){
this.index = val;
var defaultTab = this.tabs[this.index]['tabName'];
if(!this.assetDetailDataModel.catalogId){
this.assetDataLoadedService.assetDetailPublisher.subscribe(data=>{
this.loadComponentByTab(defaultTab);
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
});
}
else{
this.loadComponentByTab(defaultTab);
}
}
handleChange(e) {
let tabName: string = e.originalEvent.currentTarget.innerText;
this.selectedTab = e.index;
//this.loadComponentByTab(tabName);
}
loadComponentByTab(tabName:string){
switch (tabName) {
case AppSettingProperties.TAB_METADATA:
this.componentData = { component: AssetsDataTableComponent, inputs: {} }
break;
case AppSettingProperties.TAB_CATEGORY:
let categoryConfig: object = {"catalog_id":this.assetDetailDataModel.catalogId,"item_id":this.assetDetailDataModel.assetId};
console.log(categoryConfig);
this.componentData = { component: SingleEditCategoryExplorerComponent, inputs: { tabConfig: categoryConfig } }
break;
case AppSettingProperties.TAB_RELATEDITEMS:
this.componentData = { component: GenericDataTableComponent, inputs: { tabConfig: this.relatedConfig } }
break;
case AppSettingProperties.TAB_VERSIONS:
this.componentData = { component: GenericDataTableComponent, inputs: { tabConfig: this.versionConfig } }
break;
}
}
}
Dynamic Component:
import { Component, Input, ViewContainerRef, ViewChild, ReflectiveInjector, ComponentFactoryResolver } from '#angular/core';
#Component({
selector: 'dynamic-component',
template: `<div #dynamicComponentContainer></div>`,
})
export class DynamicComponent {
private currentComponent = null;
#ViewChild('dynamicComponentContainer', { read: ViewContainerRef }) dynamicComponentContainer: ViewContainerRef;
constructor(private resolver: ComponentFactoryResolver) { }
// 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 }) {
console.log("Building Component Start");
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;
console.log("Building Component Finish");
}
}
Another thing is that the console start in dynamic component is shown 8 times.
While console finish is shown 4-5 times.
Seems really weird behavior.
As #echonax wrote in comment.
This is because you are trying to iterate something that is not an array.
Most probably this.tabs.
You can try and write out {{tabs|json}} in a div instead of the *ngFor
Since your response takes sometime to load your DOM will have tabs variable as undefined array.
To solve this initialize the variable to an empty array as below
tabs:Array<any> = []
or inside the constructor as
constructor(){
this.tabs = [];
}