前言
當談到學習例子,肯定是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標記:
<ActionBar
title="Tour
of Heroes"></ActionBar>
<StackLayout>
<SegmentedBar
#sb
[items]="navItems"
selectedIndex="0"
(selectedIndexChange)="navigate(sb.selectedIndex)"></SegmentedBar>
<router-outlet></router-outlet>
</StackLayout>
頂級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標記:
<StackLayout>
<Label
text="Top
Heroes"
class="h2"></Label>
<FlexboxLayout
flexWrap="wrap"
flexDirection="row">
<Label
*ngFor="let
hero of heroes"
text="{{
hero.name }}"
(tap)="edit(hero.id)"
flexGrow="1"
class="hero-flexgrid-item"></Label>
</FlexboxLayout>
</StackLayout>
創建我們的響應數據採用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文件包括以下內容:
<StackLayout>
<GridLayout
rows="auto"
columns="*,
auto"
margin="5"
marginTop="15">
<TextField
#heroName
hint="Hero
Name"
row="0"
col="0"
class="text-input"></TextField>
<Button
text="Add"
(tap)="add(heroName.text);
heroName.text = ''"
class="btn
btn-primary"
row="0"
col="1"></Button>
</GridLayout>
<Label
text="My
Heroes"
class="h2"></Label>
<ScrollView>
<StackLayout>
<GridLayout
*ngFor="let
hero of heroes"
rows="auto"
columns="30,
*, 10"
class="hero-grid-item">
<Label
text="{{
hero.id }}"
row="0"
col="0"></Label>
<Label
text="{{
hero.name }}"
row="0"
col="1"
(tap)="edit(hero.id)"></Label>
<Label
text="x"
row="0"
col="2"
(tap)="remove(hero.id)"></Label>
</GridLayout>
</StackLayout>
</ScrollView>
</StackLayout>
雖然我們可以用一個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標記:
<StackLayout>
<Label
text="{{
hero.name }} Details..."
class="h2"></Label>
<GridLayout
rows="auto,
auto"
columns="100,
*"
margin="5">
<Label
text="ID:"
row="0"
col="0"></Label>
<Label
text="{{
hero.id }}"
row="0"
col="1"></Label>
<Label
text="Name:"
row="1"
col="0"></Label>
<TextView
#heroName
hint="{{
hero.name }}"
row="1"
col="1"
class="text-input"
margin="0"></TextView>
</GridLayout>
<GridLayout
rows="auto"
columns="*,
*">
<Button
text="Cancel"
(tap)="cancel()"
class="btn
btn-danger"
margin="5"
row="0"
col="0"></Button>
<Button
text="Save"
(tap)="save(hero.id,
heroName.text)"
class="btn
btn-primary"
margin="5"
row="0"
col="1"></Button>
</GridLayout>
</StackLayout>
喜歡的英雄榜頁面,我決定利用我在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。畢竟,他們是一個在同一個。
如果您想了解更多關於嵌套的子組件和它們之間的路由,留言點個贊。 後續會更新。。。