前言
当谈到学习例子,肯定是angular中文社区的英雄教程,发现在官方项目中,这是一个很棒的教程,因为它涵盖了很多主题,但是,它是一个web应用教程。如果我们想从它建立一个移动应用程序,或者更具体地说,一个与Android和iOS支持的原生移动应用程序呢?
我们将看到如何建立一个英雄之旅启发了iOS和Android的移动应用程序的使用angular和nativescript。 让我们弄清楚我们在这里得到什么。重要的是要注意,这是一个英雄指南,因为它不会是完全相同的。我想保留一些原创性。
我们不会做任何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标记:
text
=
"Top Heroes"
class
=
"h2"
>
flexWrap
=
"wrap"
flexDirection
=
"row"
>
*
ngFor
=
"let hero of heroes"
text
=
"{{ hero.name }}"
( tap
)
=
"edit(hero.id)"
flexGrow
=
"1"
class
=
"hero-flexgrid-item"
>
创建我们的响应数据采用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"
>
text
=
"Add"
( tap
)
=
"add(heroName.text); heroName.text = ''"
class
=
"btn btn-primary"
row
=
"0"
col
=
"1"
>
text
=
"My Heroes"
class
=
"h2"
>
*
ngFor
=
"let hero of heroes"
rows
=
"auto"
columns
=
"30, *, 10"
class
=
"hero-grid-item"
>
text
=
"{{ hero.id }}"
row
=
"0"
col
=
"0"
>
text
=
"{{ hero.name }}"
row
=
"0"
col
=
"1"
( tap
)
=
"edit(hero.id)"
>
text
=
"x"
row
=
"0"
col
=
"2"
( tap
)
=
"remove(hero.id)"
>
虽然我们可以用一个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标记:
text
=
"{{ hero.name }} Details..."
class
=
"h2"
>
rows
=
"auto, auto"
columns
=
"100, *"
margin
=
"5"
>
text
=
"ID:"
row
=
"0"
col
=
"0"
>
text
=
"{{ hero.id }}"
row
=
"0"
col
=
"1"
>
text
=
"Name:"
row
=
"1"
col
=
"0"
>
# heroName
hint
=
"{{ hero.name }}"
row
=
"1"
col
=
"1"
class
=
"text-input"
margin
=
"0"
>
rows
=
"auto"
columns
=
"*, *"
>
text
=
"Cancel"
( tap
)
=
"cancel()"
class
=
"btn btn-danger"
margin
=
"5"
row
=
"0"
col
=
"0"
>
text
=
"Save"
( tap
)
=
"save(hero.id, heroName.text)"
class
=
"btn btn-primary"
margin
=
"5"
row
=
"0"
col
=
"1"
>
喜欢的英雄榜页面,我决定利用我在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。毕竟,他们是一个在同一个。
如果您想了解更多关于嵌套的子组件和它们之间的路由,留言点个赞。 后续会更新。。。