構建NativeScript跨平臺項目

前言


當談到學習例子,肯定是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。畢竟,他們是一個在同一個。


如果您想了解更多關於嵌套的子組件和它們之間的路由,留言點個贊。  後續會更新。。。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章