How to format JSON data fetch from API - html

Am getting data from API and trying to display it in my angular application I can able to fetch and display the data but it's not in a good format.
{
"countryCode": "BS",
"countryName": "BAHAMAS",
"publishedDate": "2020-03-30T00:00:00.000Z",
"alertMessage": "\nAll international flights to Uganda are suspended until 24 April 2020.|\n- This does not apply to:|\n1. Aircraft in a state of emergency.||\n2. Operations related to humanitarian aid, medical and relief flights.||\n3. Technical landings where passengers do not disembark.||\n4. Any other flight that may be so approved by the appropriate authority.||\n"
},
{
"countryCode": "FJ",
"countryName": "FIJI",
"publishedDate": "2020-03-30T00:00:00.000Z",
"alertMessage": "\n1. Passengers and airline crew are not allowed to enter Fiji.|\n- This does not apply to nationals of Fiji.||\n2. Nationals of Fiji must go into quarantine for a period of 14 days.||\n"
}
JSON data which I get from API.
The output which is expecting is
but the output which am getting is
my code as follows
<div class="card" style="width: 69rem;" *ngFor="let alert of jsonValue">
<div class="card-body" #{{alert.countryName}}>
<div class="container">
<div class="row">
<div class="col">
<span> <p class="card-title h2" style="float: left">{{alert.countryName}}</p></span>
</div>
<div class="col">
<span><img src="../../../assets/flags/{{alert.countryCode | lowercase}}.svg" style="width: 40px; height: 28px;"></span>
</div>
</div>
</div>
<p class="card-text">{{alert.alertMessage}}</p>
<p class="card-footer" style="float: right">{{alert.publishedDate | date:'short'}}</p>
</div>
</div>

The text is unusually formatted. One way to use it is to split the string as per your requirement and iterate it using *ngFor.
var alertMessage = '\nAll international flights to Uganda are suspended until 24 April 2020.|\n- This does not apply to:|\n1. Aircraft in a state of emergency.||\n2. Operations related to humanitarian aid, medical and relief flights.||\n3. Technical landings where passengers do not disembark.||\n4. Any other flight that may be so approved by the appropriate authority.||\n';
console.log(alertMessage.split(/[||\n]+/).filter(Boolean)) // <-- `filter(Boolean)` to remove empty strings
You could then use it in the component like following
Service fetching data from API
#Injectable()
export class ApiService {
...
getData() {
this.http.getData().pipe(
.map(data =>
data.forEach(item => {
item.alertMessage = item.alertMessage.split(/[||\n]+/).filter(Boolean)
})
)
);
}
}
Component template
<div class="card" style="width: 69rem;" *ngFor="let alert of jsonValue">
<div class="card-body" #{{alert.countryName}}>
<div class="container">
<div class="row">
<div class="col">
<span> <p class="card-title h2" style="float: left">{{alert.countryName}}</p></span>
</div>
<div class="col">
<span><img src="../../../assets/flags/{{alert.countryCode | lowercase}}.svg" style="width: 40px; height: 28px;"></span>
</div>
</div>
</div>
<p class="card-text">
<ul [ngStyle]="{'list-style': 'none', 'padding-left': '0'}">
<li *ngFor="let message of alert.alertMessage">{{ message }}</li>
</ul>
</p>
<p class="card-footer" style="float: right">{{alert.publishedDate | date:'short'}}</p>
</div>
</div>

May be you want to do some formatting to your data before using it in the template.
The string contains \n and | which has a special meaning. You can use them to format your data.
for Example let say you are getting this data from service inside a method someMethod() . After getting the data loop over the result array elements
and create a new Array for all the response items containing the formatted values.
someMethod(){
.... other lines to fetch the data
let data = {
"countryCode": "BS",
"countryName": "BAHAMAS",
"publishedDate": "2020-03-30T00:00:00.000Z",
"alertMessage": "\nAll international flights to Uganda are suspended until 24 April 2020.|\n- This does not apply to:|\n1. Aircraft in a state of emergency.||\n2. Operations related to humanitarian aid, medical and relief flights.||\n3. Technical landings where passengers do not disembark.||\n4. Any other flight that may be so approved by the appropriate authority.||\n"
}
let arr = str.split('|').filter((el)=>{ return el!== ""}); // splits the string and removes empty element
let newArr = arr.map((el)=>{ return el.replace("\n" ,"")}) // removes \n from string
// of course you can minimise/reduce the logic , this is just to make you understand how the formatting happened
let formattedValues = {
title : newArr[0], // first element is title
subtitle : newArr[1], // second is subtitle
options : newArr.slice(2) // rest is option
}
data['alertMessage'] = formattedValues; // assign the formatted value
}
then in HTML you can use the JSON as below :
<p class="card-text">{{alert.messageData.title}}</p>
<p class="card-text">{{alert.messageData.subtitle}}</p>
<p *ngFor="let it of alert.messageData.options">{{it}}</p>
Here is the working example : Demo

Related

How to bind JSON object key value pair separately to angular template

And i have a dynamic json coming from an API as below:
{123: "Mumbai", 456: "Bangalore", 789: "Chennai", 101: "Andhra",...}
I have below HTML code written in my template in which I am getting image-1, image-2 logos from my assets folder
<div class="row">
<div class="col-6" (click)="cityClick('Bangalore')">
<div class="img-1">
// my image-1 logo goes here
</div>
<div class="img-text">
Bangalore
</div>
</div>
<div class="col-6" col-6 (click)="cityClick('Mumbai')">
<div id="image_block" class="img-2">
// my image-2 logo goes here
</div>
<div id="text_block" class="img-text">
Mumbai
</div>
</div>
</div>
How to get the key of the json when i click on image and also display the text below the image from the corresponding key-value.
And when i click i should be able to pass the key and text inside the click event. Please help as i am new to Angular!
First convert this JSON into an JavaScript/TypeScript array like below:
var json = {123: "Mumbai", 456: "Bangalore", 789: "Chennai", 101: "Andhra" };
var jsonToBeUsed = [];
for (var type in json) {
item = {};
item.key = type;
item.value = json[type];
jsonToBeUsed.push(item);
}
This will result in data like below:
Now use NgFor in the template to bind array. (Refer this for NgFor)
<div class="row">
<div *ngFor="let item of array">
<div class="col-6" (click)="cityClick(item)">
<div class="img-1">
// my image-1 logo goes here
</div>
<div class="img-text">
{{item.value}}
</div>
</div>
</div>
</div>
We have passed the whole object in the click event. You can read any of the desired property from the object in click event handler which you will write in the component.
For your special requirement, you can use below markup:
<div class='row' *ngFor='let index of array; let i = index; let even = even'>
<div *ngIf="even" class='col-6' (click)="cityClick(array[i])">
<div class="img-1">
// my image-1 logo goes here
</div>
<div class="img-text">
{{array[i].value}}
</div>
</div>
<div *ngIf="!even" class='col-6' (click)="cityClick(array[i])">
<div class="img-1">
// my image-1 logo goes here
</div>
<div class="img-text">
{{array[i].value}}
</div>
</div>
</div>
Use this below function in your code:
getKeyByValue(object, value) {
return Object.keys(object).find(key => object[key] === value);
}
And use as
var dynamicJson = {123: "Mumbai", 456: "Bangalore", 789: "Chennai", 101: "Andhra"}
cityClick(value){
var key = this.getKeyByValue(this.dynamicJson, value);
console.log(key);
}
{123: "Mumbai", 456: "Bangalore", 789: "Chennai", 101: "Andhra",...}
Do you have influence on that JSON? This highly looks like a design issue for me. I assume those numbers are id's. I believe somethings like this should be better:
[{id: "123", name: "Mumbai"}, {id: "456", name: "Bangalore"}, {id: "789", name: "Chennai"}, {id: "101", name: "Andhra"},...}]
In that case you receive an array of cities, which could be an interface to parse to.
export interface City {
id: string;
name: string;
}
And you can easily render it in html by using *ngFor
<div *ngFor="let city of cities">
<!--do something with city.id and city.name-->
</div>
<div *ngFor let value of json |keyvalue > </div>

Select a node with its children based on its class, and turn it into an object

I want to find out how to scrape website data. This is a part of the html that I am interested in. I am using cheerio for finding the data I need.
<td class="col-item-shopdetail">
<div class="shoprate2 text-right hidden-xs">
<div class="currbox-amount">
<span class="item-searchvalue-curr">SGD</span>
<span class="item-searchvalue-rate text-black">42.0000</span>
</div>
<div class="item-inverserate">TWD 100 = SGD 4.2</div>
<div class="rateinfo">
<span class="item-timeframe">12 hours ago</span>
</div>
</div>
<div class="shopdetail text-left">
<div class="item-shop">Al-Aman Exchange</div>
<div class="item-shoplocation">
<span class="item-location1"><span class="icon icon-location3"></span>Bedok</span>
<span class="item-location2"><span class="icon iconfa-train"></span>Bedok </span>
</div>
</div>
</td>
I wish to make "col-item-shopdetail" class as an object and store all class with name "col-item-shopdetail" into an array for access.
So if possible, it will be access like array.item-inverserate or through cheerio selector like
$('.col-item.shopdetail').children[0].children[0].children[1]
I have tried looping through the names of shop and store in an array and use another loop after finish looping the names to find the rates. Then try and match the rates to the name by access same index of the array. However this did not work for unknown reason where each time the rate printed is of different value and index of the same name are different in each try.
This is close to what I want but it does not work:
how to filter cheerio objects in `each` with selector?
In other words, you want an array of objects representing elements having class .col-item-shopdetail and each of those objects should have a property corresponding to the .item-inverserate element they contain ?
You need the map method
my_array = $('.col-item-shopdetail').map(function(i, el) {
// Build an object having only one property being the .item-inverserate text content
return {
itemInverserate: $(el).find('.item-inverserate').text()
};
}).get();
// You can also directly target inverserate nodes
// which will exclude empty entries ('shopdetail' that have no 'inverserate')
// Loop over .item-inverserate elements found
// somewhere in a .col-item-shopdetail
// (beware, space matters)
my_array = $('.col-item-shopdetail .item-inverserate').map(function(i, el) {
// Build an object having only one property being the .item-inverserate text content
return {itemInverserate: $(el).text()};
// Note: If all you need is the inverserate value,
// Why not avoiding an intermediate full object?
// return $(el).text()
}).get();
Since Cheerio developers have built their API based on jQuery with most of the core methods, we can simply test snippets in the browser ...
my_array = $('.col-item-shopdetail').map(function(i, el) {
return {
itemInverserate: $(el).find('.item-inverserate').text()
};
}).get();
console.log(my_array[0].itemInverserate)
my_array_2 = $('.col-item-shopdetail .item-inverserate').map(function(i, el) {
// Build an object having only one property being the .item-inverserate text content
return {itemInverserate: $(el).text()};
}).get();
console.log(my_array_2[0].itemInverserate)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table><tr><td class="col-item-shopdetail">
<div class="shoprate2 text-right hidden-xs">
<div class="currbox-amount">
<span class="item-searchvalue-curr">SGD</span>
<span class="item-searchvalue-rate text-black">42.0000</span>
</div>
<div class="item-inverserate">TWD 100 = SGD 4.2</div>
<div class="rateinfo">
<span class="item-timeframe">12 hours ago</span>
</div>
</div>
<div class="shopdetail text-left">
<div class="item-shop">Al-Aman Exchange</div>
<div class="item-shoplocation">
<span class="item-location1"><span class="icon icon-location3"></span>Bedok</span>
<span class="item-location2"><span class="icon iconfa-train"></span>Bedok </span>
</div>
</div>
</td></tr>
</table>

get an arraylist from a service using HTTP in ANGULAR

i have a component named zoomdetails which contains the specific details of a product
when i click on the product image the zoomdetails component displays and contains the details of the clicked product
so i m using route and adding the id of the product to the URL
the problem is :
when i load the products arraylist from the service and try to get the product by its id and looping the arraylist an error appears and indicates Cannot read property 'length' of undefined
here is the zoomdetails.component.ts code :
(i ve added some log.console comments to see the results)
export class ZoomdetailsComponent implements OnInit {
x: string="";
produitzoom:IProduct;
produits:IProduct[];
errormessage1 :string ;
currentId : number;
constructor(private _route:ActivatedRoute,private _router:Router,private _productServic:ProductdataService)
{
console.log("Logging the route : " + JSON.stringify(_route.snapshot.params));
this.currentId = +_route.snapshot.params['id'];
console.log("Logging the current ID : " + this.currentId)
this._productServic.getProducts()
.subscribe(productss => this.produits=productss ,error=>this.errormessage1= <any>error);
console.log("************************************************************************************")
}
Myspan(){
this._router.navigate(['/']);
}
find (id:number,P:IProduct[]) :IProduct{
console.log("+++++++DANS FIND ERROR +++++++++++++++++++++++++")
for (let product of P )
{
if (product.idpr==id )
{
return product;
}
}
}
ngOnInit() {
console.log("-------------DANS NGONINITTT-------------------------------------------------------------")
this.produitzoom=this.find(this.currentId,this.produits)
console.log(this.produitzoom.productName)
console.log("--------------------------------------------------------------------------")
}
and this is my zoomdetails component .html
<router-outlet></router-outlet>
<div id="zoom" class="modal">
<!-- Modal content -->
<div class="modal-content">
<span class="close" (click)="Myspan()">×</span>
<div class="container">
<div class="row">
<div class="col-md-4 item-photo">
<img src={{produitzoom.imgUrl}} style="width:360px;height:650px;">
</div>
<div class="col-md-6" style="border:0px solid rgba(163, 152, 152, 0.856)">
<span class="pull-right">
<!-- Datos del vendedor y titulo del producto -->
<h1>{{produitzoom.productName}}</h1>
<h4 style="color:#337ab7"> {{produitzoom.author}} <small style="color:#337ab7">(50 ventes)</small></h4>
<!-- Precios -->
<h2 class="title-price"><small>Price</small></h2>
<h3 style="margin-top0px">{{produitzoom.price}} $</h3>
<br> <br>
<!-- Detalles especificos del producto -->
<div class="section" style="background:rgb(222, 228, 222);">
<h5 class="title-attr" >
<div>
<br>
{{produitzoom.description}}
<br> <br>
</div>
</h5>
</div>
<br><br>
<!-- Botones de compra -->
<script>
console.log("Test of value : " + JSON.stringify(produitzoom))
</script>
<button class="btn btn-success right" [routerLink]="['/Authentification',produitzoom]">
<span class="glyphicon glyphicon-shopping-cart"></span> Add to Cart
</button>
</span>
<br> <br> <br> <br>
<ul class="menu-items">
<li class="active">Customers Reviews</li>
</ul>
<div style="width:100%;border-top:1px solid silver">
<p style="padding:15px;">
<small>
Stay connected either on the phone or the Web with the Galaxy S4 I337 from Samsung. With 16 GB of memory and a 4G connection, this phone stores precious photos and video and lets you upload them to a cloud or social network at blinding-fast speed. With a 17-hour operating life from one charge, this phone allows you keep in touch even on the go.
With its built-in photo editor, the Galaxy S4 allows you to edit photos with the touch of a finger, eliminating extraneous background items. Usable with most carriers, this smartphone is the perfect companion for work or entertainment.
</small>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
and these are the errors :
Logging the route : {"id":"1"} zoomdetails.component.ts:22 Logging the current ID : 1 zoomdetails.component.ts:25 ************************************************************************************ zoomdetails.component.ts:50 -------------DANS NGONINITTT------------------------------------------------------------- zoomdetails.component.ts:38 +++++++DANS FIND ERROR +++++++++++++++++++++++++ ZoomdetailsComponent_Host.html:1 ERROR TypeError: Cannot read property 'length' of undefined (from the find method )
ERROR TypeError: Cannot read property 'imgUrl' of undefined (from the html file produitzoom.imgurl)
what should i do !
first, about the imgUrl error, because of the fact that initially produitzoom is undefined, and it gets it's value after an async call, you can change the value of binding to this: [src]="produitzoom? produitzoom.imgUrl : null".
also about the other error, you are calling this.produitzoom=this.find(this.currentId,this.produits) inside your ngOnInit function, but again, bacuase of the fact that the produits is also undefined at the beginning of the component's lifecycle, and gets it's value after an async call. you should move that this.find() call over to the subscribtion's success. something like this:
productss => {
this.produits=productss;
this.produitzoom = this.find(this.currentId,this.produits)
}
Note!!
it's also very important and recommended that if you are subscribing to an observable, you unsubscribe it at the end of that component's Lifecycle (inside ngOnDestroy function). otherwise, this would cause memory leeks and untracable errors... you can do that by defining a property for subscription like:
productsSubscription: Subscription;
dont forget to import Subscription from rxjs/subscription. and then assign the subscription to this property like:
this.productsSubscription = this._productServic.getProducts()
.subscribe(.....);
and inside ngOnDestroy:
ngOnDestroy(){
if(this.productsSubscription){
this.productsSubscription.unsubscribe();
}
}
have that if there to prevent any undefined-related errors.
The problem is that you are loading the products in your constructor, which is asynchronous. Then in your ngOnInit function that is called later on you're using the result of those loaded products, but unfortunately they seem to be not loaded yet. Therefore your P array is not existing yet and so you are using an undefined object in a for loop, which is not working.
ngOnInit() {
console.log("-------------DANS NGONINITTT-------------------------------------------------------------")
// --> here you use the products from the constructor
// but they are not loaded yet, because the loading process takes time and is asynchronous
this.produitzoom=this.find(this.currentId,this.produits)
console.log(this.produitzoom.productName)
console.log("--------------------------------------------------------------------------")
}
What you should do is place the loading of your products in the ngOnInit function as well. There you can wait for the result and then call the find function.
nOnInit(){
// don't subscribe in the constructor, do it in the ngOnInit
this._productServic.getProducts().subscribe(productss => {
// now the results are here and you can use them
this.produits = productss;
// only then you can call the find function safely
this.produitzoom = this.find(this.currentId, this.produits)
}, error => this.errormessage1 = <any>error);
}

Get JSON Multidimensional Array Keys - AngularJS

I am trying to build a form out of a JSON array. I need to load the keys into the HTML. Here is an example of this array:
{
"Fred": {
"description": "A dude"
},
"Tomas": {
"description": "Another Dude",
"Work": {
"Current": "No Employer",
"Previous": "Enron"
}
}
}
What I was are the values Fred & Thomas. When I run this in Angular HTML:
<div ng-repeat="set in sets">
<p ng-repeat="(key, val) in set">
<span ng-bind="key"></span>: <span ng-bind="val"></span>
</p>
</div>
I get the error "ngRepeat-dupes" although Fred and Tomas are not duplicate values. Any help is greatly appreciated.
You are getting the dupe error from a key being the same in both objects. You can fix it by using track by $index, however in the data you have provided, there are no dupes... see fiddle - https://jsfiddle.net/t4q4nrfp/36/
IF you did have dupes in your data though you just add in track by $index (you can use other things as well index is generally a default) like so :
<div ng-repeat="set in sets track by $index"> << add here if you have dupes are this level
<p ng-repeat="(key, val) in set track by $index"> << or here if dupes at this level
<span ng-bind="key"></span>: {{val}} <span ng-bind="val"></span>
</p>
</div>
Also, just to be clear, you are working with an object not an array.
use track by $index:
<div ng-repeat="set in sets track by $index">
<p ng-repeat="(key, val) in set track by $index">
<span ng-bind="key"></span>: <span ng-bind="val"></span>
</p>
</div>

Nested ngrepeats with inconsistent data structures

I am creating a set of divs from a JSON object using ng-repeat that construct "note" cards. The number of cards is determined by the number of JSON objects in the array. "Type" designates the header of the card, and "Content" designates the body. However, the contents of "Content" is not consistent. In some examples, the content is merely a sentence or paragraph. In others, the content is an array. When the content is a sentence, the formatting is handled fine. When the content is an array, I end up with the actual array in text format [{"year":"2009", "operation":"caesarean"},{"year":"2010", "operation":"torn meniscus"}] as the body of the card. Any ideas of how to implement this without scripting each card individually?
HTML
<section name="patientinfo">
<div align="center" ng-controller="HistoryCtrl">
<div class="grid-container" ng-repeat="record in historyItems">
<div class="row" row>
<div class="col-2" ng-repeat="history in record.History">
<div class="note" draggable="true">
<div class="row" id="rowhead3">
<div class="col-6" >
<span class="note-header">{{history.type}}</span>
</div>
</div>
<div class="row">
<div class="col-6" style="overflow: auto; height:250px;">
<div>
<span class="note-content">
{{history.content}}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
JSON Example (the actual JSON is huge). There are many other entries besides "history". Also, these notes are not from a real person, so don't worry.
"History": [
{
"type": "medical",
"content": [
{
"condition": "IBS"
},
{
"condition": "Torn Meniscus Right Knee"
},
{
"condition": "Seasonal Allergies"
}
]
},
{
"type": "SOCIAL HISTORY",
"content": "Lives with husband and 3 children. No history of violence, domestic violence, or rape. Feels safe at home and in relationship. ETOH on weekends (socially 4 drinks in one weekend occasionally) and occasionally smokes cigarettes and marijuana. Admits to very rare marijuana on special occasions."
}]
Example of what I'm ending up with:
http://i.stack.imgur.com/MtuBN.png
You should transform or augment the data into a new format where you have the following structure for each note:
{
type: "string",
contentType: "(text|list)",
content: "string" // OR array of strings
}
To do this, you will need to have custom logic to transform each object in the array to a single string.
Then just use an ng-switch on the contentType attribute to decide which markup to use (e.g. a <ul> if contentType is list).