This is a guest post by Bradley Gore. If you'd like to work with us on a guest post, tweet us at @NativeScript.
If you're building mobile apps, it doesn't take long until you have a need for a tabbed interface. Tabbed interfaces are widely used across the mobile landscape, and apps built with NativeScript are no exception. Fortunately, there's a TabView component. Let's get to know it, shall we?
*Video tutorial here
To get started with
TabView, we simply need to declare it in our XML file and then give it some items to populate the view with:
As you can see, we have a component and it contains a set of items,
TabView.items. Each item is a
TabViewItem and it has a
title and a
view. The title is what appears in the tab, and the
TabViewItem.view is what appears in the screen when that tab is selected. There are a slew of other properties (
tabsBackgroundColor, etc...) you can give, but this is the crux of what's needed to make the
And it's that simple.. or, so it seems.
Even though this is the standard example given —actually, it's only a slightly modified copy from NativeScript's Cookbook entry— the truth of the
TabView is that it's one of those things that's easy to learn and hard to master. Things can start to get a bit tricky when you have larger views, more complex view models for each tab, etc... so we're going to have to get to know our
TabView a bit better in order to
bend it to our will use it successfully :)
Let's walk through these 4 key ingredients to effectively using the
TabViewcomponent, doesn't mean they all have to live together... Get in the habit of doing this, and you'll be glad you did. I'll show you how :)
TabViewhas some unique characteristics (at least on Android, which is where I focus for now) when it comes to when
TabViewItemviews are loaded and unloaded, and knowing this can make all the difference.
Tip: The techniques I show for managing view models can actually apply to more than just
TabView - they can apply to any views you component-ize :)
This is the first key to effectively working with the
TabView, and the benefits will be felt immediately. Let's roll with the example we started with - we have two
TabViewItems (Left Tab and Right Tab) that reside in the
TabView, and we want to separate those out. Let's start with the file structure, for that will help other things make sense as we go. Here's how I'd structure it:
After getting the files structured, I'll use XML Namespaces to bring in our views (see these NativeScript docs or this previous post of mine for a primer on how that works). So, my XML for myTabView.xml would look like this:
It may not look like a big change now, but imagine each of those two tabs had dozens of lines of XML each and then tack on a couple more
TabViewItem in the same condition and the difference becomes clear.
Now that our views are separated out, they each have their own XML and JS files - this means that setting a
bindingContext for the entire tab's view is exactly how you would for any page, and can wire up
unloaded, etc... events for our view. But, how will our view know if it's the selected view? Fortunately, the
TabView has an event for this and we can listen to it in our individual views. Here's what our leftTab.xml and leftTab.js files would look like:
View instance has a handle to the parent, and since we know in this instance that our parent is a
TabView, we can simply tap into its
selectedIndexChangeEvent. Also, notice that we're unsubscribing our handler in the
onViewUnloaded event - this is super important. Otherwise, if you have enough tabs to warrant loading/unloading views as user navigates, then your event handlers will live on after your
View has unloaded and result in running the same handler function multiple times per tab change event.
If you have a more dynamic situation, like where you may hide/show tabs based on the application state and can't depend on a statically defined
THIS_TAB_IDX, then you can just inspect the selected
TabViewItem based on the new selected index. For instance:
TabViewIteminstance has a reference to its
.viewproperty, so we can just check equality on our reference to the view, and what the selected item says its view is. There is also the
.titleproperty that could be used as well.
One interesting thing about the
TabView is how it handles (un)loading of each
TabViewItem's view. When the
TabView is first loaded, it loads up all of the items' views right then. However, as you navigate through the tabs, it starts unloading tabs that are more than one tab away from the presently selected tab, as well as loading any tabs that are only one tab away that have been unloaded. For instance, if you have 4 tabs here's a run-down of what happens:
TabView Load: All 4
TabViewItem views are loaded
Select Third Tab: First tab gets unloaded
Select Fourth Tab: Second tab gets unloaded
Select Third Tab: Second tab gets loaded
Select Second Tab: First tab gets loaded + Fourth tab gets unloaded
This truly is one of those things that are easier to show than to tell, so I proved this out in detail in the video accompanying this post. Being armed with this knowledge can make such a huge difference in how you interact with the
This part was hard for me at first. I struggled with this, posted questions to Nathanael Anderson in the NativeScript slack, and tried a multitude of techniques before finally being comfortable with this. At the end of the day, it's actually fairly simple if you understand these two key concepts:
bindingContextto any view you want to.
Viewinstance has an
_onBindingContextChanged(inherited from ui/core/bindable) that you can monkey-patch if needed.
Let's explore the use case where we have a
Page with a
TabView, and when the
Page is being navigated to it sets the binding context, and one of the
TabViewItem components will use something from that binding context for its view's binding context. Let's say the data is not asynchronously supplied, and we can use the first technique of just using a different
bindingContext for a specific
View than that of the entire
XML and JS for the Page
XML and JS for the Widgets List Tab Item
And that works quite nicely if your data isn't being loaded async (i.e. from a web service) or the
View needing a separate
bindingContext derived from the
Page is not the initially selected tab. But, what do you do if your data is async and the initially selected tab needs to derive some data from the
bindingContext of the
Page? Here's where we can monkey-patch the
We don't know when this data will actually come in - and if you're segragating your tab items' views they won't know when they can safely try to derive their data from the
Page data, so here's what we can do:
JS for the Widgets List Tab Item
This works because the dataContext you set for the Page applies to any items contained within it. This is actually true for any View. So our View gets its callback called because the promise from the Page navigatingTo event was resolved and its dataContext was updated, but we basically intercepted it and then change the context of our child item - the widgetSummary.
Now, after all that, hopefully you're more intimately familiar with the
TabView than when you started! I hope this was helpful! If it was, please drop a comment letting me know, or connect with me on twitter! Also, feel free to check out my other posts on NativeScript!