Explicitly Trusting HTML - Angular 5 - html

I have created a pipe to format the XML string that I get from a server as a pretty printing.
import { Pipe, PipeTransform } from '#angular/core';
import * as jQuery from 'jquery';
import { escape } from 'querystring';
import { DomSanitizer, SafeHtml } from '#angular/platform-browser';
//import * as angular from '../angular.js';
//CAMBIAR a string
#Pipe({
name: 'formatXml'
})
export class FormatXmlPipe implements PipeTransform {
constructor(private sanitized: DomSanitizer) { }
transform(xml: String): SafeHtml {
var formatted = '';
var reg = /(>)(<)(\/*)/g;
xml = xml.replace(reg, '$1\r\n$2$3');
var pad = 0;
jQuery.each(xml.split('\r\n'), function (index, node) {
var indent = 0;
if (node.match(/.+<\/\w[^>]*>$/)) {
indent = 0;
} else if (node.match(/^<\/\w/)) {
if (pad != 0) {
pad -= 1;
}
} else if (node.match(/^<\w[^>]*[^\/]>.*$/)) {
indent = 1;
} else {
indent = 0;
}
var padding = '';
for (var i = 0; i < pad; i++) {
padding += ' ';
}
formatted += padding + node + '\r\n';
pad += indent;
});
var escaped = formatted.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/ /g, ' ').replace(/\n/g, '<br />');
// https://stackoverflow.com/questions/39240236/insert-xml-into-dom-in-angular-2
let safeEscaped = this.sanitized.bypassSecurityTrustHtml(escaped);
return safeEscaped;
//let safeEscaped = this.sanitized.bypassSecurityTrustHtml(escaped);
//return safeEscaped;
}
}
the string that I have is shown in the following div:
<tr *ngFor="let message of messages; let i = index;">
<td *ngIf="i == _rowToShow" >{{message.xmlString | formatXml}}</td>
</tr>
_rowToShow is just a variable to limit the rows to show to an specific one and it works so it can not be the problem
so basically what I think that it is happening is that I am calling correctly to the pipe formatXml and I get the correct value of the string, but it is not a trusting HTML so it does not show it properly (it shows only the original XML).
I have read that to solve this problem in Angular 1.2 was just needed to use $sce.trustAsHtml but I do not really know how to do it.
https://odetocode.com/blogs/scott/archive/2014/09/10/a-journey-with-trusted-html-in-angularjs.aspx
Update
At the end what I get is
SafeValue must use [property]=binding:
plus my XML
Any body know what can be the problem?

Related

Angular ngOnInit() - loop into a file HTML

I would like to display the values following -> 0 1 2 without using an array.
export class AppComponent {
ngOnInit() {
for (let i = 0; i < 3; i++) {
console.log('Number ' + i);
}
}
}
Into the file HTML what should I put to retrieve my values?
Here is my code here Stackblitz
Thank you for your help.
Html is bound with some values from the .ts file which are declared on class level as fields
export class AppComponent {
field1: any
....
Local variables like the ones that you use in a for loop are not bound in the html file and can't be used there.
Html is bound and has access to either public class field variables or public methods. You could make a public method to return something from a for loop and then the html could invoke that public method and render the return value.
example
public method1(): string {
let text = '';
for (let i = 0; i < 3; i++) {
if (text === ''){
text = text + i;
} else {
text = text + ' ' + i;
}
}
return text;
}
and then on .html file you can do
<div> {{method1()}} </div>
But the method ngOnInit() that you use is a method which is invoked automatically by angular in the angular lifecycle so shouldn't be used to achieve that.
I built a pipe that generates a sequence of consecutive numbers (starting with 0) that you can use directly inside of the template, not even needing the for in the ngOnInit method.
#Pipe({
name: 'sequenceGenerator'
})
export class SequenceGenerator implements PipeTransform {
transform(numberOfItems?: number): number[] {
const length = numberOfItems ?? 0;
const sequence = Array.from({ length: length }, (_, i) => i);
return sequence;
}
}
And you can use it like this:
<div *ngFor="let item of (3 | sequenceGenerator)">
{{item}}
</div>
Have a look at the stackblitz example for further reference.

I'm trying to create a memory game where an expanding list of numbers is shown in ionic and angular and the user has to type in the answer

The way that I am doing it is that I want each of the numbers to appear then disappear. I have tried a lot of options but only the last number ends up showing when there are two or more numbers in the array. I suspect it has something to do with the for loop, but there does not seem to be a way around it.
Here is my typescript code for the generate numbers function:
generateNumbers() {
let numbersArray = new Promise<number[]>((resolve, reject) => {
let numberArray: number[] = []
for (let i = 0; i < this.level; i++) {
this.animationCtrl.create()
.addElement(this.currentNum.nativeElement)
.duration(500)
.iterations(1)
.fromTo('opacity', '1', '0.05').play()
.then(func => {
let randomnum = Math.floor(Math.random() * 9)
numberArray.push(randomnum)
this.currentIndex = i
this.currentNumber = randomnum
this.parsedCurrentNumber = JSON.parse(JSON.stringify(this.currentNumber))
}).then(func => {
this.animationCtrl.create()
.addElement(this.currentNum.nativeElement)
.duration(500)
.iterations(1)
.fromTo('opacity', '0.05', '1').play()
}).then(func => {
if (i === this.level - 1) {
resolve(numberArray)
}
})
}
})
return numbersArray
}
Here are my variable declarations and injections:
#ViewChild('currentNumber', { read: ElementRef, static: true}) currentNum: ElementRef;
level: number = 1;
levelExp: number = 1;
gameHasBegun = false;
paused = false;
numbersArray: number[] = [];
answer: string;
wrongcount: number = 0;
wrong = false;
lost = false;
currentIndex: number = 0
currentNumber: number;
parsedCurrentNumber: string;
constructor(
private router: Router,
private menu: MenuController,
private animationCtrl: AnimationController ) { }
Here is how I call my generate function:
this.generateNumbers().then(
(val) => this.numbersArray = val
)
Here is my HTML Code for the part where the numbers should be shown, but instead only one number is shown when I have two or more numbers in my array:
<ion-content #currentNumber>
<ion-label class="ion-text-center" >
<h1>{{ parsedCurrentNumber }}</h1>
</ion-label>
</ion-content>
Look at the following stackblitz.
https://stackblitz.com/edit/ionic-79e1rn
You basically need to loop through your array with a timeout.
ionViewDidEnter(){
this.runSeries(0);
}
runSeries(i){
if(i < this.nums.length){
setTimeout(() => {
this.lastNum = this.nums[i];
i++;
this.runSeries(i);
}, 1000)
}
}
and bind lastNum in your template.

Fetch URI from Post Data through Get Data

Show 1 textfield with 2 buttons - Post, Get. Take a number as input in a text field. On clicking Post, create an array of the numbers from 1 to that number. Post this array at the URL. Display the response from the Post.On clicking Get, fetch data from the URL returned by the Post and display it.
urlPost = 'https://api.myjson.com/bins';
clist: number[] = [];
strData: string = '';
S1: String = '';
ntext: number;
constructor(private netService: NetService) {}
postData() {
for (var i = 1; i <= this.ntext; i++) {
this.clist.push(i);
}
this.netService.postData(this.urlPost, this.clist)
.subscribe(postresp => {
this.strData = JSON.stringify(postresp);
});
}
getData() {
this.netService.getData(this.strData.Uri)
.subscribe(resp => {
this.strData = JSON.stringify(resp);
});
}
this line need to be improved.
this.netService.getData(this.strData.Uri)
As I understand your question, you simply have a problem with parsing a response from your postData(). So, just refer to the following -
postData() {
for (var i = 1; i <= this.ntext; i++) {
this.clist.push(i);
}
this.netService.postData(this.urlPost, this.clist)
.subscribe(postresp => {
this.S1 = postresp['uri']; // get URL here
});
}
getData() {
this.netService.getData(this.S1) // use it here
.subscribe(resp => {
this.strData = JSON.stringify(resp);
});
}
See it working here.

How to properly create an array of JSON objects in Typescript?

I'm trying to create an array of JSON objects in typescript. And following is the approach I have used.
var queryMutations:any = _.uniq(_.map(mutationData.result,
function(mutation:Mutation) {
if (mutation && mutation.gene) {
var item = {facet: "MUTATION", term: mutation.gene + " " + mutation.proteinChange}
return item;
}
else {
return {};
}
}));
var jsonString = JSON.stringify(queryMutations);
is this the correct way to do it? Appreciate your suggestions.
To me it looks ok. I'd personally make a few layout style modifications and use backtick placeholder strings.
var queryMutations:any =
_.uniq(
_.map(
mutationData.result,
function(mutation:Mutation) {
if (mutation && mutation.gene) {
return {facet: "MUTATION",
term: `${mutation.gene} ${mutation.proteinChange}`
} else {
return {};
}
}
)
);
var jsonString = JSON.stringify(queryMutations);

How to implement json2csv in Angular 2

How to implement json2csv in angular 2 using typescript?(https://www.npmjs.com/package/json2csv). Is there any other better approach.
This may not be based on the original (I haven't checked), but:
https://www.npmjs.com/package/angular2-json2csv
The readme says it should be used as a service, but it can also be used as a plain class, eg:
let csv = new CSVService();
It has its faults though:
If you have an array of objects that are similar, but not all have a value for every single property, the column headers will be missing for those properties.
It doesn't allow you to exclude columns, which may or may not be desired.
Edit:
I've come up with an alternative, which accounts for items with more properties than the others. Use or modify as you wish:
export class JSONToCSV {
private AddValue(Row: string, Value: string) {
let Comma: string = ',';
if (Row === '')
Comma = '';
return Row + Comma + Value;
}
private GetHeader(Item: any) {
let Row: string = '';
for (var Key in Item) {
Row = this.AddValue(Row, Key);
}
return Row + '\r\n';
}
private GetRow(Item: any) {
let Row: string = '';
for (var Key in Item) {
Row = this.AddValue(Row, Item[Key]);
}
return Row + '\r\n';
}
private GetPropCount(Item: any) {
return Object.keys(Item).length;
}
public Convert(Data: any, Filename: string) {
let CSV: string = '';
let ColumnsObject: any = null;
for (var Item of Data) {
if ((ColumnsObject == null) || (this.GetPropCount(Item) > this.GetPropCount(ColumnsObject))) {
ColumnsObject = Item;
}
CSV = CSV + this.GetRow(Item);
}
CSV = this.GetHeader(ColumnsObject) + CSV;
let CSVBlob = new Blob([CSV], { type: 'text/csv' });
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(CSVBlob, Filename);
} else {
// window.open(URL.createObjectURL(CSVBlob));
let url= window.URL.createObjectURL(CSVBlob);
let Anchor: any = document.createElement('a');
Anchor.setAttribute('style', 'display:none;');
document.body.appendChild(Anchor);
Anchor.href = url;
Anchor.download = Filename;
Anchor.click();
}
}
}
#dave-notage response works with Chrome, but not with Firefox.
Here is a working implementation:
<a [download]="csvFileName" [href]="getCSVDownloadLink()">CSV export</a>
import { Component } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
import * as json2csv from 'json2csv';
#Component({
selector: 'csv-download',
templateUrl: './csv-download.component.html',
styleUrls: ['./csv-download.component.scss']
})
export class CsvDownloadComponent {
public csvFileName = `test.csv`;
private SOME_DATA: any[] = [{id: 1, name: 'Peter'}, {id: 2, name: 'Sarah'}];
constructor(
private domSanitizer: DomSanitizer,
) { }
getCSVDownloadLink() {
return this.generateCSVDownloadLink({
filename: this.csvFileName,
data: this.SOME_DATA,
columns: [
'id',
'name',
],
});
}
// you can move this method to a service
public generateCSVDownloadLink(options: { filename: string, data: any[], columns: string[] }): SafeUrl {
const fields = options.columns;
const opts = { fields, output: options.filename };
const csv = json2csv.parse(options.data, opts);
return this.domSanitizer.bypassSecurityTrustUrl('data:text/csv,' + encodeURIComponent(csv));
}
}