I am fetching data from an JSON placeholder api
I want to iterate over each response and show it in my list view, however I just can't get it to work. It always just shows the waiting block and never gets to the fetch block.
I am quite new to nativescript as well so any help is appreciated.
{#await posts}
<label text="Waiting"></label>
{:then data}
<listView items="{data}" row="1" colSpan="2">
<Template let:item>
{#each data as item}
<label text="{item.id}. {item.body}" textWrap="true" />
{/each}
</Template>
</listView>
{:catch}
<label text="Error occured"></label>
{/await}
async function fetchPosts() {
let allPosts = await fetch('https://jsonplaceholder.typicode.com/posts');
return await allPosts.json();
}
let posts = fetchPosts();
Your iteration method unifies every item into a huge van. Simply change let:item to let:item={item}.
Code:
...
<Template let:item={item}>
<label text="{item.id}. {item.body}" textWrap="true" />
</Template>
...
Related
I am learning to make an app in NativeScript (Angular 2). In my item page, I want to have a button so that when I press it, I can change Label into TextView/TextField for editing the information of the item.
I know that I can use editable in TextView but I still want to know if it is feasible to have the button with that functionality. Thank you !!
item.component.html:
<StackLayout>
<Label class="h3" text="Name: {{ item.get_name() }}" textWrap="true">
</Label>
<Label class="h3" text="Credit: {{ item.get_credit() }}"></Label>
<Button class="btn" text="Edit" (tap)="change()"></Button>
</StackLayout>
<!-- After pressing the button -->
<StackLayout>
<TextView class="h3" [text]="item.get_name()" textWrap="true">
</TextView>
<TextView class="h3" [text]="item.get_credit()"></TextView>
<Button class="btn" text="Save" (tap)="change()"></Button>
</StackLayout>
This can be done in many ways, but one common way is by changing visibility of control and binding it to a variable / property in the code behind.
in your component html:
Then on your component ts or code-behind you can handle it in the change method:
class MyComponentSample {
isLabelMode: boolean = true; // Set to true if you want label to show by default or false if TextView as default
change() {
this.isLabelMode = !isLabelMode; // Basically you are toggling the mode here.
}
}
BottomNavigation component requires to put all tab contents on same page.
e.g:
<TabContentItem>
<GridLayout>
<Label text="Home Page" class="h2 text-center"></Label>
</GridLayout>
</TabContentItem>
<TabContentItem>
<GridLayout>
<Label text="Account Page" class="h2 text-center"></Label>
</GridLayout>
</TabContentItem>
<TabContentItem>
<GridLayout>
<Label text="Search Page" class="h2 text-center"></Label>
</GridLayout>
</TabContentItem>
I think pages are loaded like a dynamic component. I want to have tabs only with route links. When user tap a tab, I will redirect user to another page.
If I don't use TabContentItem, TabStripItems are also not shown on page. So, I added them with empty contents.
Using selectedIndexChange event I can redirect user to another page, but when one of the tab links is current page, it goes to infinite loop.
It seems like tabs has to be on a different page on this setup. This is not something I want.
Is there a way to convert the BottomNavigation component to a route based one?
Here my current code is:
(It is a Vue project.)
<template>
<BottomNavigation selectedIndex="0" #selectedIndexChange="indexChange">
<TabStrip #itemTap="test">
<template v-for="(tab, key) in tabs">
<TabStripItem :key="key">
<Label :text="tab.title"></Label>
</TabStripItem>
</template>
</TabStrip>
<template v-for="(tab, key) in tabs">
<TabContentItem :key="key">
<GridLayout>
</GridLayout>
</TabContentItem>
</template>
</BottomNavigation>
</template>
<script>
export default {
props: {
tabs: {
type: Array,
required: true
}
},
created () {
},
methods: {
indexChange: function (args) {
let newIndex = args.value
let route = this.tabs[newIndex].route
this.goToPage(route)
},
goToPage (route) {
this.$navigator.navigate(route)
}
}
}
</script>
I have the following structure:
<ScrollView tkMainContent>
<ListView [items]="students$ | async" class="list-group" *ngIf="students$">
<ng-template let-student="item">
<StackLayout>Student details go here</StackLayout>
I'm not able to show a button inside the ScrollView when there is no student in my list.
How can I still show the button?
Note: I'm testing on a real iOS device.
<FlexboxLayout flexDirection="column">
<GridLayout class="page-content" id="placeholderLayout" visibility="{{ hasContent ? 'collapse' : 'visible' }}">
<Label class="page-icon fa" text=""></Label>
<Label class="page-placeholder" style="white-space: normal" text="Click the camera button to add image"></Label>
</GridLayout>
<ScrollView>
<-- List View Here -->
</ScrollView>
</FlexboxLayout>
I use something like this on NS Core, to show placeholder content. The way to set visibility might be different in angular, but a similar markup should work for you.
In the component.ts, you should take care to evaluate if there is content to show in list view, if there are, then set hasContent to true, and false otherwise.
Hope that helps :) let me know if you face any trouble while implementing this.
First I implemented the code below.
app.component.html
<StackLayout [formGroup]="form">
<label text="Name"></label>
<TextField formControlName="Name"></TextField>
<label text="Last Name"></label>
<TextField formControlName="LastName"></TextField>
<button text="save" (tap)="save()"></button>
</StackLayout>
app.component.ts
public form: FormGroup;
constructor(private fb: FormBuilder){
this.form = this.fb.group({
"Name": ["", [Validators.required]],
"LastName": ["", [Validators.required]]
});
}
public save(){
console.log(JSON.stringify(this.form.value));
}
When I run the code above, it's everything alright. I get name and lastname correctly.
The problem occurs when I try to add an action bar to this code.
app.component.html
<ActionBar title="New" class="action-bar">
<ActionItem text="save" (tap)="save()"></ActionItem>
</ActionBar>
<StackLayout [formGroup]="form">
<label text="Name"></label>
<TextField formControlName="Name"></TextField>
<label text="Last Name"></label>
<TextField formControlName="LastName"></TextField>
<button text="save" (tap)="save()"></button>
</StackLayout>
The app.component.ts remains the same.
When I run this code and tap the button inside stacklayout I get name and lastname correctly but when I tap the ActionItem I get an empty string for both name and lastname. Am I missing something?
Your code looks just fine - in fact I have re=-tested it with this test application and everything works as expected on my side.
Side note: If testing on iOS keep in mind that subsequent console logs can be printed only once if they are identical. (this) so it might be that it appears that the log is not printed when in fact the action is done.
I am using ListView with Header portion on top of it like below,
<StackLayout>
<StackLayout height="200">
<Label text="Header content goes in this section"></Label>
<StackLayout>
<ListView [items]='posts'>
<!-- template items goes here -->
</ListView>
</StackLayout>
When we scroll to list the header is sticky in this case.
Is there a option that scroll overrides header also ?.I mean that header also part of scroll.
Fr Angular-2 application you can now use tkTemplateKey deirective and create your own headers, footers, groups and other custom list-view elements.
Example can be found here
Here is the code for a list-view with header and groups.
page.component.html
<ListView [items]="countries" [itemTemplateSelector]="templateSelector" (itemTap)="onItemTapFirstList($event)" class="list-group" separatorColor="white">
<ng-template nsTemplateKey="header" let-header="item">
<Label [text]="header.name" class="list-group-item h3 bg-primary" isUserInteractionEnabled="false" color="white" fontSize="24"></Label>
</ng-template>
<ng-template nsTemplateKey="footer" let-footer="item">
<Label [text]="footer.name" class="list-group-item" backgroundColor="gray"></Label>
</ng-template>
<ng-template nsTemplateKey="cell" let-country="item">
<StackLayout class="list-group-item">
<Label [text]="country.name" class="list-group-item-heading"></Label>
<Label [text]="country.desc" class="list-group-item-text" textWrap="true"></Label>
</StackLayout>
</ng-template>
</ListView>
page.component.ts
#Component({
moduleId: module.id,
templateUrl: "./multi-line-grouped.component.html",
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MultiLineGroupedListViewExampleComponent implements OnInit {
public countries: Array<any> = [];
public templateSelector = (item: any, index: number, items: any) => {
return item.type || "cell";
}
ngOnInit() {
for (let i = 0; i < mockedCounties.length; i++) {
this.countries.push(mockedCounties[i]);
}
}
onItemTapFirstList(args: ItemEventData) {
console.log(args.index);
}
}
Not sure if there's another way, but one way could be moving the header inside the listview. For that to work it needs to be in the posts Array, so you may want to transform that into some sort of wrapping class that can contain eiter a header or item row. Then create two templates inside the listview that depending on the template key render a header or an item.
For details on templates, see https://docs.nativescript.org/cookbook/ui/list-view#define-multiple-item-templates-and-an-item-template-selector-in-xml
You can use *ngFor creating the list.Here is the sample code for doing this.
<ScrollView>
<StackLayout>
//define your header over here
<Label text="hey header"></Label>
<StackLayout *ngFor="let item of <Array>">
<GridLayout columns="4*,*" rows="*,">
<Label row="0" col="0" text="hey label"></Label>
</GridLayout>
<StackLayout>
<StackLayout>
</ScollView>