Using ngFor in NativeScript + Angula2 application - nativescript

I'm trying to show a list of labels with ngFor.
BELTS is a simple data structure. each item in it have a subject.
I'm getting an error:
JS: Error: Error in ./SilabusComponent class SilabusComponent - inline template:2:64 caused by:
Cannot read property 'subject' of undefined.
seems that it doesn't know item. any idea?
following is the component decoretor:
#Component({
selector: "kbm-silabus",
template: `
<StackLayout>
<Label ngFor="let item of silabusList; let i=index" [text]="item.subject"></Label>
</StackLayout>
`
})
export class SilabusComponent {
private sub: any;
silabusList: Array<Silabus>;
ngOnInit() {
this.page.actionBar.title = "KBM School";
this.sub = this.route.params.subscribe(params => {
this.id = params['id'];
this.silabusList = [];
this.silabusSubjects = [];
BELTS[this.id].silabus.forEach((item, index) => {
this.silabusList.push(new Silabus(item.subject, item.content));
console.log(item.subject);
})
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}

You have missed the asterisk sign in front of ngFor, which is very important as otherwise, you can only apply ngFor for templates with its full syntax. In NativeScript projects use the *ngFor syntax.
e.g.
<StackLayout *ngFor="let item of silabusList">
<Label [text]="item.subject"></Label>
</StackLayout>
Basically, this is how to achive the same output with the different syntax rules:
<!-- Examples (A) and (B) are the same -->
<!-- (A) *ngFor -->
<Label *ngFor="let hero of heroes" [text]="hero"></Label >
<!-- (B) ngFor with template -->
<template ngFor let-hero [ngForOf]="heroes">
<Label [text]="hero"></Label >
</template>

Related

Nativescript Vue ListPicker does not update it's items

I am trying to load topics (just string values) from a backend and display them in the ListPicker. However the ListPicker won't update it's items which should be displayed.
The code is as follows:
<template>
<Page>
<ActionBar title="Create Challenge" icon="">
<NavigationButton text="Back" android.systemIcon="ic_menu_back" #tap="goBack" />
</ActionBar>
<StackLayout>
<Label text="TOPIC" class="fab lblSubTitle"/>
<ListPicker :items="topics" v-model="selectedItem" />
<Button text="check" #tap="checkIt" />
</StackLayout>
</Page>
</template>
<script>
import {ObservableArray} from 'tns-core-modules/data/observable-array';
import {FirebaseService} from '../../services/firebase-service';
export default {
data() {
return {
selectedItem: 0,
topics: new ObservableArray(["some", "hardcoded", "items"])
};
},
methods: {
goBack() {
this.$navigateBack();
},
checkIt() {
this.topics.push("new item");
}
},
created() {
console.log("Create Challenge - Loading Topics")
// Fetch additional items from the Firebase DB
FirebaseService.fetchTopics().then(result => {
result.forEach(topic => {
this.topics.push(topic);
});
});
}
}
</script>
<style scoped>
.lblSubTitle {
font-size: 15;
margin: 10dp;
color: red;
}
</style>
So the FirebaseService.fetchTopics() returns an array of strings. This works perfektly fine and adds the received values to the ObserveableArray topics.
However the ListPicker only shows the hardcoded values. Not the dynamically added ones. Also the checkIt() method won't update the view.
I have tried to change topics to a conventional array with no effect.
Link to the Playground
NativeScript Version: 6.5.0
Android Device: Pixel 2 - Android 9
ListPicker doesn't listen to changes on ObservableArray. You must use a simple Array and mutate the changes
this.topics = [...this.topics, "new item"];

How can I change color / backgroundColor of list item in nativescript-vue?

I want to update selected item style when user taps on items. nextIndex/event.index is updated but style doesn't apply. Thanks for your help.
https://play.nativescript.org/?template=play-vue&id=ihH3iO
export default {
name: "CustomListView",
props: ["page", "title", "items", "selectedIndex"],
data() {
return {
nextIndex: this.selectedIndex ? this.selectedIndex : undefined
};
},
methods: {
onItemTap(event) {
this.nextIndex = event.index;
}
}
};
.selected {
color: white;
background-color: black;
}
<ListView for="(item, index) in items" #itemTap="onItemTap">
<v-template>
<Label :class="['list-item-label', { selected: index == nextIndex }]" :text="item" />
</v-template>
</ListView>
More info about this issue.
This is expected behavior because the ListView's item template is rendered and updated by the list view when scrolling (view recycling) if you need to make sure the list view is updated when you change your property, call refresh on it.
So the solution is
<template>
<Page class="page">
<ActionBar title="Home" class="action-bar" />
<ListView v-for="(item, index) in items" #itemTap="onItemTap" ref="listView">
<v-template>
<Label :class="[{selected: index === nextIndex}, 'list-item-label']"
:text="item" />
</v-template>
</ListView>
</Page>
</template>
<script>
export default {
name: "CustomListView",
data() {
let selectedIndex = 2;
return {
items: ["Bulbasaur", "Parasect", "Venonat", "Venomoth"],
nextIndex: selectedIndex
};
},
methods: {
onItemTap(event) {
this.nextIndex = event.index;
this.$refs.listView.nativeView.refresh();
}
}
};
</script>
You need refresh your listView the code for that is this.$refs.listView.nativeView.refresh();
Don't forget to add the ref on the <ListView>

NativeScript-Angular Tabview Custom View inside Tabs

I am trying to Implement NativeScript-Angular Tabview, I am able to create the Tabs with labels and Images.
I have seen examples for Nativescript using component for .xml files.
Is there any approach for Angular-NativeScript Projects.
The official doc provides it.
https://docs.nativescript.org/angular/cookbook/tab-view-ng
See an example below:
The table-view-test.ts file is your TypeScript component:
import { Component } from "#angular/core";
export class DataItem {
constructor(public itemDesc: string) {}
}
#Component({
selector: "tab-view-test",
templateUrl: "tab-view-test.html"
})
export class TabViewTest {
public items: Array<DataItem>;
constructor() {
this.items = new Array<DataItem>();
for (let i = 0; i < 5; i++) {
this.items.push(new DataItem("item " + i));
}
}
}
The tab-view-test.html file is your HTML template:
<TabView selectedIndex="1" selectedColor="#FF0000">
<StackLayout *tabItem="{title: 'Tab 01'}">
<Label text="Primary tab item"></Label>
</StackLayout>
<StackLayout *tabItem="{title: 'Tab 02'}">
<Label text="Second tab item"></Label>
</StackLayout>
</TabView>
I hope this helps!

TextChange in repeater nativescript

i trying do to textchange in repeater and research this link.
Basic blur event in a Telerik Nativescript Mobile App
It work in single textfield but no work in repeater. Isn't set wrong anything?
XML:
<Repeater id="lstSelectedItemsSingle" items="{{itemsSingle}}">
<Repeater.itemTemplate>
<GridLayout columns="auto,*,auto,*,auto" rows="auto,auto,1" padding="6" id = "{{ matchId + dataType + 'GridSingle'}}">
<GridLayout columns="*,*,*" rows="40" col="3" borderRadius="6" borderWidth="1" borderColor="#DBDBDB" >
<button backgroundImage="res://reduce_enable" style="background-repeat:no-repeat;background-position: 50% 50%" backgroundColor="#BFBFBF" />
<TextField col="1" backgroundColor="#ffffff" col="1" text="{{stake}}" style="text-align:center" keyboardType="number" />
<button backgroundImage="res://add_icon_enable" col="2" style="background-repeat:no-repeat;background-position: 50% 50%" backgroundColor="#BFBFBF" col="2"/>
</GridLayout>
</GridLayout>
</Repeater.itemTemplate>
</Repeater>
Model:
exports.onPageLoaded = function(args){
page = args.object;
viewM.set("stake", "2");
viewM.addEventListener(observable.Observable.propertyChangeEvent, function (event) {
console.log(event.propertyName);
}
});
}
It's probably because repeaters are bound to a list of items - usually observables. If you bind inside the repeater using "{{ }}", NativeScript is going to look for that method on that specific object in the repeater. So your code should be structured something like this (TypeScript) ...
import { Observable, EventData } from 'data/observable';
import { Page } from 'ui/page';
class Item extends Observable({
text: string = '';
constructor(text: string) {
this.text = text;
this.todos.on(ObservableArray.changeEvent, (args: any) => {
// handle text change
});
}
});
class ViewModel extends Observable({
items: ObservableArray<Items>
constructor() {
this.items = new ObservableArray<Items>({
new Item('Thing 1'),
new Item('Thing 2')
});
}
});
let loaded = (args: EventData) => {
let page = <Page>args.object;
page.bindingContext = new ViewModel();
}
export { loaded }

How to add delete button and making it work in nativescript

I'm trying to add a delete button and making it work, i'm splitting up the problems in two.
In my xml file I have this:
<Page loaded="onPageLoaded">
<GridLayout rows="auto, *">
<StackLayout orientation="horizontal" row="0">
<TextField width="200" text="{{ task }}" hint="Enter a task" id="task" />
<Button cssClass="test" text="Add" tap="add"></Button>
<Button cssClass="test" text="Refresh" tap="refresh"></Button>
</StackLayout>
<ListView items="{{ tasks }}" row="1">
<ListView.itemTemplate>
<Label text="{{ name }}" />
<Button cssClass="test" text="X" tap="delbutton"></Button>
</ListView.itemTemplate>
</ListView>
</GridLayout>
</Page>
The first problem is the delbutton, which is the delete button, if i add it like that it will replace my view with a bunch of X's. I cant seem to understand why.
The second problem i'm having trouble with is how to make it work so that it loops through and deletes the item i want to delete, what im cyrrently doing is getting data form a backend server with json that looks like this:
exports.onPageLoaded = function(args) {
page = args.object;
pageData.set("task", "");
pageData.set("tasks", tasks);
page.bindingContext = pageData;
var result;
http.request({
url: "http://192.168.1.68:3000/posts.json",
method: "GET",
headers: { "Content-Type": "application/json" },
}).then(function (response) {
result = response.content.toJSON();
for (var i in result) {
tasks.push({ name: result[i].name });
}
}, function (e) {
console.log("Error occurred " + e);
});
};
exports.delbutton = function() {
console.log("REM")
};
Thanks for your help and time.
The first problem (that only the X is showing) is due to the fact that a ListView item wants exactly one (1) child. You have two (a Label and a Button). Fortunately one item might be a so what you want to do is to enclose your two elements in a StackLayout, like this:
<Page loaded="onPageLoaded">
<GridLayout rows="auto, *">
<StackLayout orientation="horizontal" row="0">
<TextField width="200" text="{{ task }}" hint="Enter a task" id="task" />
<Button cssClass="test" text="Add" tap="add"></Button>
<Button cssClass="test" text="Refresh" tap="refresh"></Button>
</StackLayout>
<ListView items="{{ tasks }}" row="1">
<ListView.itemTemplate>
<StackLayout orientation="horizontal">
<Label text="{{ name }}" />
<Label text="{{ hello }}" />
<Button cssClass="test" text="X" tap="delbutton"></Button>
</StackLayout>
</ListView.itemTemplate>
</ListView>
</GridLayout>
</Page>
As for the second part of removing items from the ListView. I don't know if your pageData is an observable object as the declaration is not part of your pasted code, but I'm guessing it is. Anyways, I've created an example of how to populate data using observables (which is the NativeScript way of building ui:s, see previous link) and how to remove an item from the ListView.
I've added comments in the code to explain what I'm doing.
var Observable = require('data/observable');
var ObservableArray = require('data/observable-array');
/**
* Creating an observable object,
* see documentation: https://docs.nativescript.org/bindings.html
*
* Populate that observable object with an (empty) observable array.
* This way we can modify the array (e.g. remove an item) and
* the UI will reflect those changes (and remove if from the ui
* as well).
*
* Observable objects are one of NativeScripts most fundamental parts
* for building user interfaces as they will allow us to
* change an object and that change gets propagated to the ui
* without us doing anything.
*
*/
var contextArr = new ObservableArray.ObservableArray();
var contextObj = new Observable.Observable({
tasks: contextArr
});
exports.onPageLoaded = function(args) {
var page = args.object;
page.bindingContext = contextObj;
/**
* Simulating adding data to array after http request has returned json.
* Also adding an ID to each item so that we can refer to that when we're
* removing it.
*/
contextArr.push({name: 'First Item', id: contextArr.length});
contextArr.push({name: 'Second Item', id: contextArr.length});
contextArr.push({name: 'Third Item', id: contextArr.length});
};
exports.delbutton = function(args) {
/**
* Getting the "bindingContext" of the tapped item.
* The bindingContext will contain e.g: {name: 'First Item', id: 0}
*/
var btn = args.object;
var tappedItemData = btn.bindingContext;
/**
* Iterate through our array and if the tapped item id
* is the same as the id of the id of the current iteration
* then remove it.
*/
contextArr.some(function (item, index) {
if(item.id === tappedItemData.id) {
contextArr.splice(index, 1);
return false;
}
});
};

Resources