Translate json arrays with angular ngx-translate - json

I have a long text to be translated in an Ionic 4 app. I am using angular ngx-translate (#ngx-translate v11.0.1).
To improve readability I would like to have the translation in multiples lines instead of one.
I have changed my i18n json, from this (en-US.json):
"common-questions-content" : "<b>Question 1?</b> Answer 1 <br> <b>Question 2?</b> Answer 2 <b>Question 3?</b> Answer 3",
To this:
"common-questions-content" : [
"<b>Question 1?</b> Answer 1 <br>",
"<b>Question 2?</b> Answer 2 <br>",
"<b>Question 3?</b> Answer 3"
],
Unexpectedly this works! But, it puts commas between every value of the array:
I load the translation service in my app.component.ts:
import {TranslateService} from '#ngx-translate/core';
...
private translateService: TranslateService,
...
this.translateService.use('en-US');
Finally I use it in my html page like that:
{{ 'common_questions' | translate }}
Is it possible to change this behavior and just show all the text without commas?

I would recommend you to have one single input per statement and without html tags int the translations such as :
in your en.JSON :
"QUESTION_1":"blabla",
"QUESTION_2":"blabla",
"QUESTION_3":"blabla",
"ANSWER_1":"blabla",
"ANSWER_2":"blabla",
"ANSWER_3":"blabla",
Then in your component, create two class properties of type array like so:
public questions : string[];
pulbic answers : string[];
constructor (private translate: TranslateService) {
translate.get(["QUESTION_1", "QUESTION_2", "QUESTION_3"]).subscribe(
values => {
this.questions = Object.keys(values).map(key => values[key]);
}
);
translate.get(["ANSWER_1", "ANSWER_2", "ANSWER_3"]).subscribe(
values => {
this.answers = Object.keys(values).map(key => values[key]);
}
);
}
Then in your html display, customize, add click events or whatever you need :
<ion-grid>
<ion-row>
<ion-col col-6>
<ion-grid>
<ion-row *ngFor="let q of questions"><b>{{q}}</b></ion-row>
</ion-grid>
</ion-col>
<ion-col col-6>
<ion-grid>
<ion-row *ngFor="let a of answers">{{a}}</ion-row>
</ion-grid>
</ion-col>
</ion-row>
</ion-grid>
This is basic html implementation, but you see how much potential is left for you to use. you can define click events, animations, colors, selected items and so on..

A more cleaner solution which:
avoids creating unhanded subscriptions,
is reusable across different components
is to create a custom pipe:
import { Pipe, PipeTransform } from '#angular/core';
import { TranslatePipe } from '#ngx-translate/core';
#Pipe({
name: 'translateList',
})
export class TranslateListPipe extends TranslatePipe implements PipeTransform {
transform(key: any, length: number): any[] {
return [...Array(length).keys()].map((i) => super.transform(`${key}.${i}`));
}
}
And use it like so in any template with help of NgFor:
<div *ngFor="let item of 'TRANSLATION_KEY' | translateList:2">{{ item }}</div>
Where TRANSLATION_KEY is key that hols an array of 2 strings:
"TRANSLATION_KEY": ["hello", "world"]
This custom pipe extends ngx-translate's own TranslatePipe and uses super.transform(...) call under the hood, so all translation heavy lifting is still handled by ngx-translate.

Related

Property 'type' does not exist on type 'unknown'

I am receiving various errors like this after I do
ionic build --prod --release
src/app/pages/top-media/top-media.page.ts:18:16
18 templateUrl: './top-media.page.html',
~~~~~~~~~~~~~~~~~~~~~~~
Error occurs in the template of component TopMediaPage.
Error: src/app/pages/top-media/top-media.page.html:106:36 - error TS2339: Property 'type' does not exist on type 'unknown'.
106 <ng-container *ngIf="media?.type==='T'">
html
<ion-refresher slot="fixed" (ionRefresh)="topMediaSet($event)">
<ion-refresher-content pullingIcon="circles" refreshingSpinner="crescent" refreshingText="Refreshing...">
</ion-refresher-content>
</ion-refresher>
<ion-row>
<ion-col class="ion-text-center">
<ion-button class="media-btn" mode="ios" color="danger" (click)="filter('V')">
<ion-icon slot="icon-only" name="videocam-outline"></ion-icon>
</ion-button>
</ion-col>
<ion-col class="ion-text-center">
<ion-button class="media-btn" mode="ios" color="success" (click)="filter('A')">
<ion-icon slot="icon-only" name="volume-high-outline"></ion-icon>
</ion-button>
</ion-col>
<ion-col class="ion-text-center">
<ion-button class="media-btn" mode="ios" color="tertiary" (click)="filter('T')">
<ion-icon slot="icon-only" name="document-outline"></ion-icon>
</ion-button>
</ion-col>
<ion-col class="ion-text-center">
<ion-button class="media-btn" mode="ios" color="warning" (click)="filter('P')">
<ion-icon slot="icon-only" name="camera-outline"></ion-icon>
</ion-button>
</ion-col>
</ion-row>
<ion-row *ngFor="let media of topMedia | filterByType: mediaType | slice:1; let i = index">
<ng-container *ngIf="media?.type==='T'">
{{media?.message | slice:0:2000}}
</p>
The problems occurs when i use the filter I created | filterByType: mediaType
ts
topMedia:any =[];
mediaType:string = '';
constructor(
...
) {
this.topMediaSet();
}
topMediaSet(refresher?:any) {
this.offset = 0;
if (typeof refresher == 'undefined') {
this.loading = true;
}
this.userData.topMedias(this.offset).pipe(
map((data: any) => {
if (data.success) {
this.topMedia = data.topMedia;
}
if (typeof refresher != 'undefined') {
refresher.target.complete();
}
this.loading = false;
})
).subscribe()
}
filter(mediaType: string) {
this.mediaType = mediaType;
console.log(mediaType);
}
filter pipe
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'filterByType',
})
export class FilterByTypePipe implements PipeTransform {
/**
* Takes a value and makes it lowercase.
*/
transform(items: any[], type: any): any {
if (!items) return [];
if (!type) return items;
type = type.toLowerCase();
console.log('type', type);
return items.filter(it => {
if (it.type) {
return it.type.toLowerCase().includes(type);
}
});
}
}
I am getting this error if i put
filterByType: mediaType |
If I REMOVE THIS LINE FOR THE NGFOR I AM NOT GETTING ANY ERRORS BUT I NEED THE FILTER. Any hint?
Thanks
Production flags invoke stricter compilers.
Null means it doesn't exist.
You are trying to send null to a pipe, hence the error message.
Without a working stackblitz reproducing your error, it is challenging to provide an accurate answer, but try adding ngIf to your P tag like so.
This will first test for existence, with null evaluating to false.
<p class="ion-text-wrap" *ngIf="media?.message">
{{media?.message | slice:0:2000}}
</p>
change
topMedia:any =[];
to
topMedia: Array<any> =[];
this should probably solve your problem. but if you know what are you going to put inside the topMedia array, I suggest you define a Media interface and define the array like this:
export interface Media{
message: string;
...
}
...
topMedia: Array<Media> =[];
And if you are worried about message being null you should check it with *ngIf in the container of the message or above that (e.g. like the way E. Maggini said)
I got it to show no errors but it doesnt seem to be the correct thing to do. Just a workaround
I replaced
media.type
to
media['type']
If someone has a better answer please share. But it did deleted all the errors i was getting after ionic build --prod --release

How to use *ngIf on an object to check if key has a specific value without doing *ngFor firstly?

What I want to do is to show the first row in HTML (in below code) only on a condition using *ngIf. I've done the basic validation, but I have a hard time and can't find how I can do *ngIf accessing an object key directly (and not using an *ngFor before, on a different element). What I want to do is to show the row only when object.key === 'specific value'. I've tried options with "keys", "Object" but nothing seems to work. If you guys have any suggestion I would appreciate it.
my HTML
<ion-grid *ngIf="!isLoading && loadedBio.length > 0">
<ng-container *ngFor="let bio of loadedBio">
<ng-container *ngIf="bio.category === '1'">
<ion-row class="ion-padding">
<ion-col>
<ion-list class="no-last-border">
<ion-item
detail="false"
*ngIf="bio.category === '1'">
<ion-label
[ngStyle]="{
color:
bio.category === '1'
? '#A34F83'
: 'var(--ion-color-secondary)'
}"
class="bioLabel"
>{{bio.friendlyName}}</ion-label>
</ion-item>
</ion-list>
</ion-col>
</ion-row>
</ng-container>
</ng-container>
my JS object I want to access the key from has the following format
loadedBio = [{key: value, key2: value}]
If you need to display each of the properties on your found object, then, in your component, you'd want to convert that found object to an array first - where each element in the array represents a property on the object, and then iterate over it to display each element:
const obj1 = {
a: 'a title',
b: 42,
c: false
};
const arr1 = Object.values(obj1);
Put together that would look like this. First, in the component:
import { Component } from '#angular/core';
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
loadedBio = [{'key': 1, 'key2': 2}, {'key': 3, 'key2': 4}];
obj1 = {
a: 'a title',
b: 42,
c: false
};
arr1 = Object.values(this.obj1);
constructor() {
}
}
And in the view:
<ion-header>
<ion-navbar>
<ion-title>Home</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ng-container *ngIf="loadedBio">
<ion-list *ngFor="let bio of loadedBio">
<ng-container *ngIf="bio.key === 1">
<ion-item>{{bio.key}}</ion-item>
</ng-container>
</ion-list>
</ng-container>
<ng-container *ngFor="let e of arr1">
<ion-item>{{e}}</ion-item>
</ng-container>
</ion-content>
This should give you an idea of how to handle your situation. Sounds like you want to transform the data in your component, and then iterate over it need be in your view.
Here is a link to the working StackBlitz:
Best to filter your array in the controller, like this
this.filteredBio = this.loadedBio.filter(el=>el.category === 'overall')
and then simply use *ngFor on this filteredBio array. This will only have the objects whose category is 'overall'
Another solution is to implement a custom pipe Refer this

How to get dynamic list when key is unknown in html form

I am working on angular application. i am able to display list witch has key value
see example
<ion-item *ngFor="let list of lists" [(ngModel)]="lists"
<ion-row>
<ion-col col-7 class="title">
{{ list.accountName}}
how to display list when key and array both are dynamic
for example i want to display
list=[{key1:"fd",key2:"fdf"}]
in html page i dont khow what is key 1 or key 2 it will come dynamic or every time key will change so how to print
{{array.unkhownkey}
i have another solution
how to merge if i will get key in 2 different list and list in different list
for example
{{array}}.{{key}}
Using Pipe
<div *ngFor="let list of lists" [(ngModel)]="lists" >
<ion-item *ngFor="let key of list | keys" >
<ion-row>
<ion-col col-7 class="title">
{{key}}: {{c[key]}}
</ion-col>
</ion-row>
</ion-item>
</div>
ts
import { PipeTransform, Pipe } from '#angular/core';
#Pipe({name: 'keys'})
export class KeysPipe implements PipeTransform {
transform(value, args:string[]) : any {
let keys = [];
for (let key in value) {
keys.push(key);
}
return keys;
}
}
You can create a pipe which you can inject in *ngFor html.
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'forArrayProp'
})
export class ForArrayProp implements PipeTransform {
transform(value: {}): string[] {
if (!value) {
return [];
}
return Object.keys(value);
}
}
Then in templates:
<div *ngFor="let property of response">
<div *ngFor="let itemkey of property | forArrayProp">
{{property[itemkey]}}
</div>
</div>

split json array in angular 2

I am new in angular.when I try to fetch value of textbox which is in a loop getting json array in this format - in the formsubmit method if I do console.log(myForm.value) then I get this big list:
[
{
"product-0":"Voltas",
"avi-0":"5",
"def-0":"0",
"quantity-0":3,
"product-1":"Voltas1",
"avi-1":"5",
"def-1":"1",
"quantity-1":3,
"product-2":"Voltas2",
"avi-2":"5",
"def-2":"7",
"quantity-2":1,
"product-3":"Voltas0",
"avi-3":"5",
"def-3":"6",
"quantity-3":1
}
]
can anyone help me to get json in this format
[
{
"product-0":"Voltas",
"avi-0":"5",
"def-0":"0",
"quantity-0":3
},
{
"product-1":"Voltas",
"avi-0":"5",
"def-0":"0",
"quantity-0":3
}
]
in angular 2.thanks in advance.
this is my html code
<form #myForm="ngForm">
<ion-item *ngFor="let productlist of productlists;let i = index">
<ion-row>
<ion-col width-15><input type="hidden" id="hiddenValue" [(ngModel)]="productlist.product" name="product-{{i}}" /> {{productlist.product}}</ion-col>
<ion-col width-25><input type="hidden" id="hiddenValue" [(ngModel)]="productlist.avi" name="avi-{{i}}"/>{{productlist.avi}}</ion-col>
<ion-col width-25><input type="hidden" id="hiddenValue" [(ngModel)]="productlist.def" name="def-{{i}}"/>{{productlist.def}}</ion-col>
<ion-col width-25 class="add">
<button clear (click)="increment(productlist)"><ion-icon name="add-circle" ></ion-icon></button>
<ion-item><ion-input type="number" [(ngModel)]="productlist.quantity" name="quantity-{{i}}" class="quantity"></ion-input></ion-item>
<button clear (click)="decrement(productlist)"><ion-icon name="remove-circle" ></ion-icon></button>
</ion-col>
</ion-row>
</ion-item>
<button ion-button (click)="formSubmit(myForm.value)">submit</button>
</form>
In your template, you have a single form and a single form can only return a single object.
If you want to split that into multiple objects then you need to write code in your submit method to iterate through 'the big list' and create the structure you want:
[ {} ] => [ {}, {}. {} ]
You would have to search for object.keys.substr(0,6) matching 'product'. This is not a good idea even though it could work.
What I suggest instead is that you create nested forms - one form for each item in the list. I suggest you use reactive forms for this, as it is much easier.
This is because you need to create a component that represents a single product in your array:
[ {}, {}, {} ] =
[{product-item}, {product-item}, {product-item}]
ie:
ProductList-Component has a form with ngFor and inside the ngFor you load multiple children, like this:
<product-item *ngFor="let product of productlists"
[product]="product">
</product-item>
Notice that this product-item component will need an #Input, which is a formGroup, eg:
#Input()
public product: FormGroup;
See this article to learn how you can build this.
In particular see the section that starts with "First idea is obviously to extract a component that represents a single item in the form array"

Multi Tabs ionic2

i have an ionic2 app and i want to display an ion-tabs with multiple ion-tab from an array. This array i want to populate with a generic tab and pass it an array.
I have this component <multi-tab></multi-tab>:
<ion-tabs tabsPlacement="top" class="multi-tabs">
<ion-tab *ngFor="let tab of tabs" [tabTitle]="tab.title" [root]="tab.component"></ion-tab>
</ion-tabs>
And this is the ts file:
[some imports here]
export class Tab {
title: string;
component: any;
};
const tabs = [{title: 'Snacks', component: Snacks},
{title: 'Drinks', component: Drinks},
{title: 'Frozen', component: Frozen},
{title: 'Custom', component: customTab}];
#Component({
selector: 'multi-tab',
templateUrl: 'multitab.html'
})
export class multiTab {
tabs : Array<Tab>;
constructor(public modalCtrl: ModalController) {
this.tabs = tabs;
}
}
This is my custom tab component:
<ion-content>
<ion-list>
<ion-item *ngFor="let item of items">
<h2>{{item.title}}</h2>
<p>Quantity: {{item.units}}</p>
<ion-icon name="trash" item-right color="indigo100" (click)="onDelete(item)"></ion-icon>
</ion-item>
</ion-list>
<ion-fab center bottom>
<button ion-fab mini color="pink200" (click)="onAdd()"><ion-icon name="add"></ion-icon></button>
</ion-fab>
</ion-content>
And this the ts file:
[some imports]
const CustomItems = [{
'title': 'custom',
'units': 1
}];
#Component({
templateUrl: 'customTab.html'
})
export class customTab {
items : Array<Item>;
constructor(public modalCtrl: ModalController) {
this.items = CustomItems;
}
[some methods]
I want to use in all tabs the customTab and give it an array to initialize (the items array property) But the closest I've ever been is having this error:
Can't resolve all parameters for customTab(?)
when i tried:
constructor(public aux: Array<Item>) {
this.items = aux;
}
There are 2 options, either use the property rootParams (explained in depth here for example) or create a service that is injected in both components.
Both solutions have their merits, the first one is using the mechanism introduced explicitly for that purpose because ion-tabs are custom components and you can't use inputs and outputs. The second solution is the main mechanism of sharing data in angular applications (like a user session) but can seem as overkill for this small task.
There is an example of using rootParams in this commit.