How to pass bindingContext to top-level TabView - nativescript

I need to localize TabViewItem title when TabView used as main navigation with Frame items (app generated with Nativescript Sidekick -> General -> TypeScript -> Tab Navigation).
For localization I use nativescript-localize plugin, with next initialization in app.ts for use in template:
import * as app from "tns-core-modules/application";
import { localize } from "nativescript-localize";
app.getResources().L = localize;
app.run({ moduleName: "app-root" });
And app-root.xml has next content:
<TabView androidTabsPosition="bottom">
<TabViewItem title="{{ L('catalog') }}">
<Frame defaultPage="pages/catalog/catalog-page"></Frame>
</TabViewItem>
<TabViewItem title="{{ L('notifications') }}">
<Frame defaultPage="pages/notifications/notifications-page"></Frame>
</TabViewItem>
</TabView>
If I use next construction {{ L('catalog') }} in final pages with initialized bindingContext then everything works as expected. But in top-level TabView this construction does not work. I think it is because not initialized bindingContext, but how can I initialize it for top-level TabView.
For testing and prototyping, I create Playground - top level TabView. In this Playground instead nativescript-localize plugin I use the function which return text, and as you can see it successfully executed in home-page.xml, but does not executed in app-root.xml.

I suspect it's an edge case bug, it seems the expressions are not processed until a bindingContext is set.
Just declaring bindingContext attribute in XML seems to solve the issue
<TabView androidTabsPosition="bottom" bindingContext="">
Updated Playground

Related

Nativescript (tap) event on a custom Angular component

I'm trying to bind a (tap) event on a custom Angular component in nativescript.
I created a custom component called 'ns-custom' and tried to bind the (tap) event to it. But it doesn't work.
In the custom component I'm doing this:
<StackLayout>
<Label text="Title"></Label>
<Label text="some text"></Label>
</StackLayout>
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'ns-custom',
templateUrl: './custom.component.html',
styleUrls: ['./custom.component.css']
})
export class CustomComponent{
constructor() { }
}
And in the parent element I'm doing this:
<ns-custom (tap)="onCustomComponentClick()"></ns-custom>
onCustomComponentClick() {
console.log('custom-component was tapped');
}
I expect the (tap) event to fire when I tap the custom component, but it does not. I built the same structure in pure Angular, and the (click) event does fire if put to a custom component.
I tried to propogate the (tap) event from the custom component like below, but then it fired twice (as expected because the tap event would propogate up to the parent component if I don't use event.stopPropagation()):
<StackLayout (tap)="emitTap()">
<Label text="Title"></Label>
<Label text="some text"></Label>
</StackLayout>
#Component({
selector: 'ns-custom',
templateUrl: './custom.component.html',
styleUrls: ['./custom.component.css']
})
export class CustomComponent{
#Output() tap = new EventEmitter();
emitTap() {
this.tap.emit();
}
}
And then catch the event on the parent component:
<ns-custom (tap)="onCustomComponentClick()"></ns-custom>
onCustomComponentClick() {
console.log('custom-component was tapped');
}
But now the tap event fires twice. I can solve it by changing the EventEmitter name to something other than 'tap' (for ex. #Output() childTapped = new EventEmitter(); and <ns-custom (childTapped)="onCustomComponentClick()"></ns-custom>) or pass the $event object on tap and then use event.stopPropagation(), but this is not elegant at all.
Any idea how to solve this simple problem in an elegant way?
This is basically answered by #mick-morely in the comments but I thought I would write up a more descriptive example and why I think it is a useful way of doing it.
You basically need to create a custom event for your custom component. While it seems tedious not to be able to re-use the tap event, it can actually improve your code quality, making it more descriptive.
So if I have a custom HoleComponent describing a "Hole", it can look like this:
Hole Template
<GridLayout rows="*" columns="*" (tap)="emitTap()">
<Label row="0" text="A very deep hole"></Label>
</GridLayout>
Hole Code
#Output() holeTap = new EventEmitter();
emitTap() {
this.holeTap.emit();
}
This Hole component can then be used by a parent like this:
Parent template
<Hole (holeTap)="onHoleTap()"></Hole>
Making the event explicitly named actually helps to make the code more readable imho. It also forces the developer to think more about the Domain they are working with which helps in conforming to the ubiquitous language if working with DDD is your thing.

How to make a NativeScript switch component’s state (ON/OFF) persist between page/app navigation based on user interaction?

My problem:
Currently, I have a switch component I’m fiddling with.
My goal is to be able to click the switch component “ON” and have it stay switched “ON” when I navigate back to the home or another page.
Attempted solutions & experimentation:
I attached a method to the native (checkedChanged) event that comes “built-in” with NativeScript switch components.
Passed the built-in (checkedChanged) event, the event data from the switch click.
Then brute forced the switch component’s “checked” attribute, to permanently be TRUE or FALSE in two separate test cases on first user click, regardless of subsequent clicks...but whenever I navigate away from the page the switch in question is reset. I know this because my flag/counter is also reset. Every time I navigate away from the page.
Any tips on how to do this. Say I click a switch “ON” in one page in the app and navigate to another page in the same app. Or close out the app completely is there any way to make the state persist? Meaning that switch stays switched ON or OFF based on human interaction/toggling?
Resources:
https://docs.nativescript.org/angular/ui/ng-ui-widgets/switch
Use application-settings to store simple value persistently. There are also options like nativescript-localstorage / SQLite for storing data persistently. application-settings may be a good choice for simple key value pairs.
import { Component } from "#angular/core";
import { setBoolean, getBoolean } from "tns-core-modules/application-settings";
#Component({
selector: "Settings",
moduleId: module.id,
template: `
<ActionBar title="Settings" class="action-bar">
</ActionBar>
<GridLayout>
<ScrollView class="page">
<StackLayout class="form">
<GridLayout columns="*,auto">
<Label text="On / Off" class="h2"></Label>
<Switch [ngModel]="value" (ngModelChange)="onSwtichChange($event)"></Switch>
</GridLayout>
</StackLayout>
</ScrollView>
</GridLayout>
`
})
export class SettingsComponent {
value = getBoolean("my-value", false);
constructor() {
}
onSwtichChange(value: boolean) {
setBoolean("my-value", value);
}
}
Playground Sample

Hide tab buttons on Nativescript-Angular TabView

I'm trying to find a way to remove the tab buttons on a element with an Angular 6 app but with no avail so far. Basically, I only want to keep the Tab contents and their swipe functionality.
Apparently there are specific android and iOS methods you can use but I'm unsure how to do that.
<TabView [(ngModel)]="tabSelectedIndex" (selectedIndexChanged)="onSelectedIndexChanged($event)" (loaded)="tabViewLoaded($event)">
<ng-container *ngFor="let article of articles" #tabView>
<StackLayout *tabItem="{title: article.id}">
<StackLayout>
<NewsDetails></NewsDetails>
</StackLayout>
</StackLayout>
</ng-container>
</TabView>
On my .ts file I can find a reference to the element like this:
#ViewChild("tabView") tabView: ElementRef;
ngAfterViewInit() {
console.dir(this.tabView.nativeElement);
}
But I have no idea what to do from now on. Any ideas? All previous questions regarding this have not worked.
Here is a sample playground link: https://play.nativescript.org/?template=play-ng&id=iK9ZTM
Use the code below with the loaded event of TabView.
onTabViewLoaded(event: EventData) {
const tabView = <TabView>event.object;
if (isIOS) {
tabView.viewController.tabBar.hidden = true;
}
if (isAndroid) {
const tabLayout = tabView.nativeViewProtected.tabLayout;
tabLayout.getLayoutParams().height = 0;
tabLayout.requestLayout();
}
}
I recently did that for a sample work I posted in Uplabs

NativeScript Angular 2 Get more data when Scroll to Bottom (endless scrolling)

HI for example below is my view
<ScrollView>
<StackLayout>
<StackLayout *ngFor="let kid of kids" id="kidList">
<StackLayout orientation="horizontal" class="some-class">
<Lable text="{{ kid.fname }} {{ kid.lname }}"></Lable>
<Lable text="{{ kid.age }} years ago"></Lable>
</StackLayout>
</StackLayout>
</StackLayout>
</ScrollView>
I want to append the more data getting from server to 'kidList' when scroll reaches to bottom of screen in {N} aAngular2.
It's very hard to me build the layouts and adding childs in 'js' like below(KidInformation has more data).
let stackLayout = new StackLayout();
stackLayout.addChild('something newly constructed with JS')
Is there a way we can do in My Component just by adding child as view by passing local parameters to view , I mean like in below way
let kidListStacklayout = view.getViewById(this.page, 'kidList');
kidListStacklayout.addChild('views/kid/kid-item.html')
and kid-item.html will look like
<StackLayout orientation="horizontal" class="some-class">
<Lable text="{{ kid.fname }} {{ kid.lname }}"></Lable>
<Lable text="{{ kid.age }} years ago"></Lable>
</StackLayout>
The stock list view supports what you want. Don't use a scrollview and adding more layouts to the screen. This will cause lag and other issues. A listview recycles UI view components to reduce overhead of the layout growing in size. You want to use the loadMore event on the list view. https://docs.nativescript.org/cookbook/ui/list-view
Of course as the comment above ^^^ the free UI suite from telerik provides the RadListView which also supports infinite scrolling with a loadMore event.
Find out how to do it ( using Angular & TypeScript) :
import { ScrollView, ScrollEventData } from "ui/scroll-view";
#Component({...})
class ComponentClass implements OnInit, AfterViewInit {
#ViewChild("scrollid") scrollView: ElementRef;
ngOnInit(){
let scrollv : ScrollView = <ScrollView>this.scrollView.nativeElement;
scrollv.on(ScrollView.scrollEvent, function (args: ScrollEventData) {
if(scrollv.scrollableHeight === args.scrollY){
console.log("load more items here !!! ");
}
});
}
}
The scrollv.scrollableHeight gets updated by itself.
Tested on android emulator only. Must work on both Plateforms.

Nativescript - Lost databinding after app resume

I have a View which is binded to a ViewModel (that inherits from Observable) with a TextField and a Switch.
When i change the values, all is binded perfectly and I can see my new values.
But if I go to the home screen and then, go back to the app, the values are erased.
The view is a basic form like a customer CRUD.
Here is the XML :
<Page xmlns="http://schemas.nativescript.org/tns.xsd"
loaded="loaded">
<!-- ... -->
<TextView text="{{ valueA }}" />
<!-- ... -->
<Slider value="{{ valueB }}" minValue="1950" maxValue="2016" />
</Page
Here is the code-behind :
let viewModel: ViewModel;
let page: Page;
export function loaded(args: EventData) {
page = <Page>args.object;
setTimeout(function() {
viewModel = new ViewModel();
page.bindingContext = viewModel;
}, 0);
}
Do you have any idea why i loose my form data ?
Most likely, when you come back to the app, the loaded event is fired where you are constructing the view model. You should try constructing in a different page event (navigatedTo) or persist the data using the "application-settings" module. Then read the values from app-settings when you are constructing the observable view model. Hope this helps.
https://docs.nativescript.org/ApiReference/application-settings/HOW-TO.html

Resources