I have an Angular 6 Project with a Component which gets an tile Object passed by its parrent. For every tile passend I want to generate the same Chart with chart.js. I works fine for the first Chart but all the others don't get rendered. The console Error Code:
Failed to create chart: can't acquire context from the given item
My tile.component.html
<div *ngIf="tile.type === 'tileImg'">
<div class="card custom-card"
routerLinkActive="glowing">
<img class="card-img-top rounded" src="{{ tile.imgPath }}" alt="Tile image" />
<div class="card-body">
<p class=" card-text text-center">{{ tile.name }}</p>
</div>
</div>
</div>
<div *ngIf="tile.type === 'tileChart'">
<div class="card custom-card"
routerLinkActive="glowing">
<div>
<canvas id="canvas">{{ chart }}</canvas>
</div>
<div class="card-body">
<p class=" card-text text-center">{{ tile.name }}</p>
</div>
</div>
</div>
My tile.component.ts - Don't mind the comments, just for testing purposes
import { Component, OnInit, Input } from '#angular/core';
import { Chart } from 'chart.js';
import { Tile } from 'src/app/tile-container/tile/tile.model';
//import { TileChart } from 'src/app/tile-container/tile/tile-chart.model';
#Component({
selector: 'app-tile',
templateUrl: './tile.component.html',
styleUrls: ['./tile.component.css']
})
export class TileComponent implements OnInit {
#Input() tile: Tile;
//tileChart: TileChart;
chart = [];
constructor() { }
ngOnInit() {
//console.log(this.tile);
//console.log(this.tile.getType());
//console.log(this.tile.getChartType() + " " + this.tile.getChartData() + " " + this.tile.getType().localeCompare('tileChart'));
//console.log(this.tile.getType() == 'tileChart');
if (this.tile.getType() == 'tileChart') {
this.generateChart(this.tile.getChartType(), this.tile.getChartData());
}
}
generateChart(chartType: string, chartData: number[]) {
this.chart = new Chart('canvas', {
type: chartType,
data: {
datasets: [{
data: chartData,
backgroundColor: ['#F39E01', '#b8bbc1']
}],
labels: [
'Verbrauch diese Woche',
'Einsparung in kWh'
]
},
options: {
legend: {
display: false,
},
rotation: 1.1 * Math.PI,
circumference: 0.8 * Math.PI
}
});
}
}
And the parent tile-container.component.html - Not really necessary
<div class="container custom-container">
<div class="container-heading">
<h2>{{ tileContainer.name }}</h2>
</div>
<hr />
<div class="row">
<div class="col text-center"
*ngFor="let tile of tileContainer.tiles">
<app-tile
[tile]="tile">
</app-tile>
</div>
</div>
</div>
Screnshot from missing charts
EDIT
This is my edited typescript code. every tile has an id which I tried to use to have a unique id for every chart created.
ngOnInit() {
console.log(this.tile.id);
if (this.tile.getType() == 'tileChart') {
this.chartId = this.tile.id.toString();
this.ctx = document.getElementById(this.chartId);
console.log(this.ctx);
this.generateChart(this.tile.getChartType(), this.tile.getChartData());
}
}
This a the html where I used databinding.
<div>
<p>{{ chartId }}</p>
<canvas id="{{ chartId }}">{{ chart }}</canvas>
</div>
Picture of error codes
in the template (html), the id for the canvas has to be different for each chart
i am going to give you diferrents approaches the first one it is the most simple the anothers you need a litle more knowledge...i hope it's help
1.-
You can genera a single component just for render the chartjs graphic for example call it chart-dynamic with several inputs id for grabs the unique id you need for render several charts, and dataChart for all fully object to render , asuming that your tile. component looks like this
Important!! you dataChart must be thinking like a array of object and each object it is basicly a chart you will render into your template (follow the oficial documentacion of chartJs)
<div *ngIf="tile.type === 'tileImg'">
<div class="card custom-card"
routerLinkActive="glowing">
<img class="card-img-top rounded" src="{{ tile.imgPath }}" alt="Tile image" />
<div class="card-body">
<p class=" card-text text-center">{{ tile.name }}</p>
</div>
</div>
</div>
<div *ngIf="tile.type === 'tileChart'">
<div class="card custom-card"
routerLinkActive="glowing">
<!-- NEW CODE -->
<ng-container *ngIf="dataChart?.length > 0" >
<div *ngFor="let chart of dataChart; let i=index">
<app-chart-dynamic [id]="SomeUniqueID" [dataChart]="chart" [type]="chart.type"></app-chart-dynamic>
</div>
</ng-container>
<!-- Finish here -->
<div class="card-body">
<p class=" card-text text-center">{{ tile.name }}</p>
</div>
</div>
</div>
IN YOUR tile.component.ts Generate data method as array of objects, move generateChart function into the new component
import { Component, OnInit, Input } from '#angular/core';
import { Chart } from 'chart.js';
import { Tile } from 'src/app/tile-container/tile/tile.model';
//import { TileChart } from 'src/app/tile-container/tile/tile-chart.model';
#Component({
selector: 'app-tile',
templateUrl: './tile.component.html',
styleUrls: ['./tile.component.css']
})
export class TileComponent implements OnInit {
#Input() tile: Tile;
//tileChart: TileChart;
chart = [];
public dataChart: [];
constructor() { }
ngOnInit() {
//console.log(this.tile);
//console.log(this.tile.getType());
//console.log(this.tile.getChartType() + " " + this.tile.getChartData() + " " + this.tile.getType().localeCompare('tileChart'));
//console.log(this.tile.getType() == 'tileChart');
this.getCharts();
}
public getCharts() {
// call data from you service or data mock
this.dataChart = {....response};
}
}
now assuming you has created your new component should looks like this (you have already import charJs and another stuff)
import { Component, OnInit, Input, ViewChild, ElementRef, AfterViewInit } from '#angular/core';
import { Chart } from 'chart.js';
#Component({
selector: 'app-chart-dynamic',
templateUrl: './chart-dynamic.component.html',
styleUrls: ['./chart-dynamic.component.css']
})
export class ChartDynamic implements OnInit, AfterViewInit {
#Input() datasChart: any;
#Input() id: string;
#Input() type?: string;
public idChart: any;
#ViewChild('chart') chart: ElementRef;
public chartObject: any;
constructor() { }
ngOnInit() {
}
generateChart(id: string ,chartType?: string, chartData: any) {
this.idChart = this.id;
this.chart = new Chart(`${this.idChart}`, this.datasChart );
}
ngAfterViewInit() {
this.drawGraphics();
}
}
app-chart-dynamic html file
<div class="some-class-style" >
<canvas [id]="id" #chart> {{ chart }}</canvas>
</div>
it should work if you add into your modules and etc
the another approach is combine viewChild and viewChildren with factory resolver it is more complex but more power full you should check firts based on angular documentation
Related
In Angular i have dynamically created a group of buttons that all have the same action (to show text when clicked) everything works fine yet when I click one button they all perform the action. I need them to work independently of each other. I have dynamically made a different id for each button and was wondering if there was a way to us the id to have them work independently.
Button HTML and TS files:
<button id="{{index}}" class="btn btn-secondary" style="float: right;" (click)="onClick()">Discription</button>
import { Component, EventEmitter, OnInit, Output, Input } from '#angular/core';
#Component({
selector: 'app-discription-btn',
templateUrl: './discription-btn.component.html',
styleUrls: ['./discription-btn.component.css']
})
export class DiscriptionBtnComponent implements OnInit {
#Output() btnClick = new EventEmitter();
#Input() index!: number
constructor() { }
ngOnInit(): void { }
onClick() {
this.btnClick.emit();
}
}
Button Parent HTML and TS files:
<div class="card mb-4 h-100">
<img class="card-img-top-other" src= "{{ post.link }}" />
<div class="card-body">
<div class="small text-muted"> {{ post.created }} <app-discription-btn (btnClick) = "toggleDiscription()" [index] = index></app-discription-btn> </div>
<h2 class="card-title h4"> {{ post.title }} </h2>
<div *ngIf="showDiscription">
<p class="card-text"> {{ post.summary }} </p>
<a class="btn btn-primary" href="#!">Read More -></a>
</div>
</div>
</div>
import { Component, OnInit, Input } from '#angular/core';
import { Subscription } from 'rxjs';
import { BlogPost } from 'src/app/Post';
import { DiscriptionUiService } from 'src/app/services/discription-ui.service';
#Component({
selector: 'app-other-posts',
templateUrl: './other-posts.component.html',
styleUrls: ['./other-posts.component.css']
})
export class OtherPostsComponent implements OnInit {
#Input() post! : BlogPost
#Input() index! : number;
showDiscription : boolean = false;
subscription : Subscription;
constructor(private discritpionService: DiscriptionUiService) {
this.subscription = this.discritpionService.onToggle().subscribe((value) => (this.showDiscription = value));
}
ngOnInit(): void {
}
toggleDiscription(){
this.discritpionService.toggleDiscription();
}
}
Main HTML and TS files:
<div class="container">
<div class="row">
<div class="col-lg-8"><app-featured-post *ngFor="let post of posts; let i = index;" [post] = "post" [index] = "i"></app-featured-post></div>
<div class="col-lg-4"><app-side-widgets></app-side-widgets></div>
<app-other-posts *ngFor="let post of posts | myFilterPipe:filterargs; let i = index;" [post] = "post" [index] = "i" class="col-lg-4" style="padding-top: 10px;" ></app-other-posts>
<nav aria-label="Pagination">
<hr class="my-0" />
<ul class="pagination justify-content-center my-4">
<li class="page-item disabled"><a class="page-link" href="#" tabindex="-1" aria-disabled="true">Newer</a></li>
<li class="page-item active" aria-current="page"><a class="page-link" href="#!">1</a></li>
<li class="page-item"><a class="page-link" href="#!">Older</a></li>
</ul>
</nav>
</div>
</div>
import { Component, OnInit } from '#angular/core';
import { BlogPostService } from 'src/app/services/blog-post.service';
import { BlogPost } from '../../Post';
#Component({
selector: 'app-posts',
templateUrl: './posts.component.html',
styleUrls: ['./posts.component.css']
})
export class PostsComponent implements OnInit {
filterargs = {title: 'The Beginning'}
posts: BlogPost[] = [];
constructor(private postService: BlogPostService ) { }
ngOnInit(): void {
this.postService.getPosts().subscribe((posts) => (this.posts = posts));
}
}
Any ideas would be a great help. Thank you ahead of time!
Hi I've done this filter pipe where I want to look for my imagename and my imageid from all my images, but it just looks for the name and id from the first image.
Anything is wrong on my code aparently.
This is my filter.pipe.ts class where I implement my search method
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'filter'
})
export class FilterPipe implements PipeTransform {
transform(value: any, arg: any): any {
if (arg === '' || arg.length < 1) return value;
const resultPosts = [];
for (const imagen of value) {
if (imagen.name.toLowerCase().indexOf(arg.toLowerCase()) > -1) {
resultPosts.push(imagen);
}else if (imagen.imagenId.toLowerCase().indexOf(arg.toLowerCase()) > -1){
resultPosts.push(imagen);
};
return resultPosts;
}
}
}
My list.component.html where I have my input for searching:
<div class="row">
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="text"name="filterImagen" placeholder="Search" [(ngModel)]="filterImagen">
<button class="btn btn-primary my-2 my-sm-0" type="submit">Search</button>
</form>
<div class="col-md-4" *ngFor="let imagen of imagenes | filter:filterImagen; index as i">
//when I look for the imagename or imageid, it just looks if my first image has the name I write on the searchbar
<div class="card mb-3 animated zoomIn">
<h3 class="card-header">{{imagen.name}}</h3>
<div class="card-body">
<h5 class="card-title"><b>ID: </b>{{imagen.imagenId}}</h5>
</div>
<div class="card-body text-center">
<img style="height: 200px; width: 100%; display: block;" src="{{imagen.imagenUrl}}" alt="Card image">
</div>
</div>
</div>
</div>
/* On my list.component.ts (here I just have a variable filter declared like: )*/
imagenes: Imagen[] = [];
filterImagen = ''; //just declared it here
//I already imported my FormsModule on app.module.ts and my classes.
Did you remember to add the pipe to Declarations? Or better yet, export it from a module, and import that module into the app.module?
UPDATE - I see your error :)
You have to move return resultPosts out of the for-loop.
If you're interested, I refactored the pipe for clarity:
import { Pipe, PipeTransform } from "#angular/core";
import { Imagen } from "./app.component";
#Pipe({
name: "filter"
})
export class FilterPipe implements PipeTransform {
transform(value: Imagen[], arg: any): any {
if (arg === "" || arg.length < 1) return value;
return value.filter(imagen => imagen.name.toLowerCase().indexOf(arg.toLowerCase()) > -1 ||
imagen.imagenId.toLowerCase().indexOf(arg.toLowerCase()) > -1
);
}
}
Example:
outer.html
<div *ngFor="let x of y; let i = index">
<inner-c></inner-c>
</div>
inner-c.html
<div #bar>
<div class="row no-gutters mt-3 mb-3 ml-5 mr-5">
foo
</div>
</div>
How to get access to #bar DOM element list in outer.component.ts controller? Is that possible?
I tried
#ContentChildren('bar') inputElems: QueryList<ElementRef>; and #ViewChildren('bar') inputElems: QueryList<ElementRef>;
in outer.component.ts but those return an empty list.
I don't believe you can directly access the elements from other components. You could however set up an event emitter in the child component to emit the ElementRef of the child element. Try the following
inner-c.component.ts
import { Component, ViewChild, ElementRef, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'inner-c',
templateUrl: './inner-c.component.html',
styleUrls: ['./inner-c.component.css']
})
export class InnerCComponent {
#Output() childElement = new EventEmitter();
#ViewChild('bar')
set viewChild(list: ElementRef){
this.childElement.emit(list);
}
constructor() {
}
}
inner-c.component.html
<div #bar>
<div class="row no-gutters mt-3 mb-3 ml-5 mr-5">
foo
</div>
</div>
app.component.ts
export class AppComponent {
y = [0, 1, 2, 3, 4];
childElement(event) {
console.log(event);
}
}
app.component.html
<div *ngFor="let x of y; let i = index">
<inner-c (childElement)="childElement($event)"></inner-c>
</div>
I cannot make this eventemitter work. Can you please help? I am a beginner and it should be quite simple code for you.
I have a parent component, reading two different emitters from two different children:
<app-van [vans]="vans"></app-van>
<app-modal *ngIf="modalOpen" (closed)="onClick()" (openModal)="onClickTwo($event)"></app-modal>
import { Component, OnInit } from '#angular/core';
import { Van } from '../../interface';
#Component({
selector: 'app-fleet-home',
templateUrl: './fleet-home.component.html',
styleUrls: ['./fleet-home.component.css']
})
export class FleetHomeComponent implements OnInit {
modalOpen = true;
vans: Van [] = [
{ name: 'Ubeddu', description: 'Mercedes Sprinter', plate: 'NH55GKA' },
{ name: 'Abbestia', description: 'Ford Transit', plate: 'DK66HHR' },
{ name: 'Eumulu', description: 'Citroen Berlingo', plate: 'DR55MKL' }
];
constructor( ) { }
ngOnInit() {
}
onClick() {
this.modalOpen = !this.modalOpen;
console.log('modalOpen changed');
}
onClickTwo(event) {
this.modalOpen = event;
console.log('modalOpen changed');
}
}
the parent listened to this child:
<div (click)="onCloseClick()" class="ui dimmer visible active">
<div (click)="$event.stopPropagation()" class="ui modal visible active">
<div class="asuca">
<form class="ui form" >
<h4 class="ui dividing huge header">Van</h4>
<div class="required field">
<label class="ui header">Van Name</label>
<input type="text" placeholder="Van NickName">
</div>
<div class="field">
<label class="ui header">Description</label>
<input type="text"placeholder="Description">
</div>
<div class="field">
<label class="ui header">Plate</label>
<input type="text"placeholder="License Plate">
</div>
<button (click)="onCloseClick()" class="ui button" type="submit">Submit</button>
</form>
</div>
</div>
</div>
import { Component, OnInit, ElementRef, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-modal',
templateUrl: './modal.component.html',
styleUrls: ['./modal.component.css']
})
export class ModalComponent implements OnInit {
#Output() closed = new EventEmitter();
constructor(private el: ElementRef) { }
ngOnInit() {
document.body.appendChild(this.el.nativeElement);
}
// tslint:disable-next-line: use-lifecycle-interface
ngOnDestroy() {
this.el.nativeElement.remove();
}
onCloseClick() {
this.closed.emit();
}
}
and doesnt listen to the second child:
<div class="ui fluid four black cards">
<div *ngFor="let van of vans" class="card">
<div class="content">
<div class="header">
{{ van.name }}
</div>
<div class="meta">
{{ van.description }}
</div>
<div class="description">
{{ van.plate }}
</div>
</div>
<div class="extra content">
<div class="ui two buttons">
<div (click)="onEditClick(true)" class="ui basic black button">Edit</div>
<div class="ui basic red button">Delete</div>
</div>
</div>
</div>
</div>
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-van',
templateUrl: './van.component.html',
styleUrls: ['./van.component.css']
})
export class VanComponent implements OnInit {
#Input() vans = [];
#Output() openModal = new EventEmitter<boolean>();
constructor() { }
ngOnInit() {
}
onEditClick(event: boolean) {
this.openModal.emit(event);
}
}
the whole thing is to hide the modal clicking around the screen and show it again clicking a button.
On the console.log, the object emitter by the first child has got an observer, where the object emitter by the second child as none; no idea what means though.
thanks in advance for the help. I can provide the whole folder if needed. I am just trying to learn :)
Seems like you have missed binding the output event in parent template. Please correct like below:
<app-van [vans]="vans" (openModal)="onEditClick($event)"></app-van>
I have this code:
import { Component, ElementRef, Renderer2 } from '#angular/core';
#Component({
selector: 'my-app',
template: '<button (click)="runR()">Run</button>
<div class="testme">
<div class="somediv">
<div class="dynamically_created_div unique_identifier"></div>
<div class="dynamically_created_div unique_identifier"></div>
<div class="dynamically_created_div unique_identifier"></div>
</div>
</div>',
})
export class AppComponent{
hostEl: any;
constructor(
private el:ElementRef,
private renderer:Renderer2,
) {
this.hostEl = el.nativeElement;
}
runR(){
let change_this;
change_this= this.renderer.createElement('span');
this.renderer.addClass(change_this, 'change_this');
this.renderer.appendChild(this.hostEl, change_this);
}
}
Is there any way in Angular2 to add HTML to the .dynamically_created_div?
Because the above only adds to the end of the HTML of the component.
I also tried with:
import { Component, ElementRef, ViewChild, Renderer, AfterViewInit } from '#angular/core';
#Component({
selector: 'my-app',
template: `<button (click)="runR()">Run</button>
<div class="testme">
<div class="somediv">
<div class="dynamically_created_div">
</div>
</div>
</div>
`,
})
export class AppComponent {
constructor(private renderer:Renderer) {}
runR() {
#ViewChild('dynamically_created_div') d1:ElementRef;
this.renderer.invokeElementMethod(this.d1.nativeElement, 'insertAdjacentHTML', ['beforeend', '<div class="new_div">new_div</div>'] );
}
}
But it's not working because the #ViewChild directive must be outside the function and I can't have control over it anymore
I also tried like this:
<div class="dynamically_created_div" [innerHtml]="newHTML"></div>
this.newHTML = '<div class="new_div">new_div</div>';
Thing I cannot do because my content is dynamic and uses unique IDs and I cannot use [innerHtml] dynamically ( it only works for what I put in themplate for the first time, then anything else that changes can't use innerHtml anymore.
I checked Angular2: Insert a dynamic component as child of a container in the DOM but there is the same problem, the placeholders aren't dynamic
UPDATE:
My code is a little bit more complex:
TS:
import { AfterContentInit, Component, OnInit, OnDestroy, ViewEncapsulation } from '#angular/core';
import { NgForm, FormsModule, ReactiveFormsModule, FormGroup, FormControl, FormBuilder, Validators } from '#angular/forms';
import { SFService } from '../services/sf.service';
import { Injectable, Pipe, PipeTransform } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
providers: [ SFService ],
})
export class AppComponent implements OnInit {
constructor(
private sfservice: SFService,
) {}
ngOnInit(){
this.sfservice.getMembers().subscribe(members => {
this.members = members.members;
});
}
members: Member[];
member_selector: Member[];
member_each: Member;
member_selector_each: Member[];
cases: Case;
runR(){
this.members.forEach(member_each => {
this.member_selector.forEach(member_selector_each => {
if(member_each.Id === member_selector_each.Id){
console.log(member_selector_each.Id);
this.sfservice.getCaseHistory(member_selector_each.Id, "2017-04-25T00:00:00", "2017-04-28T23:59:59").subscribe(cases => {
this.member_each['cases'] = cases;
console.log(this.member_each);
});
}
})
})
}
}
HTML:
<form #myForm="ngForm" novalidate>
<select name="member_selector_name" [(ngModel)]="member_selector" multiple ng-model="selectedValues" style="height:200px;">
<option *ngFor="let member of members" [ngValue]="member">{{member.Name}}</option>
</select>
<button (click)="runR()">Run</button>
</form>
<div id="results">
<div *ngFor="let mem of members" class="member-card-{{mem.Id}}">
<div class="card-container">
<div *ngFor="let case of mem.Cases" class="case-card" id="{{case.Id}}">{{case.Number}}
</div>
</div>
</div>
</div>
I was trying to use only ngFor but now I get
Cannot set property 'cases' of undefined
What's the problem with this approach?
export class AppComponent{
#ViewChild('d1') d1:ElementRef;
#ViewChild('d2') d2:ElementRef;
#ViewChild('d3') d3:ElementRef;
constructor(private renderer:Renderer2) { }
runR(){
let change_this;
change_this= this.renderer.createElement('span');
this.renderer.addClass(change_this, 'change_this');
this.renderer.appendChild(this.d1, change_this);
}
}
Template:
<div class="dynamically_created_div unique_identifier" #d1></div>
<div class="dynamically_created_div unique_identifier" #d2></div>
<div class="dynamically_created_div unique_identifier" #d3></div>
you can use ngfor and create you elements inside it and using index you can create different ids and names.
I do something like this i dont know if you want to do the same but here's my code to create some input's dynamically and add or access their values
<div *ngFor="let comp of templateVals | async;let i=index">
<md-input-container class="example-90" *ngIf="comp.type=='code'">
<textarea rows="4" mdInput name="desc{{i}}" [(ngModel)]="comp.data" placeholder="Description"></textarea>
</md-input-container>
<md-input-container class="example-90" *ngIf="comp.type=='text'">
<textarea rows="4" mdInput name="text{{i}}" [(ngModel)]="comp.data" placeholder="Text"></textarea>
</md-input-container>
<md-input-container class="example-90" *ngIf="comp.type=='title'">
<input mdInput name="title{{i}}" [(ngModel)]="comp.data" placeholder="Title">
</md-input-container>
<span class="example-90" *ngIf="comp.type=='upload'">
<input-file *ngIf="!comp.data" [acceptId]="comp.id" (onFileSelect)="addedFileInfo($event)"></input-file>
<span *ngIf="comp.data">{{comp.data}}</span>
</span>
<span class="example-10">
<button md-mini-fab (click)="removeThis(comp)"><md-icon>remove circle</md-icon></button>
</span>
</div>