Randomize and Limit ngFor List Items on HTML - html

I have a large JSON file and am trying to randomize and limit the ones that are shown to a list item on HTML. The file has more than 100 items but I would like to show only ten at a time.
I understand doing this by pagination on the server side would be better but this project is to be used locally and for learning purposes only.
This is a project based on this repository. It is basically the same but I would like handle more items by paginating it on the client side(hero-list.component.html). It would be worth checking it out. Otherwise I will paste below the important part.
Can someone help me?
hero.service.ts
import { Injectable } from '#angular/core';
import {
EntityCollectionServiceBase,
EntityCollectionServiceElementsFactory
} from '#ngrx/data';
import { Hero } from '../core';
#Injectable({ providedIn: 'root' })
export class HeroService extends EntityCollectionServiceBase<Hero> {
constructor(serviceElementsFactory: EntityCollectionServiceElementsFactory) {
super('Hero', serviceElementsFactory);
}
}
hero-list.component.ts
import {
Component,
EventEmitter,
Input,
Output,
ChangeDetectionStrategy
} from '#angular/core';
import { Hero } from '../core';
#Component({
selector: 'app-hero-list',
templateUrl: './hero-list.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HeroListComponent {
#Input() heroes: Hero[];
#Output() deleted = new EventEmitter<Hero>();
#Output() selected = new EventEmitter<Hero>();
selectHero(hero: Hero) {
this.selected.emit(hero);
}
deleteHero(hero: Hero) {
this.deleted.emit(hero);
}
// trackByHero(hero: Hero): string {
// return hero.id;
// }
trackByHero(_ /* index not used */: number, hero: Hero): string {
return hero.id;
}
}
hero-list.component.html
<ul class="list">
<li
*ngFor="let hero of heroes; trackBy: trackByHero; let i = index"
role="presentation"
>
<div class="card">
<app-card-content
[name]="hero.name"
[description]="hero.description"
></app-card-content>
<footer class="card-footer">
<app-button-footer
class="card-footer-item"
[className]="'delete-item'"
[iconClasses]="'fas fa-trash'"
(clicked)="deleteHero($event)"
label="Delete"
[item]="hero"
></app-button-footer>
<app-button-footer
class="card-footer-item"
[className]="'edit-item'"
[iconClasses]="'fas fa-edit'"
(clicked)="selectHero($event)"
label="Edit"
[item]="hero"
></app-button-footer>
</footer>
</div>
</li>
</ul>
db.json:
{
"heroes": [
{
"id": "HeroAslaug",
"name": "Aslaug",
"description": "warrior queen"
},
{
"id": "HeroBjorn",
"name": "Bjorn Ironside",
"description": "king of 9th century Sweden"
},
{
"id": "HeroIvar",
"name": "Ivar the Boneless",
"description": "commander of the Great Heathen Army"
},
{
"id": "HeroLagertha",
"name": "Lagertha the Shieldmaiden",
"description": "aka Hlaðgerðr"
},
{
"id": "HeroRagnar",
"name": "Ragnar Lothbrok",
"description": "aka Ragnar Sigurdsson"
},
{
"id": "HeroThora",
"name": "Thora Town-hart",
"description": "daughter of Earl Herrauðr of Götaland"
}
]
}
Appreciate it!

A "pagination" if you has all the elements in an array is simply use slice pipe
You has two variables
page:number=0 //the first page is 0
pageSize:number=10
<li *ngFor="let hero of heroes|slice:(page*pageSize):(page+1)*pageSize;
let i = index">
...
</li>
To randomize an array simply use
arrayRandom=this.heroes.map(x=>({ord:Math.random(),data:x}))
.sort((a,b)=>a.ord>b.ord?1:a.ord<b.ord?-1:0)
.map(x=>x.data)

Related

How to display mock data in html in Angular

I have created mock service file and I want to display in my html but not really sure how to make it display properly so I'll be really appreciated If I can get any help or suggestion.
<div class="container2">
<div class="header" style="height: 400px">
<div class="content3">
<div>
<h1 class="kpi-title">90,346</h1>. // I'm trying to remove this hard code html
<p class="kpi-des">Users Right Now</p> // and display it from my mock data file.
</div>
<div>
<h1 class="kpi-title">250++l</h1>
<p class="kpi-des">Saved</p>
</div>
<div>
<h1 class="kpi-title">$34.5 mill</h1>
<p class="kpi-des">New User per Week</p>
</div>
</div>
</div>
</div>
TS
import { ProductService } from '../../data/product-suite.service';
export class MaxisProductSuiteComponent {
productService: ProductService[];
ngOnIT(){
}
product-suite.service.ts
export class ProductService {
productsuite: ProductSuite[] = [
{
id: 1,
title: '90,346',
description: 'Users',
},
{
id: 2,
title: '$34.5 mill',
description: 'Saved',
},
{
id: 3,
title: '250++',
description: 'New User per Week',
},
];
}
Please find the below code for your solutions:
create a json file assets folder with name output.json.
{
"result" : [
{
"id": 1,
"title": "90,346",
"description": "Users at Ford"
},
{
"id": 2,
"title": "$34.5 mill",
"description": "Saved for Ford"
},
{
"id": 3,
"title": "250++",
"description": "New User per Week"
},
{
"id": 4,
"title": "64%",
"description": "Users At Ford"
}
]
}
in service file write below code:
import { observable, Observable } from "rxjs";
import { MaxisProductSuite } from "src/Model/model";
import { HttpClient } from '#angular/common/http';
import { Injectable } from "#angular/core";
#Injectable()
export class MaxisProductService {
constructor(private http: HttpClient){}
getAllMaxisps():Observable<MaxisProductSuite> {
return this.http.get<MaxisProductSuite>("./assets/output.json");
}
}
then component file add below code:
import { DOCUMENT } from '#angular/common';
import { Component, Inject, OnInit } from '#angular/core';
import { MaxisProductSuite } from 'src/Model/model';
import { MaxisProductService } from 'src/service/MaxisProductService';
#Component({
selector: 'app-temp',
templateUrl: './temp.component.html',
styleUrls: ['./temp.component.scss']
})
export class TempComponent implements OnInit {
maxisps: MaxisProductSuite[];
public resultData:MaxisProductSuite=null;
constructor(#Inject(DOCUMENT) private document: Document, private service : MaxisProductService) {}
ngOnInit() {
this.service.getAllMaxisps().subscribe((res:MaxisProductSuite) => {
console.log(res);
this.resultData =res;
});
}
}
then HTMl file add below code:
<div *ngFor="let item of resultData?.result">
<div class="header" style="height: 400px">
<h1>{{item.id}}</h1>
<h2>{{item.title}}</h2>
<h3>{{item.description}}</h3>
</div>
add below model in model file
export interface MaxisProductSuite {
result : Result[]
}
export interface Result{
id?: number;
title: string;
description: string;
}
I hope it will help you to get the solution.
happy to HELP!
make your service in module provider or make it injectable "root".
inject the service in your component you want to display data in constructor as a dependency injection.
assign your component variable array with productService.
in your HTML loop about your data array using *ngFor=" let product of products".
use you product value in the interpolation {{ product.id }}.

Angular 8 - Implementing a hierarchical list recursively with dynamic expansion and collapse

I am trying to build a hierarchical expand/collapse list to represent a parent child relationship. Upon Initial load, the parent nodes will be listed. If they have children, a carat icon is displayed otherwise, a bullet icon is displayed. Upon clicking the carat icon, an API call is made to retrieve data and the child information is displayed. Upon clicking the same carat icon again, no API call is made, but the immediate child list is hidden. This should happen recursively in case the children are parent nodes.
Desired behavior on Initial load:
Desired behavior after carat icon is clicked to expand:
JSON Data
{
"page": {
"results": [{
"id": "1001",
"title": "American",
"children": {
"page": {
"results": [{
"id": "1003",
"title": "Chevy",
"children": {
"page": {
"results": [],
"start": 0,
"limit": 25,
"size": 0
}
}
}],
"start": 0,
"limit": 25,
"size": 1
}
}
}, {
"id": "1002",
"title": "German",
"children": {
"page": {
"results": [],
"start": 0,
"limit": 25,
"size": 0
}
}
}],
"start": 0,
"limit": 2,
"size": 2
}
}
Component Code
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
import { WikiService } from '../wiki.service';
import { WikiTree } from '../../interfaces/WikiTree';
import { AppConfigService } from '../../services/app-config.service';
#Component({
selector: 'app-wiki-tree-nav',
templateUrl: './wiki-tree-nav.component.html',
styleUrls: ['./wiki-tree-nav.component.css']
})
export class WikiTreeNavComponent implements OnInit {
public wikiPageId: any = '1000';
wikiTree$: Observable<WikiTree>;
public resultsPage: any;
constructor(private wikiService: WikiService, private route: ActivatedRoute,
private appConfigService: AppConfigService) {
this.route.params.subscribe(res => this.wikiPageId = res.id);
}
ngOnInit() {
this.getWikiTree(0, 200, this.wikiPageId);
}
getWikiTree(start: number = 0, limit: number = 200, pageId: string = '') {
this.wikiTree$ = this.wikiService.GetWikiTree(start, limit, pageId);
this.wikiTree$.subscribe((data) => {
this.resultsPage = data;
},
(err) => {
}
);
}
getCSSClass(pageId: string) {
let cssClass = '';
if (this.wikiPageId === pageId) {
cssClass = 'boldTitle';
}
return cssClass;
}
}
HTML Code
<ul *ngIf="wikiTree$ | async as wikiTree">
<li *ngFor="let result of wikiTree.page.results; index as i" style="margin-bottom:10px;">
<div>
<span *ngIf="result.children.page.size==0" style="margin-left:4px;margin-right:4px;">
<clr-icon shape="circle" class="is-solid" size="6"></clr-icon>
</span>
<span *ngIf="result.children.page.size>0">
<a (click)="getWikiTree(0,200,result.id)">
<clr-icon shape="caret right" size="14"></clr-icon>
</a>
</span>
<span style="margin-left:5px;font-size:14px;" [ngClass]="getCSSClass(result?.id)">
{{result.title}}
</span>
</div>
</li>
</ul>
My current HTML code replaces the entire tree instead of appending to the parent nodes. How can I implement the Angular HTML code to perform the expansion and collapse recursively?
I go to create a structural directive, so my app.component.html becomes like
<div recursive [children]="data" [search]="dataService.getData"></div>
where I defined
constructor(public dataService:DataService){}
ngOnInit()
{
this.dataService.getData(0).subscribe(res=>this.data=res)
}
I need pass the "search" function to the recursive component that return an Observable of an array of objects, the objects has as properties 'id','label' and 'hasChildren' to indicate if has children or not, e.g. the json response becomes like
[{id:1,label:'label1',hasChildren:true},{id:2,label:'label2'}]
In a recursive component I like defined as #Input() level,children and parent. As you want to, in a click call to a function that get the "children", I add the search and in a "open" function, I change the property "isOpen" and call to the searchFunction. See that in the object that manage the component, at first there're no property 'children' nor 'isOpen', I add these "on-fly"
#Component({
selector: "[recursive]",
templateUrl: "./tree-view.component.html",
styleUrls: ["./tree-view.component.css"]
})
export class TreeViewComponent {
#Input() level: number;
#Input() children: any;
#Input() parent: any;
#Input() search: (any) => Observable<any>;
self = this;
open(item) {
item.isOpen = !item.isOpen;
if (!item.children) {
item.loading="...."
this.search(item.id).subscribe(res=>{
item.loading=null
item.children=res;
})
}
}
}
With this conditions, our tree-view.html is like
<ul class="tree" *ngIf="level==undefined">
<ng-container *ngTemplateOutlet="tree;context:{children:children,search:search}">
</ng-container>
</ul>
<ng-container *ngIf="level!=undefined">
<ng-container *ngTemplateOutlet="tree;context:{children:children,search:search}">
</ng-container>
</ng-container>
<ng-template #tree let-children="children" let-search="search">
<li *ngFor="let item of children">
<div (click)="item.hasChildren && open(item)">
<span [ngClass]="!item.hasChildren?'doc':item.isOpen?'open':'close'" ></span>
{{item.label}}{{item.loading}}
</div>
<ul recursive *ngIf="item.children && item.isOpen"
[children]="item.children"
[parent]="self"
[level]="level!=undefined?level+1:0"
[search]="search"
>
</ul>
</li>
</ng-template>
see stackblitz, I hope this help you
NOTE: I use three css class, .doc,.open and .close to indicate the states of the tree and a "fool" service with of and delay to simulate a call

Replace an Url from Json with a button that gives URL to another function

I’m trying to implement a function in my web application, that can discover URLs in a loaded Json via Regular Expressions using angular. Afterwards the URLs get replaced with buttons and when the button gets clicked the exact URL who got replaced gets handed in another function in another component which loads the given URL.
Until now I’m at the point that I can replace the URL of the loaded JSON with a button. I’m using a pipe for that named transform-url.pipe:
import {
Pipe,
PipeTransform,
Input,
Component
} from '#angular/core';
import {
DomSanitizer
} from "#angular/platform-browser";
#Pipe({
name: 'transformUrl',
pure: false
})
export class TransformUrlPipe implements PipeTransform {
constructor(protected sanitizer: DomSanitizer) {}
transform(value: any, ): any {
if (value.match === 0) {
return value;
}
return this.sanitizer.bypassSecurityTrustHtml(
value.replace(/"url:\/\/.*\/.*\/.*"/g, "<button type='button' >Run</button>")
);
}
}
Hmtl:
<h3>Unrecognized JSON data:</h3>
<pre [innerHTML] = "genericJson | transformUrl"></pre>
Sample of Json:
"documentVersion": "1.0",
"documentType": "Urls",
"name": {
"request": {
"version": "1.0",
"abc": [
{
"productUrl": "url://awrtwtgsfgshsfh/sfg/v1/document/jsfhljhl564356lhgljhsljh5895hj",
"attributes": [
{
"attributeSet": {
"attributes": {
"426824828246824828282468248": {
"value": "1"
},
"647474373748648248282824": {
"value": "true"
}
}
}
}
]
},
"productUrl": "url://adgagjfjfjfjhf/sfg/v1/document/adfah5ahfah5jahlkhaliohßjkjlaß73-3",
"attributes": [
{
"attributeSet": {
"attributes": {
"426824828246824828282468248": {
"value": "1"
},
"647474373748648248282824": {
"value": "true"
}
}
}
}
]
},
I found nothing on google on how to do this specific task. Is a pipe even the best solution for this? I tried to implement functions in the pipe but it didnt work.
Another thing that i cant figure out is how i can make every button unique so the application knows which excat URL it should take when the button is clicked? And how can I select the URL and give it in another function in another component?
First of all rather than using pipe, I have created solution in Component only.
Stringify JSON which needs to be get values from. genericJson
Remove first and last ", which is matched in regex.
Using *ngFor, create buttons and pass separate link to click function.
Demo (check console for button click)
EDIT: NEW DEMO.
import { Component } from "#angular/core";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
name = "Angular";
genericJson = { ... some json ... }
formatedOutput: (string | boolean)[] = [];
ngOnInit() {
// adding formatting to make regex search easier
const jsonFormattedString = JSON.stringify(this.genericJson, null, 4);
this.formatedOutput = jsonFormattedString.split('\n');
}
onClick(out: string) {
// do whatever operation on link
const link = out.match(/url:\/\/.*\/.*\/.*/)[0];
console.log(link);
}
urlFound(out: string): boolean {
const match = out.match(/"url:\/\/.*\/.*\/.*"/);
if (match !== undefined && match !== null && match.length > 0) {
return true;
}
return false;
}
}
Use matched links in HTML template,
<div>
<div *ngFor="let out of formatedOutput">
<ng-container *ngIf="urlFound(out); else simple_json"><pre>{{out}}<button (click)="onClick(out)">Link</button></pre></ng-container>
<ng-template #simple_json><pre>{{out}}</pre></ng-template>
</div>
</div>
I think, you should do it like this instead of using pipe:
.html
<button (click)="goTo(genericJson.url)">Run</button>
.ts
genericJson = {
url: "www.google.com"
};
goTo(url: string) {
if (url) {
if (!url.includes("https")) {
url = "https://" + url;
}
window.open(url, "_blank");
}
}

Working with data from observable in a component in Angular 6

I am not sure if I have phrased this question correctly, so I apologize for the clunky wording. I am relatively new to angular but am pretty comfortable with making HTTP requests and working with the data in other frameworks (like VueJS). I am beginning to understand the Observables that angular uses. I am trying to make a blog application, and have an express backend that has the JSON for the blog posts.
In my post.service.ts I have:
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable, of } from 'rxjs';
import { Post } from '../post';
#Injectable({
providedIn: 'root'
})
export class PostService {
private apiUrl = 'http://localhost:8081/posts';
getPosts(): Observable<Post[]> {
return this.http.get<Post[]>(this.apiUrl);
}
constructor( private http: HttpClient,
private postService: PostService ) { }
}
And then I want to list all the posts in my post-list.component.ts:
import { Component, OnInit } from '#angular/core';
import { PostService } from '../../services/post.service'
import { Post } from '../../post';
#Component({
selector: 'app-post-list',
templateUrl: './post-list.component.html',
styleUrls: ['./post-list.component.css']
})
export class PostListComponent implements OnInit {
public posts = [];
constructor(private postService: PostService) { }
ngOnInit() {
this.postService.getPosts()
.subscribe(data => this.posts = data);
}
}
But the posts array becomes an object and i'm not sure how to use it as an array. If I try to use the *ngFor method, I get an error. The page shows [object Object] if I put {{posts}} in the html. If i do {{posts | json}} it shows the actual JSON, but I still cannot iterate through it.
This is what the json looks like:
{
"posts": [
{
"_id": "5b04b269fde3ca29b35ffc3e",
"name": "Mike",
"title": "Stuff",
"post": "This is a post about stuff"
},
{
"_id": "5b04b24dfde3ca29b35ffc3d",
"name": "OtherUser",
"title": "New Post Test",
"post": "This is a test post"
},
{
"_id": "5b02ed783aa641758c08e601",
"name": "Emerjawn",
"title": "Post"
}
]
}
Before I try to setup CRUD for this application, I want to simply figure out how to display the data which I still cannot do and it is driving me insane. Thank you in advance for the help.
Your return JSON is an object which has field posts holding your needed array data so just take posts field from your server response and render such array of posts. Something like this:
ngOnInit() {
this.postService.getPosts()
.subscribe(data => this.posts = data.posts);
}
For better typing you can always specify your variable type i.e. public posts: Post[] then you will have type checking while coding.

How to loop through nested JSON data

I am trying to loop through json data bellow, to so each element. I need to get down to the details data and then in side that loop through the f1,f2 into a div. I have tried using the index but that didn't work. Also I don't know how many f1,f2 there will be as it is returned from an api
JSON data
{
"data":[
{
"title": "test",
"image": "assets/imgs/test.png",
"date": "22/07/2018 - 19.00",
"location": "test",
"details": [
{
"f1":[
{
"FunctioName": "test",
"Time": "10:00:00"
}
],
"f2":[
{
"FunctioName": "test",
"Time": "11:00:00"
}
]
}
]
}
]
}
HTML
<div *ngFor="let item of card">
<div class="swiper-zoom-container">
<div class="out-card-box">
<h2>Date</h2>
<p>{{item.date}}</p>
<h2>Program</h2>
<div *ngFor="let details of item.details; let i = index">
</div>
</div>
</div>
</div>
TS
import { Component } from '#angular/core';
import { App } from 'ionic-angular';
import { DataService } from "../../services/data";
import { LoginPage } from "../login/login";
import { AngularFireAuth } from "angularfire2/auth";
import { Storage } from "#ionic/storage";
#Component({
selector: 'page-card',
templateUrl: 'card.html',
})
export class CardPage {
card:any;
constructor(private dataservice: DataService, private afAuth:AngularFireAuth, private app:App, private storage:Storage) {
this.dataservice.cardData().subscribe(
data => {
var jsonObj = JSON.parse(data["_body"]);
this.card = jsonObj.data;
console.log(jsonObj.data)
}
);
}
You can create an object which will hold the returned data from the api and you can just navigate the object values.
Example:
export class Class1 {
data: Class2[];
}
export class Class2 {
title: string;
image: string;
date: string;
location: string;
details: Class3[];
}
export class Class3 {
f1: Class4[];
f2: Class4[];
}
export class Class4 {
FunctioName: string;
Time: string
}
#Component({
selector: 'page-card',
templateUrl: 'card.html',
})
export class CardPage {
card:Class1;
constructor(private dataservice: DataService, private afAuth:AngularFireAuth, private app:App, private storage:Storage) {
this.dataservice.cardData().subscribe(
data => {
this.card = data;
}
);
}
then in your component template
<div *ngFor="let item of card.data">
<div class="swiper-zoom-container">
<div class="out-card-box">
<h2>Date</h2>
<p>{{item.date}}</p>
<h2>Program</h2>
<div *ngFor="let details of item.details; let i = index">
<!-- Print the remaining items here -->
</div>
</div>
</div>
</div>