How to build a tree node with Angular 6? - html

I have to generate a tree node with Angular. The object is called Page and each Page can have children and those children can also have their own children and so on.
Now I have to show the hierarchical structure in HTML from my Angular Page object.
Also, what I would want to indent the children like this:
Page parent #1
.... Child page of #1
......... Child of child page #1
Page parent #2
.... Child page of #2
........ Child of child page #2
You get the idea.
My page object is:
export class PageModel {
id!: number;
title?: string | undefined;
typeId?: string | undefined;
parentId?: number | undefined;
children?: PageModel[] | undefined;
publishedDateLocal?: string | undefined;
}
My html component is for now a simple table...
<table class="table">
<thead>
<tr>
<td>Title</td>
<td>Page Type</td>
<td>Published</td>
<td></td>
</tr>
</thead>
<tbody>
<tr *ngFor="let page of model.pageModels">
<td>{{ page?.title }}</td>
<td>{{ page?.pageTypeId }}</td>
<td>{{ page?.publishedDateLocal }}</td>
<td>
<a class="remove" (click)="deletePage(page.id)" [routerLink]="">
<span class="glyphicon glyphicon-trash text-danger"></span>
</a>
<a [routerLink]="['/pageAddEdit', page.id]">
Edit
</a>
</td>
</tr>
</tbody>
</table>
Thank you for any help

I would recommend using a recursive structure for this.
Create a very simply component that takes PageModel as an input. You can then display that, and look through each of the children using *ngFor, like so:
#Component({
selector: 'hello',
template: `
<li>{{model.title}}</li>
<ul>
<hello *ngFor="let child of model.children" [model]="child"></hello>
</ul>
`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent {
#Input() model: PageModel;
}
Here is a simple
StackBlitz example

Related

how do I access nested json data with vuejs>

I would like to create a table and populate it with data using vue.js and v-for but I don`t know how to access the nested JSON file.
If I simply call {{items}} the data is presented but there is no way i manage to filter it
here is my code:
<template>
<div id="app">
<thead>
</thead>
<tbody>
<table>
<thead>
<tr>
<th>id</th>
<th>press</th>
<th>date</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items" :key="item.id">
<td>{{ item.id }}</td>
<td>{{ item.results.downloadable.document_en }}</td>
<td>{{ item.}}</td>
</tr>
</tbody>
</table>
</tbody>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
items:[]
}
},
created() {
axios.get(`https://zbeta2.mykuwaitnet.net/backend/en/api/v2/media-center/press-release/?page_size=61&type=5`)
.then(response => {
this.items = response.data
})
}
}
</script>
Based on the result of your endpoint you should change your assignment of items to
.then(response => {
this.items = response.data.results
})
And your loop to
<tr v-for="item in items" :key="item.id">
<td>{{ item.id }}</td>
<!-- as downloadable is an array, see update below etc. -->
</tr>
But be aware - if you assign the data.results directly you will lose the so called "paginator" information that also contains the link to load more.
So another option would be to assign
this.items = response.data
HOWEVER, be aware that you should then define items in your data as null or empty object (not array, as this would be false)
And then change your loop to something like this (it's now looping in item.results)
<tbody v-if="items && items.results">
<tr v-for="item in items.results" :key="item.id">
<td>{{ item.id }}</td>
<!-- as downloadable is an array - see Update below etc. -->
</tr>
</tbody>
This approach would allow you to show the total count via items.count for example
UPDATE:
Actually downloadable is an array! I can only assume what you actually want to achieve to here. I've created a jsfiddle to showcase it: https://jsfiddle.net/v73xe4m5/1/
The main thing you probably want to do is filter the entry to only show entries where downloadable contains a document_en.
<tr v-for="item in items.results" :key="item.id">
<td>{{ item.id }}</td>
<td>
<div class="downloads">
<span
v-for="downloadable in item.downloadable.filter(d => !!d.document_en)"
:key="downloadable.id"
>{{ downloadable.document_en.file }}</span>
</div>
</td>
</tr>
I'm not familiar with that endpoint / api - so I don't know if it might return more than one relevant document per item.
As you can see I used a second v-for loop inside the <td> in order to go through all downloadable entries. Before doing so, they are filtered, so only entries that actually have a document_en value are shown. You can adapt this as you want.
Hope that helps!
this is the correct form to get the array from the json and save to this.items
this.items = response.data.results
I encourage you to always console.log(response) after api call , and access data according to the structure of the api results

Splitting hyperlinks in Angular4

I am new to angular4 working over MEAN Stack My data is coming from Mongo DB in the form of a list of hyperlinks.Now I want that when I click over each link in UI, it should be opened as a separate link but as of now, it is opening a combined link i.e. it is taking as a single entry.
I am trying to pass ';' after each link in Mongo Db and at the UI level I am trying to separate/split based on each ';'.
<tbody>
<tr *ngFor="let item of items">
<td> {{ item.SNo }}</td>
<td> {{ item.UseCase }}</td>
<td>
{{ item.ReferenceMaterials }}
</td>
</tr>
</tbody>
My structure of JSON is:
{
"_id":"537437505593",
"SNo" :"1",
"UseCase":"hfwioegepepohgy",
"Focus":"hellow world",
"RefernceLinks":"link1";"link2";"link3"
}
These links are rendered to UI as link1;link2;link3.Clicking over link1 click over all other links also.Kindly help.
You need to create a custom pipe
#Pipe({
name: 'split'
})
export class SplitPipe implements PipeTransform {
transform(val:string, separator:string):string[] {
return val.split(separator);
}
}
And use it like this
<a *ngFor="let link of item.ReferenceMaterials|split" href="{{link}}">{{link}}</a>
Or item.RefernceLinks like your json shows...
Use ngFor.
<a *ngFor="let link of item.ReferenceMaterials" href="{{link}}">{{link}}</a>

collapsible lists with *ngFor

As it looks in the photo, that I want is, when I click show more, I want to expand this row, and show me all sensors in there
I'm using materialize, and my code html is:
<table class="bordered table-bordered" [mfData]="homeboxsp | dataFilter : filterQuery" #mf="mfDataTable" [mfRowsOnPage]="rowsOnPage"
[(mfSortBy)]="sortBy" [(mfSortOrder)]="sortOrder">
<thead style="color:black; background:rgb(207, 235, 245);border:1px solid rgb(190, 190, 190);">
<tr>
<th>
<mfDefaultSorter by="client">Client</mfDefaultSorter>
</th>
<th>
<mfDefaultSorter by="description">Homebox</mfDefaultSorter>
</th>
<th>
<mfDefaultSorter by="sensors">Sensors</mfDefaultSorter>
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of homeboxsp">
<td>{{item.client}}</td>
<td>{{item.description}}</td>
<td>
<ul>
<ul *ngFor="let sensor of item.sensors">
<li>{{sensor.sensor_serial}}</li>
</ul>
</ul>
</td>
</tr>
</tbody>
</table>
you can do this in simple way using ngIf
<ul *ngFor="let sensor of item.sensors;let i = index;">
<li *ngIf="i < value">{{sensor.sensor_serial}}</li>
<button (click)="showmore()">showmore</button>
</ul>
ts
value:number = 2;
showmore(){
let value = this.value;
this.value= value+1;
}
you can modify logic as you want
The SlicePipe is perfect for this kind of situations. It does pretty much the same as the accepted answer with fewer *ngIf needed.
#Component({
selector: 'my-app',
template: `
<div>
<div *ngFor="let val of values | slice:0:visible">{{val}}</div>
<div *ngIf="visible==3" (click)="visible=values.length">show more</div>
<div *ngIf="visible==values.length" (click)="visible=3">show less</div>
</div>
`,
})
export class App {
visible= 3;
values = [1, 2,3,4, 5,6,7,8,9]
constructor() {
}
}
You can see a working example here.
The same principle applies if you want to have multiple lists that expand. You just need to control the visibility for each list separately.
Here's an example of that.
#Component({
selector: 'my-app',
template: `
<div>
<div *ngFor="let group of groups">
<div>{{group.name}}</div>
<div *ngFor="let val of group.values | slice:0:(group.expanded? group.values.length :3)">{{val}}</div>
<div *ngIf="!group.expanded" (click)="group.expanded=true">show more</div>
<div *ngIf="group.expanded" (click)="group.expanded=false">show less</div>
</div>
</div>
`,
})
export class App {
groups = [{name: 'a', values: [1, 2,3,4, 5,6,7,8,9]},{name: 'b', values: [1, 2,3,4, 5,6,7,8,9]}]
constructor() {
}
}
And the working plnkr here.
You can build a simple component that can show just a certain number of sensors, or adjust it to show just certain height:
<td><values-display-cmp [values]="row"></values-display-cmp></td>
Then the component:
#Component({
selector: 'values-display-cmp',
template: `
<ul>
<li *ngFor="let val of shownValues">{{val}}</li>
</ul>
More
`
})
export class ValuesDisplayComponent {
#Input() set values(values) {
this._values = values;
this.shownValues = values.slice(0, 2);
};
open = false;
shownValues = [];
_values = [];
more() {
this.open = true;
this.shownValues = this._values;
}
}
Here's a full example.
Separate your array in 2 parts, and after clicking show more concat them. Seems like not a big deal.

Create an Angular component to display a set of html table cells to embed in other tables

I have a lot of html tables that share the same 15 columns along with custom columns that are specific for each table. I would like to create these common 15 columns as a component that takes in a set of data and shows up as the 15 td's without a wrapper. I can't figure out how to create an Angular component that doesn't show the wrapper tag in the DOM and allows me to pass in input. Below is kind of what I'd like to do:
<tr *ngFor="let item of items">
<td>Column 1</td>
<my-common-columns [data]="item"></my-common-columns>
<td>Another column</td>
</tr>
Unfortunately with the above code, <my-common-columns> tag shows up in the rendered DOM, which messes up the table. I can only have td's under the tr tag.
I also tried <ng-container *ngComponentOutlet="myCommonColumns"> since ng-container's don't show up in the DOM, but I can't figure out how to pass data into it.
import { Component, TemplateRef, ViewChild } from '#angular/core';
#Component({
selector: 'my-common-columns',
template: `<ng-template let-item>
<td>inner item1:{{item}}</td>
<td>inner item2:{{item}}</td>
</ng-template>`
})
export class CommonComponent {
#ViewChild(TemplateRef) template: TemplateRef<any>;
}
#Component({
selector: 'my-app',
template: `
<table>
<tr *ngFor="let item of data">
<td>first</td>
<ng-template [ngTemplateOutlet]="common.template"
[ngTemplateOutletContext]="{$implicit: item}">
</ng-template>
<td>last</td>
</tr>
</table>
<my-common-columns #common></my-common-columns>
`
})
export class AppComponent {
data = [1, 2, 3, 4, 5];
}
live example
result:
ngComponentOutlet will show tag in HTML too.
Either, you should use abstraction to display tables for complex problems.
Depending on your data structure you could probably do something like:
<td *ngFor="let col of item">{{ col }}</td>
You have to use your component as a attribute.
So put it to <td my-common-column></td> and in your component change selector: [my-common-column]. These [ ] brackets allows you to use component as attribute.
Why don't you use the code below?
<tr *ngFor="let item of items">
<td>Column 1</td>
<td> {{ item }}</td>
<td>Another column</td>
</tr>

why table cell is not reading the table row in parent component?

I have 2 components here: app.component.html and app-server.component.html
app.component.html
<table class="table" style="width:200px;">
<tr>
<td>Names</td>
<td>Age</td>
</tr>
<tr><app-server-element *ngFor="let student of students" [student]='student'></app-server-element></tr>
</table>
server-element.component.html:
<td class="column">{{student.name}}</td>
<td class="column">{{student.age}}</td>
but the result is like this why?:
In addition to the problem others have suggesting - there seems to be another problem. Your <app-server-element *ngFor="let student of students" [student]='student'></app-server-element> acts as a cell because it is a direct child of your tr. The td elements inside app-server-element have no effect. You could use replace in your directive to get rid of the wrapper - that way the td element would be a direct child of the tr element and it would work as expected.
Edit
After gathering more information regarding your problem, please make the following changes to your code:
Change the server-element.component.ts to:
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: '[appServerElement]',
templateUrl: './server-element.component.html',
styleUrls: ['./server-element.component.css']
})
export class ServerElementComponent implements OnInit {
#Input()
appServerElement: { name: string, age: string };
constructor() {
//
}
ngOnInit() {
//
}
}
and change selector: 'app-server-element' to selector: '[appServerElement]'.
Change the app.component.html to the following:
<table class="table" style="width:200px;">
<tr>
<td>Names</td>
<td>Age</td>
</tr>
<tr [appServerElement]="student" *ngFor="let student of students"></tr>
</table>
As ankit suggested you need to iterate of <tr>:
<table class="table" style="width:200px;">
<tr>
<td>Names</td>
<td>Age</td>
</tr>
<tr *ngFor="let student of students"><app-server-element *ngFor="let student of students" [student]='student'></app-server-element></tr>
your code iterate over app-server-element which results:
<tr>
<app-server-element></app-server-element>
<app-server-element></app-server-element>
<app-server-element></app-server-element>
<app-server-element></app-server-element>
</tr>
while you want: <tr><app-server-element></app-server-element></tr>
<tr><app-server-element></app-server-element></tr>
<tr><app-server-element></app-server-element></tr>