构建NativeScript跨平台项目

前言


当谈到学习例子,肯定是angular中文社区的英雄教程,发现在官方项目中,这是一个很棒的教程,因为它涵盖了很多主题,但是,它是一个web应用教程。如果我们想从它建立一个移动应用程序,或者更具体地说,一个与Android和iOS支持的原生移动应用程序呢?

我们将看到如何建立一个英雄之旅启发了iOS和Android的移动应用程序的使用angular和nativescript。
让我们弄清楚我们在这里得到什么。重要的是要注意,这是一个英雄指南,因为它不会是完全相同的。我想保留一些原创性。



构建NativeScript跨平台项目_第1张图片


我们不会做任何HTTP请求对一个REST API,但我们将模拟他们的模拟数据。我们要证明的一点就是,本地移动应用程序可以创建angular和nativescript使用几乎相同的代码和逻辑。


要求:


由于我们正在开发一个移动应用程序,不再只是一个基于浏览器的Web应用程序,有几个要求,必须满足这个项目是成功的。
nativescript 2.5 +
Android SDK
Xcode的iOS
Android SDK是必需的,如果你想建立的Android平台。它是兼容的Linux,Mac和Windows。Xcode是如果你希望建立的iOS平台。由苹果设定的每个限制,iOS的开发只能发生在Mac上。有关配置nativescript每个构建工具的更多信息,请访问官方nativescript文档。


创建一个angular工程支持nativescript项目:


我们将创建一个新项目英雄之旅的应用程序。在这一点上所有的创建要求必须满足。
从nativescript CLI执行以下:

创建项目

tns create tour-of-heroes --ng

导航至项目地址

cd tour-of-heroes
使用命令行添加到移动平台
tns platform add ios
tns platform add android
在设备上运行应用程序(--emulator是默认模拟器上运行)
tns run android --emulator
tns run ios --emulator

创建项目后,我们将留下一个文件和目录结构,这是一个有点不同的一个可能已创建的angular-CLI。我们所有的开发都会发生在这个项目的应用程序目录中,如果你来自web开发的话,应该看起来很熟悉。


创建具有angular的数据服务:


官方英雄之旅应用程序HTTP请求对一个静态的API来消耗数据。由于API开发很容易成为自己的一个主题,我们将使用模拟数据代替。这个模拟数据将在一个angular服务内驻留一系列函数。
创建一个app/services/ data.service.ts文件在您的项目具有以下文件的代码:


import { Injectable } from "@angular/core" ;
 
@ Injectable ( )
export class DataService {
 
     private heroes: Array < any > ;
 
     public constructor ( ) {
         this . heroes = [
             {
                 "id" : 1 ,
                 "name" : "Captain America"
             } ,
             {
                 "id" : 2 ,
                 "name" : "Iron Man"
             } ,
             {
                 "id" : 3 ,
                 "name" : "Hulk"
             } ,
             {
                 "id" : 4 ,
                 "name" : "Black Widow"
             } ,
             {
                 "id" : 5 ,
                 "name" : "Thor"
             }
         ] ;
     }
 
     public getHeroes ( ) : Array < any > {
         return this . heroes ;
     }
 
     public getHero ( id : number ) : any {
         for ( let i = 0 ; i < this . heroes . length ; i ++ ) {
             if ( this . heroes [ i ] . id == id ) {
                 return this . heroes [ i ] ;
             }
         }
         return - 1 ;
     }
 
     public delete ( id : number ) {
         for ( let i = 0 ; i < this . heroes . length ; i ++ ) {
             if ( this . heroes [ i ] . id == id ) {
                 this . heroes . splice ( i , 1 ) ;
                 break ;
             }
         }
     }
 
     public add ( value : string ) {
         this . heroes . push (
             {
                 "id" : Math . floor ( Math . random ( ) * ( 100 - 1 ) ) + 1 ,
                 "name" : value
             }
         ) ;
     }
 
     public edit ( id : number , name : string ) {
         for ( let i = 0 ; i < this . heroes . length ; i ++ ) {
             if ( this . heroes [ i ] . id == id ) {
                 this . heroes [ i ] . name = name ;
                 break ;
             }
         }
     }
 
}

那么我们在上面的代码中做了什么,为什么我们需要它呢?


首先,通过创建一个angular服务,我们创建一个可以在应用程序的每个组件之间共享的单组件。
在构造函数方法中,我们定义了一些示例数据。服务中的每个方法都将操纵构造函数方法中定义的模拟数据。
英雄数据将包含一个ID和一个名字。当创建一个新的英雄,ID将是一个随机数。当使用API时,所有的方法都会击中API,API会担心生成或查询。


在服务可以使用它必须注入到我们的应用程序的“ngmodule块。这一块可以在app/ app.module.ts文件中找到:


import { NgModule , NO_ERRORS _SCHEMA } from "@angular/core" ;
import { NativeScriptModule } from "nativescript-angular/nativescript.module" ;
import { NativeScriptFormsModule } from "nativescript-angular/forms" ;
import { AppRoutingModule } from "./app.routing" ;
import { AppComponent } from "./app.component" ;
 
import { DataService } from "./services/data.service" ;
 
@ NgModule ( {
     bootstrap : [
         AppComponent
     ] ,
     imports : [
         NativeScriptModule ,
         NativeScriptFormsModule ,
         AppRoutingModule
     ] ,
     declarations : [
         AppComponent
     ] ,
     providers : [ DataService ] ,
     schemas : [
         NO_ERRORS _SCHEMA
     ]
} )
export class AppModule { }

我们已经引进了数据服务类和添加到“ngmodule块供应商阵列。这不会是我们最后一次更改app/ app.module.ts文件。
对一个nativescript应用服务的更多信息,查看以前的教程,我写的,在一个nativescript-angular应用共享供应商的工作。


配置应用程序路由器并定义使用的页面:


此应用程序将有三个不同的页面。我们将有一个仪表板,将给我们一个快速看看我们的英雄,一个页面上市和添加新的英雄,和一个页面编辑现有的英雄。
请在项目中创建下列文件和目录:


mkdir - p app / components / dashboard
mkdir - p app / components / heroes
mkdir - p app / components / hero
touch app / components / dashboard / dashboard .component .ts
touch app / components / dashboard / dashboard .component .html
touch app / components / heroes / heroes .component .ts
touch app / components / heroes / heroes .component .html
touch app / components / heroes / hero .component .ts
touch app / components / heroes / hero .component .html

如果你的操作系统不允许mkdir命令,去创建这些文件和目录,可以采用手动。
虽然我们刚刚创建了我们的网页,这些实际上是初始页面。这是因为我们希望使用分段的条形图能够在它们之间切换。分段的条形图将作为父页存在,该母页控制对每个子页的导航。为此,创建以下:

mkdir - p app / components / parent
touch app / components / parent .component .ts
touch app / components / parent .component .html

在这一点上,我们可以开始设计每个创建的页面和添加页面逻辑。


定义应用程序的nativescript-angular:


我们还没有添加任何组件,但我们可以设计我们的路线在预期。路由定义添加到项目中的app/ app.routing.ts文件。打开这个文件:


import { NgModule } from "@angular/core" ;
import { NativeScriptRouterModule } from "nativescript-angular/router" ;
import { Routes } from "@angular/router" ;
 
import { ParentComponent } from "./components/parent/parent.component" ;
import { DashboardComponent } from "./components/dashboard/dashboard.component" ;
import { HeroesComponent } from "./components/heroes/heroes.component" ;
import { HeroComponent } from "./components/hero/hero.component" ;
 
const routes: Routes = [
     { path : "" , component : ParentComponent , children : [
         { path : "" , component : DashboardComponent } ,
         { path : "heroes" , component : HeroesComponent } ,
         { path : "hero/:id" , component : HeroComponent } ,
     ] }
] ;
 
@ NgModule ( {
     imports : [ NativeScriptRouterModule . forRoot ( routes ) ] ,
     exports : [ NativeScriptRouterModule ]
} )
export class AppRoutingModule { }


同样,我们还没有创建我们的类,但这是在预期。我们感兴趣的是路线阵列。
请注意,我们有一个顶层路由空路径。带有空路径的任何路由意味着它是默认路由。加载应用程序时,它会默认加载的parentcomponent类。这条路线有三个孩子,其中一个是默认的。子路径中的一个接受一个id参数,我们可以通过。
而不是路由的必然联系的,我们必须对“ngmodule块添加所有组件。打开项目的app/ app.module.ts文件:


import { NgModule , NO_ERRORS _SCHEMA } from "@angular/core" ;
import { NativeScriptModule } from "nativescript-angular/nativescript.module" ;
import { NativeScriptFormsModule } from "nativescript-angular/forms" ;
import { AppRoutingModule } from "./app.routing" ;
import { AppComponent } from "./app.component" ;
 
import { ParentComponent } from "./components/parent/parent.component" ;
import { DashboardComponent } from "./components/dashboard/dashboard.component" ;
import { HeroesComponent } from "./components/heroes/heroes.component" ;
import { HeroComponent } from "./components/hero/hero.component" ;
 
import { DataService } from "./services/data.service" ;
 
@ NgModule ( {
     bootstrap : [
         AppComponent
     ] ,
     imports : [
         NativeScriptModule ,
         NativeScriptFormsModule ,
         AppRoutingModule
     ] ,
     declarations : [
         AppComponent ,
         ParentComponent ,
         DashboardComponent ,
         HeroesComponent ,
         HeroComponent
     ] ,
     providers : [ DataService ] ,
     schemas : [
         NO_ERRORS _SCHEMA
     ]
} )
export class AppModule { }

在上面我们已经进口的每个预期的成分和增加他们的“ngmodule块声明数组。您希望在应用程序中使用的每个组件、指示和管道都必须添加到声明数组中。这些被称为申报类。这将是我们最后一次访问应用程序/ app.module.ts文件。


设计和开发应用页面:


通过定义的路由和组件文件,我们可以开始设计和开发每个应用程序页面。像英雄的演示应用程序的官方行程,在我们的nativescript应用不会太复杂。核心差异将驻留在UI中,因为HTML与xml的差异。
要开始的事情,我们应该创建我们的父导航页,将管理我们的三个子页面的每一页。


为我们子页面创建父页面:


父页面负责显示一个nativescript分段杆和路由每个子页面翻阅它。
打开项目的app/components/parent/ parent.component.ts文件包括以下文件的代码:


import { Component } from "@angular/core" ;
import { SegmentedBarItem } from "ui/segmented-bar" ;
import { Router } from "@angular/router" ;
 
@ Component ( {
     selector : "parent" ,
     templateUrl : "./components/parent/parent.component.html" ,
} )
export class ParentComponent {
 
     public navItems: Array < SegmentedBarItem > ;
 
     public constructor ( private router: Router ) {
         this . navItems = [ ] ;
         this . navItems . push ( this . createSegmentedBarItem ( "Dashboard" ) ) ;
         this . navItems . push ( this . createSegmentedBarItem ( "Heroes" ) ) ;
     }
 
     private createSegmentedBarItem ( title : string ) : SegmentedBarItem {
         let item : SegmentedBarItem = < SegmentedBarItem > new SegmentedBarItem ( ) ;
         item . title = title ;
         return item ;
     }
 
     public navigate ( index : number ) {
         switch ( index ) {
             case 0 :
                 this . router . navigate ( [ "/" ] ) ;
                 break ;
             case 1 :
                 this . router . navigate ( [ "/heroes" ] ) ;
                 break ;
         }
     }
 
}

在上述parentcomponent类中我们定义了我们的分割栏选项卡和当他们点击。对于这个例子,我们只有两个标签,一个用于显示仪表板,另一个是英雄列表。
记得在应用程序/ app.routing.ts文件我们选择的路吗?我们使用时,试图在分段栏项目点击点击。
那么,父组件后面的UI看起来像什么呢?打开项目的app/components/parent/ parent.component.html文件,包括HTML标记:

title = "Tour of Heroes" >
     #sb [items ] = "navItems" selectedIndex = "0" (selectedIndexChange ) = "navigate(sb.selectedIndex)" >
    

顶级HTML将有一个动作条,然后是一个分段的。分段杆将使用#某人的模板变量将允许我们通过指数的变化事件中。每个在打字稿的逻辑创建的项目添加到HTML通过项目属性。
因为这个HTML充当包装器,我们有一个<路由器出口>,每个孩子都可以通过。


创建英雄仪表板:


我们启动应用程序时看到的第一个子组件是仪表板。在我们的例子中,它只显示我们所有的英雄,但它将显示他们不同的替代组件。
从记录的代码,打开项目的app/components/dashboard/ dashboard.component.ts文件包括以下内容:


import { Component } from "@angular/core" ;
import { Router } from "@angular/router" ;
import { DataService } from "../../services/data.service" ;
 
@ Component ( {
     selector : "dashboard" ,
     templateUrl : "./components/dashboard/dashboard.component.html"
} )
export class DashboardComponent {
 
     public heroes: Array < any > ;
 
     public constructor ( private router: Router , private data: DataService ) {
         this . heroes = this . data . getHeroes ( ) ;
     }
 
     public edit ( id : number ) {
         this . router . navigate ( [ "/hero" , id ] ) ;
     }
 
}


我们所做的项目的app/ app.module.ts文件导入数据的类,但它也需要进口的每一个组成部分,我们希望使用它。
在构造函数方法中注入服务后,我们可以得到所有可用的英雄。如果我们想编辑任何英雄,我们可以通过传递ID值导航到细节页面
那么,这背后的样式像HTML吗?打开项目的app/components/dashboard/ dashboard.component.html文件,包括HTML标记:


    
     flexWrap = "wrap" flexDirection = "row" >
        
    


创建我们的响应数据采用flexbox启发flexboxlayout。如果列不适合行,它们将包到下一行。列是由循环通过了样式中发现英雄的生成逻辑。如果我们点击任何列,我们将浏览页面编辑数据。


创建英雄名单:


现在让我们看看我们的其他分割项目屏幕,英雄名单。从理论上说,我们可以完成更多的屏幕上比仪表板。然而,这取决于你和你如何设置它。
打开项目的app/components/heroes/ heroes.component.ts文件包括以下文件的代码:


import { Component } from "@angular/core" ;
import { Router } from "@angular/router" ;
import { DataService } from "../../services/data.service" ;
 
@ Component ( {
     selector : "heroes" ,
     templateUrl : "./components/heroes/heroes.component.html" ,
} )
export class HeroesComponent {
 
     public heroes: Array < any > ;
 
     public constructor ( private router: Router , private data: DataService ) {
         this . heroes = this . data . getHeroes ( ) ;
     }
 
     public add ( value : string ) {
         if ( value != "" ) {
             this . data . add ( value ) ;
         }
     }
 
     public remove ( id : number ) {
         this . data . delete ( id ) ;
     }
 
     public edit ( id : number ) {
         this . router . navigate ( [ "/hero" , id ] ) ;
     }
 
}


从逻辑的角度来看,这heroescomponent对dashboardcomponent非常相似,但有更多的选择。这些选项包括添加和删除英雄。
当我们要添加或删除一个英雄,从数据业务服务相关的方法被称为。
现在让我们看看这个逻辑背后的HTML。打开项目的app/components/heroes/ heroes.component.html文件包括以下内容:


     rows = "auto" columns = "*, auto" margin = "5" marginTop = "15" >
         #heroName hint = "Hero Name" row = "0" col = "0" class = "text-input" >
        
    
    
    
        
             * ngFor = "let hero of heroes" rows = "auto" columns = "30, *, 10" class = "hero-grid-item" >
                
                
                
            
        
    


虽然我们可以用一个flexboxlayout再一次,我决定改变它,并使用一个标准的GridLayout相反。这种布局更像一张桌子。
此页面的上部有一个非常基本的形式与文本输入和按钮。按下按钮时,字段中的文本是通过添加在打字的方法。此页的下部是一堆表格。每个表有三列,其中两列具有单击事件。我们可以很容易地使用一个表多行,而不是多个表。


创建编辑现有英雄的方法:


第三个和最后的子页面给我们一个方法来编辑任何现有的英雄。也可以称为主详细设计场景中的详细页。
从页面逻辑,打开项目的app/components/hero/ hero.component.ts文件包括以下文件的代码:


import { Component , OnInit } from "@angular/core" ;
import { Location } from "@angular/common" ;
import { ActivatedRoute } from "@angular/router" ;
import { DataService } from "../../services/data.service" ;
 
@ Component ( {
     selector : "hero" ,
     templateUrl : "./components/hero/hero.component.html" ,
} )
export class HeroComponent implements OnInit {
 
     public hero: any ;
 
     public constructor ( private location: Location , private route: ActivatedRoute , private data: DataService ) {
         this . hero = { } ;
     }
 
     public ngOnInit ( ) {
         this . route . params . subscribe ( params = > {
             this . hero = this . data . getHero ( params [ "id" ] ) ;
         } ) ;
     }
 
     public cancel ( ) {
         this . location . back ( ) ;
     }
 
     public save ( id : number , name : string ) {
         if ( name != "" ) {
             this . data . edit ( id , name ) ;
             this . location . back ( ) ;
         }
     }
 
}


喜欢与其他两个子页面的herocomponent类设计是非常相似的。而不是与一系列英雄的工作,我们将与一个单一的英雄。
这里的目标是让取消这将带我们到前一页或储存,将调用我们的数据业务服务并返回前一页后。
打开项目的app/component/hero/ hero.component.html文件并添加下面的HTML标记:


    
     rows = "auto, auto" columns = "100, *" margin = "5" >
        
        
        
         #heroName hint = "{{ hero.name }}" row = "1" col = "1" class = "text-input" margin = "0" >
    
     rows = "auto" columns = "*, *" >
        
        
    


喜欢的英雄榜页面,我决定利用我在flexboxlayout布局GridLayout。两者都会,这只是取决于你的设计偏好。
我们本质上只是在屏幕上显示英雄信息,并在点击事件中提交新的英雄名称。


应用程序组件的全局CSS:


就像Web的角度,我们可以选择本地CSS每个组件或全局CSS,可用于所有组件。
为了简单起见,我们在整个指南中使用的类名将存在于全局CSS文件中。打开项目的app/ app.css包括以下:

@import 'nativescript-theme-core/css/core.light.css';
 
.h2 {
     margin-top : 15 ;
     margin-left : 5 ;
}
 
.hero-flexgrid-item {
     width : 32% ;
     margin : 5 ;
     padding : 10 ;
     background-color : #EEEEEE ;
}
 
.hero-grid-item {
     margin : 5 ;
     padding : 10 ;
     background-color : #EEEEEE ;
}
 
.text-input {
     border-color : #CCCCCC ;
     border-width : 1 ;
     padding : 5 ;
     margin-right : 5 ;
}
 
.btn {
     margin : 0 ;
}
 
.btn-danger {
     background-color : red ;
     color : #FFFFFF ;
}


自定义CSS对我们的应用程序的成功并不是真正必要的,但它确实有助于使我们的应用程序看起来更吸引人,类似于web版本。


结论:


您刚刚看到如何使您自己的移动兼容的英雄应用程序与angular。而官方教程是指向Web,我们把它带到Android和iOS作为原生移动应用程序与NativeScript。
如果您想更进一步地使用此应用程序,您可以在正式教程中用HTTP请求替换服务中的模拟数据。然后你就可以建立自己的基于REST的API,它应该工作以来的所有HTTP和RxJS运营商,你会发现在angular在Web中会存在Angular -nativescript。毕竟,他们是一个在同一个。


如果您想了解更多关于嵌套的子组件和它们之间的路由,留言点个赞。  后续会更新。。。



你可能感兴趣的:(NativeScript)