can use service inside a foreach() - javascript

import { Component, Input, Output, OnInit, OnChanges } from '#angular/core';
import { ViewComponent } from '../view/view.component';
import { HitoService } from '../../services/hito.service';
#Component({
selector: 'app-time-line',
templateUrl: './time-line.component.html',
styleUrls: ['./time-line.component.css'],
providers: [HitoService]
})
export class TimeLineComponent implements OnChanges, OnInit {
#Input() calbuscador: String;
#Input() idcalbuscador: String;
public pepe: String
nom_cal1: any;
hito1: any = {};
hito2: any = {};
constructor(public _hitoService: HitoService) {
}
ngOnInit() {
}
///we retrieve the value sento to this component in OnChanges
ngOnChanges() {
this.nom_cal1 = this.calbuscador;
this.drawtimeline(this.nom_cal1, this._hitoService, this.idcalbuscador);
}
result: any[] = [];
drawtimeline(nom_cal, _hitoService, idcalbuscador) {
var container = document.getElementById('timeLine');
//alert("id cal sele es :" + idcalbuscador);
var k = 0;
var j = 0;
var master = new vis.DataSet();
var items = new vis.DataSet();
this.result.push({ "_id": this.idcalbuscador,
"title": nom_cal });
this.result.forEach(function (ev) {
master.add([{ id: ev._id, content: ev.title, cal_id: ev._id }]);
var g = ev._id;
for (var i= 0; i<this.result.length; i++){
console.log("hola");
console.log(this.result[i]._id);
this._hitoService.getHitos(this.result[i]._id)
.subscribe(hito2 => {
this.hito2 = hito2
var items: any;
items = hito2;
items.forEach(function (item) {
items.add([{ id: k, group: g, start: item.start_datetime, end: item.end_datetime, style: itemStyle(item.design), className: "pepe" }]);
k++;
});
j++;
});
}
});
I am trying to implement a timeline using the vis.js, I retrieve the name and id of the timeline want in this class component then in the ngOnChanges I call the function to draw the timeline passing to it the name of the timeline, it's id and the services in other to get the observables item of the this specific timeline. I have an array that will store the timelines (result) I want to view and then an observable I subscribed to, to add the items of the timelines. The first foreach() will remove the first element in the result array, get the observables of items for that result and the second foreach() will go through the observables items and print the items, then it start over and move to the next. But all I get in the browsers console is : TypeError: this is undefined. Probably not making use of the service in the forach()

You can use an arrow function inside your forEach to keep using the enclosing scope.
this.result.forEach((ev) => {
// your code here
}

Assign current this to another variable (Say, that) then, use that into your callback.
Note: Inside your callback function this is changed to the JavaScript context by which the function is called.
const that = this; // save the current 'this' to 'that' variable
this.result.forEach(function (ev) {
master.add([{ id: ev._id, content: ev.title, cal_id: ev._id }]);
var g = ev._id;
for (var i= 0; i< that.result.length; i++){
console.log("hola");
console.log(that.result[i]._id);
that._hitoService.getHitos(that.result[i]._id)
.subscribe(hito2 => {
that.hito2 = hito2
var items: any;
items = hito2;
....
....
....

Related

Is there a Lifecycle Hook for components nested inside mat-tab?

New to Tabulator.
I have a mat-tab-group that I'm using for navigating through three Tabulator Tables.
The approach that I'm using is to create a div element inside component.ts, and the insert it into an existing div within the component.html.
The issue that I'm having is that only the selected tab has its html loaded into the DOM, so any div elements in an unselected tab aren't in the DOM, and cant be referenced by id into my component.ts. When I switch to a tab other than the one that was initially loaded, the tabulator table is not drawn.
What hoping is that Angular has a lifecycle hook that get's called on child-components of mat-tab that I can use to trigger a draw, when a user selects that tab.
I'm open to other/better approaches, of course.
mat-tab-group html
<mat-tab-group *ngIf="hasData" mat-align-tabs="start">
<mat-tab *ngFor="let tablename of tableNames">
<ng-template matTabLabel>
<span>{{tablename}}</span>
</ng-template>
<div>
<app-tabulator-table *ngIf="dataServiceSelectedDate" [integrationName]="integrationName" [tableName]="tablename" [processDate]=dataServiceSelectedDate>
</app-tabulator-table>
</div>
</mat-tab>
</mat-tab-group>
tablulator-table.component.ts
export class TabulatorTableComponent implements OnInit {
#Input() tableName: string;
#Input() processDate: string;
#Input() integrationName: string;
fields: RunDataTableField[];
dataContent: RunDataContent;
tab = document.createElement("div");
tabColumns: any[] = [];
tabRows: any[] = [];
constructor(private clientDataService: ClientDataService) {
}
ngOnInit() {
this.clientDataService.getRunDataContent(this.integrationName, this.processDate, `${this.tableName}.json`).toPromise().then(dataContent =>
{
console.log(this.dataContent)
this.tabColumns = this.buildHeaders(dataContent);
this.tabRows = this.buildRows(dataContent);
this.redraw();
});
}
calculationFormatter = function (cell, formatterParams) {
var value = cell.getValue();
cell.getElement().style.backgroundColor = "#fdd0ff";
return value;
}
buildHeaders(runData: RunDataContent): any[] {
console.log(`${this.tableName}: creating headers object`);
var headers: any[] = [];
runData.schema.fields.forEach(f => {
var c: any = {};
c.title = f.name;
c.field = f.name;
c.headerFilter = "input"
switch (f.validationType) {
case "calculation": {
c.formatter = this.calculationFormatter
break;
}
case "raw": {
}
case "table": {
}
default: {
break;
}
}
if (f.tip != null && f.tip.length > 0) {
c.tooltip = f.tip;
c.headerTooltip = f.tip;
}
headers = headers.concat(c);
});
console.log(`${this.tableName}: createad headers object`);
console.log(this.tabColumns);
return headers;
}
buildRows(runData: RunDataContent): any[] {
console.log(`${this.tableName}: creating rows object`);
var rows: any[] = [];
runData.rows.forEach(f => {
rows = this.tabRows.concat(f);
});
return rows;
}
private drawTable(): void {
new Tabulator(this.tab, {
layout: "fitDataStretch",
selectable: true,
selectableRangeMode: "click",
data: [],
columns: this.tabColumns,
height: "311px"
});
document.getElementById(`${this.tableName}`).appendChild(this.tab);
new Tabulator()
}
redraw() {
console.log(`${this.tableName}: drawing table`)
this.drawTable();
console.log(`${this.tableName}: completed drawing table`)
}
}
According to Angular Material Docs, By default, the tab contents are eagerly loaded. Eagerly loaded tabs will initalize the child components but not inject them into the DOM until the tab is activated.
If the tab contains several complex child components or the tab's contents rely on DOM calculations during initialization, it is advised to lazy load the tab's content.
Tab contents can be lazy loaded by declaring the body in a ng-template with the matTabContent attribute.
So, the correct approach would be to use ng-template with matTabContent. Please find below code for reference :
<mat-tab *ngFor="let table of tableNames" [label]="table">
<ng-template matTabContent>
<div>
<app-tabulator-table *ngIf="dataServiceSelectedDate"
[integrationName]="integrationName" [tableName]="tablename"
[processDate]=dataServiceSelectedDate>
</app-tabulator-table>
</div>
</ng-template>
</mat-tab>

One function calls another function without any specifc call in Angular

I really need help, because I don't know why this is happening.
I have an Angular webapp with a Firebase backend. My webapp shows 7 random products from a products collection in Firebase. This 7 random products should be stored so that, the users can see it on different devices.
But if i call my savemethod, something weird is happening. The save method, calls a the method, who generates the 7 random products and i don't know why, because i don't call it expecially.
Heres my code:
export class ProductComponent implements OnInit {
schedulenumbers: ISchedule = { numbers: [] }; //Just an Object with an array of numbers in it
length: number = 0; //lenght is needed to show or hite the list in the template
filteredproducts: IProduct[] = [];
numbers: number[] = []; // the numbers to filter the products
constructor(private productservice: ProductService) {
console.log("constructor wurde aufgerufen");
}
ngOnInit(): void {
this.filteredproducts = [];
console.log("ngOnInit was called")
//get the numbers from the database
this.productservice.getnumbers().subscribe(
numbers => {
this.numbers = numbers.numbers;
}
);
//get all products from the database collection
this.productservice.getallproducts().pipe(
map((products) => {
this.length = products.length;
for (let i = 0; i < 7; i++) {
this.filteredproducts.push(product[this.numbers[i]]);
}
return this.filteredproducts;
})
).subscribe();
}
getRandomproducts(): void {
this.filteredproducts = [];
this.numbers = [];
console.log("getRandomproducts was called");
this.productservice.getallproducts().pipe(
map(products => {
for (let i = 0; i < 7; i++) {
this.numbers[i] = Math.floor(Math.random() * products.length + 1)
if (products[this.numbers[i]] == undefined) {
i -= 1;
}
else {
this.filteredproducts.push(products[this.numbers[i]]);
}
}
console.log(this.numbers);
return this.filteredproducts;
})
).subscribe();
}
saveNumbers(): void {
console.log("savenumbers was called")
console.log(this.numbers);
this.schedulenumbers.numbers = this.numbers;
console.log(this.filteredproducts.length);
this.productservice.updatenumbers(this.schedulenumbers);
}
}
Here is the code for the productservice:
#Injectable({
providedIn: 'root'
})
export class ProductService {
Productcollection: AngularFirestoreCollection<IProduct>;
schedulenumbers: AngularFirestoreDocument<ISchedule>;
numbers: Observable<ISchedule>
Products$: Observable<IProduct[]>;
constructor(private firestore: AngularFirestore, private auth: AngularFireAuth) {
this.auth.currentUser.then(user => {
this.productcollection = this.firestore.collection(`${user.uid}`);
this.schedulenumbers = this.firestore.collection(`${user.uid}`).doc("numbers")
})
}
getallproducts() {
return this.Products$ = this.Productcollection.valueChanges({ idField: 'ProductId' }).pipe(
map(products =>
products.filter(product => product.ProductId != 'numbers')
)
);
}
getnumbers() {
return this.numbers = this.schedulenumbers.valueChanges();
}
updatenumbers(numbers: ISchedule) {
this.schedulenumbers.update(numbers)
}
addrecipe(product: IProduct) {
this.Productcollection.add(product);
}
}
I have attached a picture from the Firefox console that shows that if I press the button for the getRandomproducts method twice and then I press the button for the saveNumbers method, strangely white from the saveNumbers method, the for loop of getrandomproduct number method is called and in the filteredproducts array are not longer only 7, but much more products. But why?
Firefox console

unable to select all checkboxes in tree using angular2-tree on init

Objective : i have a button named "feed data" so when ever i click it the data will be loaded i mean the tree with checkboxes here my requirement is when ever i click it along with data all the check boxes have to be checked on init i tried using
this.treeComp.treeModel.doForAll((node: TreeNode) => node.setIsSelected(true));
but it is not working below is my code
click(tree: TreeModel) {
this.arrayData = [];
let result: any = {};
let rs = [];
console.log(tree.selectedLeafNodeIds);
Object.keys(tree.selectedLeafNodeIds).forEach(x => {
let node: TreeNode = tree.getNodeById(x);
// console.log(node);
if (node.isSelected) {
if (node.parent.data.name) //if the node has parent
{
rs.push(node.parent.data.name + '.' + node.data.name);
if (!result[node.parent.data.name]) //If the parent is not in the object
result[node.parent.data.name] = {} //create
result[node.parent.data.name][node.data.name] = true;
}
else {
if (!result[node.data.name]) //If the node is not in the object
result[node.data.name] = {} //create
rs.push(node.data.name);
}
}
})
this.arrayData = rs;
tree.selectedLeafNodeIds = {};
}
selectAllNodes() {
this.treeComp.treeModel.doForAll((node: TreeNode) => node.setIsSelected(true));
// firstNode.setIsSelected(true);
}
onTreeLoad(){
console.log('tree');
}
feedData() {
const results = Object.keys(this.data.info).map(k => ({
name: k,
children: this.data.info[k].properties
? Object.keys(this.data.info[k].properties).map(kk => ({ name: kk }))
: []
}));
this.nodes = results;
}
feedAnother() {
const results = Object.keys(this.dataa.info).map(k => ({
name: k,
children: this.dataa.info[k].properties
? Object.keys(this.dataa.info[k].properties).map(kk => ({ name: kk }))
: []
}));
this.nodes = results;
}
onActivate(event) {
this.selectedDataList.push(event.node.data);
console.log(this.selectedDataList)
}
onDeactivate(event) {
const index = this.selectedDataList.indexOf(event.node.data);
this.selectedDataList.splice(index, 1);
console.log(this.selectedDataList)
}
below is my stackblitz https://stackblitz.com/edit/angular-hrbppy
Use updatedata and initialized event to update the tree view to check all checkboxes.
app.component.html
<tree-root #tree *ngIf ="nodes" [nodes]="nodes" [options]="options" [focused]="true"
(initialized)="onTreeLoad()"
(updateData)="updateData()"
(select)="onActivate($event)"
(deselect)="onDeactivate($event)">
</tree-root>
It'll initiate tree-root component only if nodes variable is available,
then in the initialized and updateData event call selectAllNodes method to select all checkboxes.
app.component.ts
updateData() {
this.selectAllNodes();
}
onTreeLoad(){
this.selectAllNodes();
}
Refer to this slackblitz for working example.
just, in your function feed data call to your function this.selectAllNodes() enclosed in a setTimeout. You can see your forked stackblitz
setTimeout(()=>{
this.selectAllNodes()
})
NOTE: I see in your code you try to control in diferents ways the items selected. I simplified using a recursive function.
In this.treeComp.treeModel.selectedLeafNodeIds we have the items that are changed, so
getAllChecked()
{
const itemsChecked=this.getData(
this.treeComp.treeModel.selectedLeafNodeIds,null)
console.log(itemsChecked);
}
getData(nodesChanged,nodes) {
nodes=nodes||this.treeComp.treeModel.nodes
let data: any[] = []
nodes.forEach((node: any) => {
//in nodesChanged we has object like {1200002:true,123132321:false...}
if (nodesChanged[node.id]) //can be not changed, and then it's null because
//it's not in object or can be changed to false
data.push({id:node.id,name:node.name})
//or data.push(node.name); //if only need the "name"
if (node.children)
data=[...data,...this.getData(nodesChanged,node.children)]
}
);
return data
}
Updated I updated the function getData to include the "parent" of the node, but looking the code of #Raghul selvam, his function like me more than mine.
getData(nodesChanged,nodes,prefix) {
nodes=nodes||this.treeComp.treeModel.nodes
let data: any[] = []
nodes.forEach((node: any) => {
if (nodesChanged[node.id])
data.push(prefix?prefix+"."+node.name:node.name)
if (node.children)
data=[...data,...this.getData(nodesChanged,node.children,prefix?prefix+"."+node.name:node.name)]
}
);
return data
}
And call it as
this.getData(this.treeComp.treeModel.selectedLeafNodeIds,null,"")
You could add this in your onTreeLoad function. You could add a boolean flag(treeLoaded) for tracking if the tree has loaded or not.
onTreeLoad(tree){
this.selectAllNodes();
this.treeLoaded = true;
}

Angular create Dynamic Component recursively

I am trying to build a dynamic component based on a Config. The component would read the config recursively and create the component. It is found that the method ngAfterViewInit() would only be called twice.
#Component({
selector: "dynamic-container-component",
template: `
<div #container
draggable="true"
(dragstart)="dragstart($event)"
(drop)="drop($event)"
(dragover)="dragover($event)"
style="border: 1px solid; min-height: 30px"></div>
`
})
export default class DynamicContainerComponent {
#Input()
dynamicConfig: DynamicConfig;
#ViewChild("container", {read: ElementRef})
private elementRef: ElementRef;
private isContainer: boolean;
private componentRef: ComponentRef<any>;
private componentRefs: ComponentRef<any>[] = [];
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private injector: Injector,
private viewContainer: ViewContainerRef,
private render: Renderer2
){
console.log("running");
}
ngAfterViewInit(){
if (this.dynamicConfig){
console.log(this.dynamicConfig)
if (this.dynamicConfig.getType() == ComponentType.INPUT){
this.isContainer = false;
let componetFactory: ComponentFactory<InputComponent> =
this.componentFactoryResolver.resolveComponentFactory(InputComponent);
this.componentRef = this.viewContainer.createComponent(componetFactory);
this.render.appendChild(this.elementRef.nativeElement, this.componentRef.location.nativeElement);
}else {
this.isContainer = true;
let items: DynamicConfig[] = this.dynamicConfig.getItems();
if (items){
for (var i=0; i<items.length; i++){
let item: DynamicConfig = items[i];
let componetFactory: ComponentFactory<DynamicContainerComponent> =
this.componentFactoryResolver.resolveComponentFactory(DynamicContainerComponent);
let componentRef: ComponentRef<DynamicContainerComponent> =
this.viewContainer.createComponent(componetFactory);
componentRef.instance.dynamicConfig = item;
this.componentRefs.push(componentRef);
this.render.appendChild(this.elementRef.nativeElement, componentRef.location.nativeElement);
}
}
}
}else {
console.log("config does not exist");
}
}
dragstart(event){
debugger;
}
drop(event){
debugger;
}
dragover(event){
debugger;
event.preventDefault();
}
}
The Component would be created by other component by the following code. If The Dynamic Component would create another Dynamic Component by componentFactoryResolver.
var configJson = {
type: ComponentType.CONTAINER,
items: [
{
type: ComponentType.CONTAINER,
items: [{
type: ComponentType.CONTAINER,
items: [{
type: ComponentType.CONTAINER,
items: [{
type: ComponentType.INPUT
}]
}]
}]
}
]
}
this.config = new DynamicConfig();
this.config.assign(configJson);
console.log(this.config);
Update
I found a similar issue in github: https://github.com/angular/angular/issues/10762
I have done something suggested by other people. but I think it is just a dirty fix.
ngAfterViewInit(){
setTimeout(function(){
if (this.dynamicConfig){
console.log(this.dynamicConfig)
if (this.dynamicConfig.getType() == ComponentType.INPUT){
this.isContainer = false;
let componetFactory: ComponentFactory<InputComponent> =
this.componentFactoryResolver.resolveComponentFactory(InputComponent);
this.componentRef = this.viewContainer.createComponent(componetFactory);
this.render.appendChild(this.elementRef.nativeElement, this.componentRef.location.nativeElement);
}else {
this.isContainer = true;
let items: DynamicConfig[] = this.dynamicConfig.getItems();
if (items){
for (var i=0; i<items.length; i++){
let item: DynamicConfig = items[i];
let componetFactory: ComponentFactory<DynamicContainerComponent> =
this.componentFactoryResolver.resolveComponentFactory(DynamicContainerComponent);
let componentRef: ComponentRef<DynamicContainerComponent> =
this.viewContainer.createComponent(componetFactory);
componentRef.instance.dynamicConfig = item;
this.componentRefs.push(componentRef);
this.render.appendChild(this.elementRef.nativeElement, componentRef.location.nativeElement);
}
}
}
}else {
console.log("config does not exist");
}
}.bind(this))
}
By the time you create your dynamic component angular has almost finished change detection cycle.
This way you can either run:
componentRef.changeDetectorRef.detectChanges()
Note: setTimeout has similar effect but fires change detection cycle on the whole app
or rename lifecycle hook to ngOnInit
Also you're passing wrong input to dynamic component:
let item: DynamicConfig = items[i];
^^^^^^^^^^^^^
but it is not DynamicConfig instance but rather plain object
...
componentRef.instance.dynamicConfig = item;
it should be:
let item: any = items[i];
const config = new DynamicConfig();
config.assign(item);
componentRef.instance.dynamicConfig = config;
Ng-run Example

Angular 5 input array from other component , cause length 0

I put the collected data from services into the array. I put this array through #Input into the second component but in it the array instead of length 18 has 0;
TS:
arr: Datas[] = [];
constructor(private dataService: DataService) {
}
ngOnInit() {
console.log("ng init");
this.getArraysFromData();
}
getArraysFromData() {
this.DataService.getDatas().subscribe((data: Datas[]) => {
for (let item of data) {
this.arr.push(item);
}
console.log("smartlamps from Map ", this.arr);
});
}
}
HTML :
<app-osm-generator [dataInput]="arr"></app-osm-generator>
COMPONENT WHERE I INPUT
#Input() dataInput: Datas[];
ngOnInit(): void {
this.takeDataFromInput();
}
takeDataFromInput() {
console.log(this.dataInput.length); <-- is 0 must be 18
for(let item of dataInput) {
console.log(item);
}
}
You are getting console.log(dataInput.length); coz its is being called before data is assigned
There are 2 ways you can solve the issue :
1) Include app-osm-generator only when data is available
<app-osm-generator *ngIf="arr.length > 0" [dataInput]="arr"></app-osm-generator>
2) implements OnChanges
ngOnChanges(changes: SimpleChanges) {
let data = changes.dataInput;
console.log('prev value: ', data.previousValue);
console.log('got name: ', data.currentValue);
console.log(data.length);
}
Checking console will clear all your doubts regarding the flow
For more details on 2nd method : READ
Suggestion :
this.DataService.getDatas().subscribe((data: Datas[]) => {
this.arr = [ ...this.arr , ...data]; // instead of looping try out ES6's feature
console.log("smartlamps from Map ", this.arr);
});
I don't know if is a mistake in the question but not this.DataService because DataService is the Service declaration and dataService is the instance injected..
this.DataService.getDatas().subscribe((data: Datas[]) => {
for (let item of data) {
this.arr.push(item);
}
console.log("smartlamps from Map ", this.item);
Good:
this.dataService.getDatas().subscribe((data: Datas[]) => { // good!
for (let item of data) {
this.arr.push(item);
}
console.log("smartlamps from Map ", this.item);
add a ngIf
<app-osm-generator *ngIf="arr" [dataInput]="arr"></app-osm-generator>

Categories