Property System
                NativeScript provides own property system based on a wrapper
                around the well known JavaScript's
                Object.defineProperty. To deliver good developer
                experience in the context of mobile development with UI and CSS
                elements, we provided extended classes of the
                Property class. This article will cover the
                provided property classes and the base techniques when working
                with views and properties including initialization, registering,
                views lifecycles and recycling and handling changed events. Some
                commonly used methods of View are demonstrated as
                well.
              
Property System Classes
                The implementation of all property classes can be found under
                @nativescript/core module. Below, we are going to
                look at all exposed classes from that module.
              
Property class
                Property is a simple wrapper around
                Object.defineProperty
                with some additional callbacks like valueChange,
                valueConverter and equalityComparer.
                When you define property you specify the owning type and the
                type of the property:
              
export const textProperty = new Property<MyButtonBase, string>({
    name: "text",
    defaultValue: "",
    affectsLayout: true
});
textProperty.register(MyButtonBase);
                Looking at
                textProperty: Property<MyButtonBase, string>
                - the owning type is MyButtonBase meaning that this
                property will be defined on instances of
                MyButtonBase. The type of the property is
                string so it will accept any text.
              
                The valueChange event is executed when the value of
                a property has changed. If the type of the property isn't
                string we will need to specify
                valueConverter and equalityComparer.
                The valueConverter is called if a string value is
                set to this property (for example from XML or CSS) and there you
                will have to convert that string to meaningful value if possible
                or throw exception if you can't. If
                equalityComparer is specified it will be called
                everytime a value is set to a property. There you can compare
                current and new value for equality. For example, if your
                property is of type Color you can use
                Color.equals as
                equalityComparer function so even if new instance
                of Color is set the comparer will return
                false if current color and new color have the same
                argb value.
              
                There is one more option in the
                Property constructor:
                affectsLayout: boolean. When set to
                true setting new value to this property will
                trigger a new layout pass. This is done as performance
                optimization. Android has an integrated layout system so most of
                the time it will invalidate itself when needed. Thus we skip one
                native call by defining affectsLayout as
                true only for iOS for example using 'isIOS' boolean
                property. Because iOS doesn't have integrated layout system if
                you know that this property could affect the layout you should
                specify it in the Property constructor.
              
                The flag affectsLayout should be true
                (mainly for iOS) when setting that property will change
                the element size and/or position. For example in our case
                setting button text to something different will either widen or
                shorten the width of the button so this will affect the element
                dimension hence with specify it as
                affectsLayout: isIOS. If this property won't change
                element position/size then you don't have to specify
                affectsLayout at all. For example
                background-color property doesn't change element
                position/size.
              
Note: In the platform specific implementation use
getDefaultandsetNativesymbols from the property object (example: textProperty), to define how this property is applied to native views. ThegetDefaultmethod is called just once before the first call tosetNativeso that we know what is the default native value for this property. The value that you return will be passed tosetNativemethod when we decide to recycle the native view. Recycling the native view of control is done only ifrecycleNativeViewfield is set to true.
CssProperty Class
                The CssProperty is very similar to
                Property type with two small differences:
              
- 
                  you have to additionally specify cssNamewhich will be used to set this property through CSS
- its value can be set from inline styles, page CSS or application CSS
export const myOpacityProperty = new CssProperty<Style, number>({
    name: "myOpacity",
    cssName: "my-opacity",
    defaultValue: 1,
    valueConverter: (v) => {
        const x = parseFloat(v);
        if (x < 0 || x > 1) {
            throw new Error(`opacity accepts values in the range [0, 1]. Value: ${v}`);
        }
        return x;
    }
});
myOpacityProperty.register(Style);
Note: For CSS properties that could be animated via keyframe animations, you can use the extended
CssAnimationPropertywhich comes with the optionalkeyframeparameter.
InheritedCssProperty Class
                The InheritedCssProperty is a property defined on
                Style type. These are inheritable CSS properties that could be
                set in CSS and propagates value on its children. These are
                properties like FontSize, FontWeight, Color, etc.
              
export const selectedBackgroundColorProperty = new InheritedCssProperty<Style, Color>({
    name: "selectedBackgroundColor",
    cssName: "selected-background-color",
    equalityComparer: Color.equals,
    valueConverter: (v) => new Color(v)
});
selectedBackgroundColorProperty.register(Style);
ShorthandProperty Class
The shorthand property provides the capability to provide shorthand syntax rules for your CSS properties. For example, instead of the explicit side-by-side syntax for all four margins
margin-top:  0;
margin-right: 10;
margin-bottom: 0;
margin-left: 10;
                The user would want to use the shorthand syntax for
                margin as follows:
              
margin: 0 10 0 10;
                Creating the shorthand margin property would
                require to have all CSS properties defined. This way, you could
                use them to set the syntax rule in our shorthand property
                getter.
              
const marginProperty = new ShorthandProperty<Style, string | PercentLength>({
    name: "margin",
    cssName: "margin",
    getter: function (this: Style) {
        if (PercentLength.equals(this.marginTop, this.marginRight) &&
            PercentLength.equals(this.marginTop, this.marginBottom) &&
            PercentLength.equals(this.marginTop, this.marginLeft)) {
            return this.marginTop;
        }
        return `${PercentLength.convertToString(this.marginTop)} ${PercentLength.convertToString(this.marginRight)} ${PercentLength.convertToString(this.marginBottom)} ${PercentLength.convertToString(this.marginLeft)}`;
    },
    converter: convertToMargins
});
marginProperty.register(Style);
export const marginLeftProperty = new CssProperty<Style, PercentLength>({ name: "marginLeft", cssName: "margin-left", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals, valueConverter: PercentLength.parse });
marginLeftProperty.register(Style);
export const marginRightProperty = new CssProperty<Style, PercentLength>({ name: "marginRight", cssName: "margin-right", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals, valueConverter: PercentLength.parse });
marginRightProperty.register(Style);
export const marginTopProperty = new CssProperty<Style, PercentLength>({ name: "marginTop", cssName: "margin-top", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals, valueConverter: PercentLength.parse });
marginTopProperty.register(Style);
export const marginBottomProperty = new CssProperty<Style, PercentLength>({ name: "marginBottom", cssName: "margin-bottom", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals, valueConverter: PercentLength.parse });
marginBottomProperty.register(Style);
CoercibleProperty Class
                The CoercibleProperty is a property that extends
                the base Property class by providing the capability to be
                coercible. For better illustration when a property might need to
                be coercible let's assume that we are working on
                selectedIndex property of some UI element that can
                hold different number of items. The base case would
                suggest that the selectedIndex would vary within
                the number of items, but what would cover the case where the
                items are changed dynamically (and the
                selectedIndex is not within the length range)? This
                is the case that can be handled by a property that can coerce
                the value.
              
                Creating the selectedIndex as coercible property
                dependent on the number of items
              
export const selectedIndexProperty = new CoercibleProperty<SegmentedBarBase, number>({
    name: "selectedIndex", defaultValue: -1,
    valueChanged: (target, oldValue, newValue) => {
        target.notify(<SelectedIndexChangedEventData>{ eventName: SegmentedBarBase.selectedIndexChangedEvent, object: target, oldIndex: oldValue, newIndex: newValue });
    },
    // in this case the coerce value will change depending on whether the actual number of items
    // is more or less than the value we want to apply for selectedIndex
    coerceValue: (target, value) => {
        let items = target.items;
        if (items) {
            let max = items.length - 1;
            if (value < 0) {
                value = 0;
            }
            if (value > max) {
                value = max;
            }
        } else {
            value = -1;
        }
        return value;
    },
    valueConverter: (v) => parseInt(v)
});
selectedIndexProperty.register(SegmentedBarBase);
                When setting the items property we will coerce the
                selectedIndex
              
[itemsProperty.setNative](value: SegmentedBarItem[]) {
    this.nativeViewProtected.clearAllTabs();
    const newItems = value;
    if (newItems) {
        newItems.forEach((item, i, arr) => this.insertTab(item, i));
    }
    selectedIndexProperty.coerce(this);
}
Registering the Property
After a property is defined it needs to be registered on a type like this:
textProperty.register(MyButtonBase);
                The CssProperties should be registered on the
                Style class like this:
              
// Augmenting Style definition so it includes our myOpacity property
declare module "@nativescript/core/ui/styling/style" {
    interface Style {
        myOpacity: number;
    }
}
// Defines 'myOpacity' property on Style class.
myOpacityProperty.register(Style);
                The registration defines that property for the type passed on to
                register method.
              
Note: Make sure that put your
registercall after your class definition or you will get an exception.
Value Change Event
                To get notification when some property value change a
                propertyNameChange has to be specified as
                eventName to addEventListener method.
              
textField.addEventListener('textChange', handler...)
NativeView Property
                Use nativeView instead of ios and
                android properties. The ios and
                android properties are left for compatibility.
                However, all view-lifecycle methods and native property
                callbacks (explained below) should work with the
                nativeView property.
              
Views Lifecycle and Recycling
NativeScript 3.0 introduced nativeView recycling. With nativeView recycling is aimed to reduce instantiation of native views which is really expensive operation in Android. In order to be able to recycle it, we need all properties exposed from the View to be of our property system.
We have method that gets the default value for a property which is get the first time a property value is changed. Once we know that our View is not needed anymore we will reset the native view to its original state and put it in a map where some future Views of the same type could reuse it. There are 3 new important methods:
- 
                  createNativeView- you override this method, create and return your nativeView
- 
                  initNativeView- in this method you setup listeners/handlers to the nativeView
- 
                  disposeNativeView- in this method you clear the reference between nativeView and javascript object to avoid memory leaks as well as reset the native view to its initial state if you want to reuse that native view later.
                In Android, avoid access to native types in the root of the
                module (note that ClickListener is declared and
                implemented in a function which is called at runtime). This is
                specific for the V8 snapshot feature which is generated on a
                host machine where android runtime is not running. What is
                important is that if you access native types, methods, fields,
                namespaces, etc. at the root of your module (e.g. not in a
                function) your code won't be compatible with V8 snapshot
                feature. The easiest workaround is to wrap it in a function like
                in the above initializeClickListener function.
              
                In this implementation, we use singleton listener (for Android -
                clickListener) and handler (for iOS -
                handler) to reduce the need to instantiate native
                classes and to reduce memory usage. If possible, it is
                recommended to use such techniques to reduce native calls.
              
Iterating Over View Children
                There are two methods that allow you to traverse view-hierarchy.
                Both of them accept a callback function that is
                called for each child. The callback should return a boolean
                value - if it is falsy the iteration will break. This is
                particularly useful if you are searching for a specific view and
                you want to stop iterating as soon as you have found it.
              
For getting View children use:
public eachChildView(callback: (child: View) => boolean): void
                This method was previously known as
                _eachChildView(). It will return
                View descendants only. For example TabView returns
                the view of each TabViewItem because is TabViewItem is of type
                ViewBase.
              
Getting ViewBase children use:
public eachChild(callback: (child: ViewBase) => boolean): void;
                This method will return all views including
                ViewBase. It is used by the property system to
                apply native setters, propagate inherited properties, apply
                styles, etc. In the case of TabView – this method will return
                TabViewItems as well so that they could be styled through CSS.
              
View Class Common Methods
                Each UI element extends the View class (e.g., like
                StackLayout or Label) and comes with a
                set of methods created to ease the UI development. Methods
                related to measuring and positioning should be called in
                navigatedTo event of the current
                Page to ensure that all layout measuring has
                passed.
              
- 
                  getViewById- Returns the child view with the specified id.<Page navigatedTo="onNavigatedTo"> <StackLayout id="myStack"> <Label text="Tap the button" /> <Button text="TAP" tap="" /> <Label id="myLabel" text="" /> </StackLayout> </Page>export function onNavigatedTo(args: EventData) { const page = <Page>args.object; let stack = <StackLayout>page.getViewById("myStack"); // e.g. StackLayout<myStack>@file:///app/page.xml:2:5; let label = <Label>stack.getViewById("myLabel"); // e.g. Label<myLabel>@file:///app/main-page.xml:5:9; }export function onNavigatedTo(args) { const page = args.object; let stack = page.getViewById("myStack"); // e.g. StackLayout<myStack>@file:///app/page.xml:2:5; let label = stack.getViewById("myLabel"); // e.g. Label<myLabel>@file:///app/main-page.xml:5:9; }
Angular Specific Note: In Angular to use
getViewByIdfor root search, we might need to inject nativePageobject. As an alternative, in Angular, we can get reference to the native elements using theViewChilddirective and thenativeElementproperty.<StackLayout #myNgStack id="myStackId"> <Label text="Using ViewChild against getViewById" /> </StackLayout>import { ViewChild, ElementRef } from "@angular/core"; export class MyComponent { @ViewChild("myNgStack") stackRef: ElementRef; myNativeStack: StackLayout; constructor(private _page: Page) { } ngAfterViewInit() { this._page.getViewById("myStackId"); this.myNativeStack = this.stackRef.nativeElement; // this._page.getViewById("myStack") === this.myNativeStack } }
- 
                  getActualSize- Returns the actual size of the view in device-independent pixels. The returned value is of typeSize.let stackSize: Size = stack.getActualSize(); let stackWidth = stackSize.width; // e.g. 411.42857142857144 DIP let stackHeight = stackSize.height; // e.g. 603.4285714285714 DIPlet stackSize = stack.getActualSize(); let stackWidth = stackSize.width; let stackHeight = stackSize.height;
- 
                  getLocationInWindow- Returns the location of this view in the window coordinate system. The returned value is of typePoint.let locationInWindow: Point = stack.getLocationInWindow(); let locationWindowX = locationInWindow.x; // e.g. 10 let locationWindowY = locationInWindow.y; // e.g. 80let locationInWindow = stack.getLocationInWindow(); let locationWindowX = locationInWindow.x; let locationWindowY = locationInWindow.y;- 
                      getLocationOnScreen- Returns the location of this view in the screen coordinate system. The returned value is of typePoint.
 let locationOnScreen : Point = stack.getLocationOnScreen(); let locScreenX = locationOnScreen.x; // e.g. 10 let locScreenY = locationOnScreen.y; // e.g. 67.42857142857143var locationOnScreen = stack.getLocationOnScreen(); var locScreenX = locationOnScreen.x; var locScreenY = locationOnScreen.y;- 
                      getLocationRelativeTo- Returns the location of this view against another view's coordinate system. The returned value is of typePoint.
 let labelLocationRelativeToStack: Point = label.getLocationRelativeTo(stack); let labelRelativeX = labelLocationRelativeToStack.x; let labelRelativeY = labelLocationRelativeToStack.y;let labelLocationRelativeToStack = label.getLocationRelativeTo(stack); let labelRelativeX = labelLocationRelativeToStack.x; let labelRelativeY = labelLocationRelativeToStack.y;
- 
                      
View Properties module Property CssProperty CssAnimationProperty InheritedCssProperty ShorthandProperty CoercibleProperty isIOS