Обеспечиваем полный цикл разработки
от дизайна до тестирования
Отделы Front-end, Back-end, iOS и Android разработки
Более 9 лет на рынке,
офисы в Днепре и Запорожье
Javascript разработчики могли бы захватить
мир, если бы заказчики правки по проектам
не прислали
(золотой фонд цитат Джейсона Стетхема)
Mobile OS functionality
Plugins
Native Libs
WebView JS Bridge
JavaScript
Html, CSS
Browser
Легкий и быстрый старт
Поддержка множества платформ
Доступ к основным встроенным функциям
Работа в автономном режиме
Огромное сообщество
Никаких ограничений по UI
Нет доступа к расширенным встроенным функциям
Сложности обработки данных
Mobile OS functionality
API Meta
Native Libs
JS to Native Bridge
JavaScript
UI Layout & Styles
JavaScript Engine
Повышенная производительность
Работа в UI потоке
Есть ограничения по UI
Общие абстракции над платформами
UI компоненты компилируются в нативные
Метаданные
Возможность прямой работы с нативным кодом
Довольно большое сообщество
Негативный опыт с Cordova в прошлом
ReactJS не входит в наш технологический стек
Интеграция с Angular "из коробки"
Marketplace и готовые решения
Большое сообщество в Slack
$55,838M
$39,128M
$88M
<ActionBar text="Dashboard"></ActionBar>
<GridLayout rows="160, 80, *">
<app-expenses-chart row="0">
</app-expenses-chart>
<Label [text]="balance.income"
row="1">
</Label>
<Button text="Sign In"
row="2">
</Button>
</GridLayout>
$ tns create MyFirstApp
<DatePicker loaded="onDatePickerLoaded"
day="20"
month="04"
year="1980">
</DatePicker>
import {
confirm,
ConfirmOptions
} from "tns-core-modules/ui/dialogs";
const options: ConfirmOptions = {
title: 'Confirm',
message: 'Are you sure?',
okButtonText: 'Ok',
cancelButtonText: 'Cancel',
}
confirm(options).then(handleResponse);
import { Image } from "tns-core-modules/ui/image";
import * as camera from "nativescript-camera";
camera.takePicture()
.then((imageAsset) => {
const image = new Image();
image.src = imageAsset;
})
.catch(handleError);
import * as geolocation from "nativescript-geolocation";
import { Accuracy } from "tns-core-modules/ui/enums";
geolocation.getCurrentLocation({
desiredAccuracy: Accuracy.high,
maximumAge: 5000,
timeout: 10000
})
.then(loc => {
handleLocation(loc)
})
.catch(handleError);
const clipboard = require("nativescript-clipboard");
clipboard.setText(someContent)
.then(() => {
console.log("Success");
});
clipboard.getText()
.then((content) => {
console.log("Clipboard content: " + content);
});
Позиционировать можно только top и left
Нельзя позиционировать в относительных величинах
Ни onNavigatedTo,
ни ngAfterViewInit не работают
Можно использовать setTimeout, но вы ведь не будете, правда?
Только loaded можно верить
<ScrollView>
<GridLayout rows="auto * auto auto"
class="dark">
<StackLayout row="0">
<Image [src]="wallet.logo"></Image>
<Label [text]="cashAmount(wallet) | currency">
</Label>
...
</StackLayout>
...
<StackLayout row="1">
...
<Button text="Submit"
(loaded)="handleLoaded()">
</Button>
</StackLayout>
</GridLayout>
</ScrollView>
<ul class="dropdown-menu">
<li *ngFor="let option of options">
<a href="#">
<span [class]="option.type">
</span>
{{ option.label }}
</a>
</li>
</ul>
<StackLayout class="dropdown-menu">
<StackLayout
*ngFor="let option of options"
orientation="horizontal">
<Label [class]="option.type"></Label>
<Label [text]="option.label"></Label>
</StackLayout>
</StackLayout>
<AbsoluteLayout class="dropdown-menu">
<ng-template ngFor let-option
[ngForOf]="options"
let-i="index">
<Label [class]="option.type"
[marginTop]="i * 35">
</Label>
<Label [text]="option.label"
[marginTop]="i * 35">
</Label>
</ng-template>
</AbsoluteLayout>
<StackLayout class="dropdown-menu">
<StackLayout
*ngFor="let option of options"
orientation="horizontal">
<Label [class]="option.type">
</Label>
<Label [text]="option.label">
</Label>
</StackLayout>
</StackLayout>
<DockLayout class="page" stretchLastChild="false">
<StackLayout dock="top" class="card">
<GridLayout columns="*, auto" rows="auto" class="card-h">
<StackLayout col="0" row="0" verticalAlignment="center">
<StackLayout orientation="horizontal">
<Label text="Terminator" class="h3 title m-r-2">
</Label>
<Label text="(1984)" class="h3 caption"></Label>
</StackLayout>
<Label text="Action" class="h5 caption"></Label>
</StackLayout>
<Image col="1" row="0"
src="~/images/logo.png" class="img-circle">
</Image>
</GridLayout>
<StackLayout class="hr-light"></StackLayout>
<StackLayout class="card-body">
<Label text="Lorem ipsum..."
textWrap="true" class="body caption"></Label>
</StackLayout>
</StackLayout>
<Button dock="bottom" text="Button"></Button>
</DockLayout>
<GridLayout columns="auto, *, auto"
rows="auto, auto, auto, auto"
dock="top" class="card">
<Label col="0" row="0"
text="Terminator"
class="h3 title m-r-2 m-l-12 m-t-10">
</Label>
<Label col="1" row="0" text="(1984)"
class="h3 caption m-t-10">
</Label>
<Label col="0" row="1" text="Action"
class="h5 caption m-l-12 p-b-10">
</Label>
<Image col="2" row="0" rowSpan="2"
src="~/images/logo.png"
class="img-circle m-r-12"
verticalAlignment="center">
</Image>
<Label col="0" colSpan="3" row="2"
class="hr-light">
</Label>
<Label col="0" colSpan="3" row="3"
text="Lorem ipsum..." textWrap="true"
class="body caption m-x-12 p-y-10">
</Label>
</GridLayout>
<GridLayout>
<StackLayout>
<app-element></app-element>
</StackLayout>
</GridLayout>
<GridLayout>
<StackLayout class="wrapper-class" appElement>
</StackLayout>
</GridLayout>
@Component({
selector: 'app-element, [appElement]',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppElement {
...
}
Используйте ngFor только, если все элементы списка умещаются на экране
И все равно не забывайте про trackBy
Для больших списков используйте ListView и RadListView
Не забывайте посматривать на
https://docs.nativescript.org/ui/professional-ui-components
$ ng add @nativescript/schematics
SharedProject
Web App
Mobile App
ng serve
npm run ios
npm run android
app.module.ts
app.module.tns.ts
HttpClientModule
NativeScriptHttpClientModule
HttpClient
Web App
Mobile App
export type StorageInterface = {
set: (key: string, val: string) => void;
get: (key: string) => string;
delete: (key: string) => void;
clear: () => void;
};
import {Injectable} from '@angular/core';
import {StorageInterface} from '~/app/interfaces';
@Injectable({
providedIn: 'root'
})
export class StorageService implements StorageInterface {
clear(): void {
sessionStorage.clear();
}
delete(key: string): void {
sessionStorage.removeItem(key);
}
get(key: string): string {
return sessionStorage.getItem(key);
}
set(key: string, val: string): void {
sessionStorage.setItem(key, val);
}
}
import {Injectable} from '@angular/core';
import {StorageInterface} from '~/app/interfaces';
const cache = require('nativescript-cache');
@Injectable({
providedIn: 'root'
})
export class StorageService implements StorageInterface {
clear(): void {
cache.clear();
}
delete(key: string): void {
cache.delete(key);
}
get(key: string): string {
return cache.get(key);
}
set(key: string, val: string): void {
cache.set(key, val);
}
}
storage.service.ts
storage.service.tns.ts
Web App
Mobile App
name.component.html
name.component.tns.html
name.component.scss
name.component.tns.scss
name.component.class.ts
name.component.ts
name.component.tns.ts
import {InjectionToken} from '@angular/core';
export const UNIVERSAL_ROUTER = new InjectionToken<string>('Custom router');
import {Router}
from '@angular/router';
...
@NgModule({
...
providers: [
...
{
provide: UNIVERSAL_ROUTER,
useExisting: Router
},
]
})
export class AppModule {
...
}
import {RouterExtensions}
from 'nativescript-angular/router';
...
@NgModule({
...
providers: [
...
{
provide: UNIVERSAL_ROUTER,
useClass: RouterExtensions
}
]
})
export class AppModule {
...
}
app.module.ts
app.module.tns.ts
Метаданные - соответствие между JavaScript и
Objective-C / Java кодом
const Toast = android.widget.Toast;
Toast
.makeText(
app.android.context,
message,
Toast.LENGTH_SHORT)
.show();
import android.widget.Toast;
Toast
.makeText(
getApplicationContext(),
message,
Toast.LENGTH_SHORT)
.show();
import {topmost} from 'tns-core-modules/ui/frame';
declare const CSToastManager: any;
CSToastManager.setDefaultDuration(2.0);
CSToastManager.setDefaultPosition('CSToastPositionBottom');
topmost().ios.controller.view.makeToast(message);
[CSToastManager setDefaultDuration:2.0];
[CSToastManager
setDefaultPosition:@"CSToastPositionBottom"];
[self.view makeToast:message];
Java
Objective C
JavaScript
JavaScript
plugin.common.ts
plugin.ios.ts
plugin.android.ts
export abstract class Common {
abstract property;
abstract method();
}
export class PluginClass extends Common {
property;
method() {
// iOS specific code
};
}
export class PluginClass extends Common {
property;
method() {
// Android specific code
};
}
Мы не используем NativeScript, если в приложении задействованы низкоуровневые или специализированные API
Мы не используем NativeScript, если элементы дизайна слишком далеки от стандартных и сложнокастомизируемы
Использование NativeScript оправдано даже для одной платформы, если у вас есть специалисты по бизнес-логике приложения
Использование NativeScript оправдано, если дизайнеры согласовывают элементы дизайна с разработчиками
NativeScript хорошо зарекомендовал себя, обеспечивая поддержку начиная с Android 5 и iOS 9
SergeyMell
Seroga.Mell
Sergey Melashych
sergey.mell@agilie.com