APP
趕在了春節以前上線了,因此此次咱們分享一下使用Ionic3 + Angular5
構建一個Hybird App
過程當中的經驗。什麼是Hybird App
以及一些技術的選型這裏就不討論了。我每次完成一個部分就寫一部分,因此有文章有點長。若是有錯誤的地方感謝你們指正~css
Ionic
?有些朋友說Angular/Ionic
不大行,可是我覺的技術沒有好壞之分,只有適合不適合。首先在我看來Ionic
已經在Hybird App
開發領域立足多年,已經至關的成熟了,我覺的比大部分的解決方案都要好。其次由於咱們的App是一個弱交互多展現類型的,Ionic
知足咱們的需求。最後是由於若是你想在沒有Android
團隊和IOS
團隊支持的狀況下獨立完成一款APP,那麼Ionic
我覺的是不二之選。由於Ionic4
還在beta
版本,而且是公司項目因此依然選用了穩定的3.X
版本。html
注意:非基礎入門教程,因此在讀這篇文章以前建議你最好先了解[Angular](https://www.angular.cn/guide/quickstart), [TS](https://www.tslang.cn/docs/home.html), [Ionic](https://ionicframework.com/docs/)
的基礎知識,這裏主要是但願你們在使用Ionic
的時候能少走一些彎路。前端
因爲我本身用的不是很熟練Rxjs
這一塊就沒有寫,等之後對Rxjs
的理解更加深入了再加上vue
既然是基於Angular
那咱們首先來了解一下Angular
,這個地方積累的是Angular
中零散的部分。若是內容多的話後期會拆分爲單獨的部分java
Angular
的生命週期android
Hooks
官方介紹webpack
constructor()
: 在任何其它生命週期鉤子以前調用。能夠用它來注入依賴項,但不要在這裏作正事。ngOnChanges(changes: SimpleChanges) => void
: 當被綁定的輸入屬性的值發生變化時調用,首次調用必定會發生在 ngOnInit()
以前 ngOnInit() => void
: 在第一輪 ngOnChanges()
完成以後調用。只調用一次 ngDoCheck() => void
: 在每一個變動檢測週期中調用,ngOnChanges()
和 ngOnInit()
以後ngAfterContentInit() => void
: Angular
把外部內容投影進組件/指令的視圖以後調用。能夠認爲是外部內容初始化ngAfterContentChecked() => void
: Angular
完成被投影組件內容的變動檢測以後調用。能夠認爲是外部內容更新ngAfterViewInit() => void
: 每當 Angular
初始化完組件視圖及其子視圖以後調用。只調用一次。 ngAfterViewChecked() => void
:每當 Angular
作完組件視圖和子視圖的變動檢測以後調用, ngAfterViewInit()
和每次 ngAfterContentChecked()
以後都會調用。ngOnDestroy() => void
:在 Angular 銷燬指令/組件以前調用。<ng-content></ng-content>
默認映射
這個內容映射方向是由父組件映射到子組件中
這個就至關於vue
中的slot,用法也都是同樣的:git
<!-- 父組件 --> <child-component> 我是父組件中的內容默認映射過來的 </child-component> <!-- 子組件 --> <!-- 插槽 --> <ng-content> </ng-content>
上面是最簡單的默認映射使用方式github
針對性映射(具名插槽)
咱們也能夠經過<ng-content>的select
屬性實現咱們的具名插槽。這個是能夠根據條件進行填充。select
屬性支持根據CSS
選擇器(ELement, Class, [attribute]...)來匹配你的元素,若是不設置就所有接受,就像下面這樣:web
<!-- 父組件 --> <child-component> 我是父組件中的內容默認映射過來的 <header> 我是根據header來映射的 </header> <div class="class"> 我是根據class來映射的 </div> <div name="attr"> 我是根據attr來映射的 </div> </child-component> <!-- 子組件 --> <!-- 具名插槽 --> <ng-content select="header"></ng-content> <ng-content select=".class"></ng-content> <ng-content select="[name=attr]"></ng-content>
ngProjectAs
上面那些都是映射都是做爲直接子元素進行的映射,那要不是呢? 我想在外面再套一層呢?
<!-- 父組件 --> <child-component> <!-- 這個時不是直接子節點了 這確定是不行的 那咱們就用到ngProjectAs了--> <div> <header> 我是根據header來映射的 </header> </div> </child-component>
使用ngProjectAs
,它能夠做用於任何元素上。
<!-- 父組件 --> <child-component> <div ngProjectAs="header"> <header> 我是根據ngProjectAs header來映射的 </header> </div> </child-component>
ng-content
有一個@ContentChild
裝飾器,能夠用來調用和投影內容。可是要注意:只有在ngAfterContentInit
聲明週期中才能成功獲取到經過ContentChild
查詢的元素。既然提到了ng-content
那咱們就來聊一聊ng-template
和ng-container
ng-template
<ng-template> 元素是動態加載組件的最佳選擇,由於它不會渲染任何額外的輸出
<div class="ad-banner-example"> <h3>Advertisements</h3> <ng-template ad-host></ng-template> </div>
ng-container
<ng-container> 是一個由 Angular
解析器負責識別處理的語法元素。 它不是一個指令、組件、類或接口,更像是 JavaScript
中 if
塊中的花括號。通常用來把一些兄弟元素歸爲一組,它不會污染樣式或元素佈局,由於 Angular 壓根不會把它放進 DOM 中。
<p> I turned the corner <ng-container *ngIf="hero"><!-- ng-container不會被渲染 --> and saw {{hero.name}}. I waved </ng-container> and continued on my way. </p>
Angular
中的指令分爲組件
,屬性指令
和結構形指令
。屬性型指令
用於改變一個 DOM
元素的外觀或行爲,例如NgStyle
。結構型指令
的職責是 HTML
佈局。 它們塑造或重塑 DOM
的結構,好比添加、移除或維護這些元素,例如NgFor
和NgIf
。
屬性型指令
Directive
裝飾符把一個類標記爲 Angular
指令, 該選項提供配置元數據,用於決定該指令在運行期間要如何處理、實例化和使用。@Directive ElementRef
獲取綁定元素的DOM對象,ElementRef。HostListener
響應用戶引起的事件,把一個事件綁定到一個宿主監聽器,並提供配置元數據。 當宿主元素髮出特定的事件時,Angular 就會執行所提供的處理器方法,並使用其結果更新所綁定到的元素。 若是該事件處理器返回 false
,則在所綁定的元素上執行 preventDefault
。HostListener Input
裝飾符把某個類字段標記爲輸入屬性,而且提供配置元數據。 聲明一個可供數據綁定的輸入屬性,在變動檢測期間,Angular
會自動更新它,@Input。@Input('appHighlight') highlightColor: string;
下面是一個完整的屬性形指令的例子
import {Directive, ElementRef, HostListener, Input} from '@angular/core'; @Directive({ selector: '[sxylight]' }) export class SxylightDirective { constructor(private el: ElementRef) { el.nativeElement.style.backgroundColor = 'yellow'; } // 指令綁定的值 @Input('sxylight') highlightColor: string; // 在指令內部,該屬性叫 highlightColor,在外部,你綁定到它地方,它叫 sxylight 這個是綁定的別名 // 指令宿主綁定的值 @Input() defaultColor: string; // 監聽宿主事件 @HostListener('mouseenter') onMouseEnter() { this.highlight(this.highlightColor || this.defaultColor || 'red'); } @HostListener('mouseleave') onMouseLeave() { this.highlight(null); } private highlight(color: string) { this.el.nativeElement.style.backgroundColor = color; } }
結構型指令
Angular
把 *ngIf
屬性 翻譯成一個 <ng-template>
元素 並用它來包裹宿主元素。<ng-template>
: 它是一個 Angular 元素,用來渲染 HTML。 它永遠不會直接顯示出來。 事實上,在渲染視圖以前,Angular 會把 <ng-template> 及其內容替換爲一個註釋。<ng-container>
: 它是一個分組元素,但它不會污染樣式或元素佈局,由於 Angular
壓根不會把它放進 DOM
中。TemplateRef
: 可使用TemplateRef
取得 <ng-template>
的內容,TemplateRef<any> ViewContainerRef
: 能夠經過ViewContainerRef
來訪問這個視圖容器,ViewContainerRef。完整示例
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'; /** * Input, TemplateRef, ViewContainerRef 這三個模塊是構建一個結構型指令必須的模塊 * Input: 傳值 * TemplateRef: 表示一個內嵌模板,它可用於實例化內嵌的視圖。 要想根據模板實例化內嵌的視圖,請使用 ViewContainerRef 的 createEmbeddedView() 方法。 * ViewContainerRef: 表示能夠將一個或多個視圖附着到組件中的容器。 */ @Directive({ selector: '[structure]' // Attribute selector }) export class StructureDirective { private hasView = false @Input() set structure(contion: boolean) { console.log(contion) if (!contion && !this.hasView) { this.viewCon.createEmbeddedView(this.template) // 實例化內嵌視圖並插入到容器中 this.hasView = true } else if (contion && this.hasView) { this.viewCon.clear() // 銷燬容器中的全部試圖 this.hasView = false } } constructor( private template: TemplateRef<any>, private viewCon: ViewContainerRef ) { console.log('Hello StructureDirective Directive'); } }
首先咱們來看看NgModule
interface NgModule { // providers: 這個選項是一個數組,須要咱們列出咱們這個模塊的一些須要共用的服務 // 而後咱們就能夠在這個模塊的各個組件中經過依賴注入使用了. providers : Provider[] // declarations: 數組類型的選項, 用來聲明屬於這個模塊的指令,管道等等. // 而後咱們就能夠在這個模塊中使用它們了. declarations : Array<Type<any>|any[]> // imports: 數組類型的選項,咱們的模塊須要依賴的一些其餘的模塊,這樣作的目的使咱們這個模塊 // 能夠直接使用別的模塊提供的一些指令,組件等等. imports : Array<Type<any>|ModuleWithProviders|any[]> // exports: 數組類型的選項,咱們這個模塊須要導出的一些組件,指令,模塊等; // 若是別的模塊導入了咱們這個模塊, // 那麼別的模塊就能夠直接使用咱們在這裏導出的組件,指令模塊等. exports : Array<Type<any>|any[]> // entryComponents: 數組類型的選項,指定一系列的組件,這些組件將會在這個模塊定義的時候進行編譯 // Angular會爲每個組件建立一個ComponentFactory而後把它存儲在ComponentFactoryResolver entryComponents : Array<Type<any>|any[]> // bootstrap: 數組類型選項, 指定了這個模塊啓動的時候應該啓動的組件.固然這些組件會被自動的加入到entryComponents中去 bootstrap : Array<Type<any>|any[]> // schemas: 不屬於Angular的組件或者指令的元素或者屬性都須要在這裏進行聲明. schemas : Array<SchemaMetadata|any[]> // id: 字符串類型的選項,模塊的隱藏ID,它能夠是一個名字或者一個路徑;用來在getModuleFactory區別模塊,若是這個屬性是undefined // 那麼這個模塊將不會被註冊. id : string }
app.module.ts
app.module.ts └───@NgModule └───declarations // 告訴Angular哪些模塊屬於NgModule │───imports // 導入須要使用的模塊 │───bootstrap // 啓動模塊 │───entryComponents // 定義組建時應該被編譯的組件 └───providers // 服務配置
entryComponents
:Angular
使用entryComponents
來啓用tree-shaking
,即只編譯項目中實際使用的組件,而不是編譯全部在ngModule
中聲明但從未使用的組件。離線模板編譯器(OTC)
只生成實際使用的組件。若是組件不直接用於模板,OTC
不知道是否須要編譯。有了entryComponents
,你能夠告訴OTC
也編譯這些組件,以便在運行時可用。
首先來看項目目錄
Ionic-frame │ build // 打包擴展 │ platforms // Android/IOS 平臺代碼 │ plugins // cordova插件 │ resources └───src // 業務邏輯代碼 │ │ app // 啓動組件 │ │ assets // 資源 │ │ components // 公共組件 │ │ config // 配置文件 │ │ directive // 公共指令 │ │ interface // interface配置中心 │ │ pages // 頁面 │ │ providers // 公共service │ │ service // 業務邏輯service │ │ shared // 共享模塊 │ │ theme // 樣式模塊 │ │ index.d.ts // 聲明文件 └───www // 打包後靜態資源
生命週期的重要性不用多說,這是Ionic
官網的介紹
constrctor => void
: 構造函數啓動,構造函數在ionViewDidLoad以前被觸發ionViewDidLoad => void
: 資源加載完畢時觸發。ionViewDidLoad只在第一次進入頁面時觸發只觸發一次 ionViewWillEnter => void
: 頁面即將給進入時觸發每次都會觸發 ionViewDidEnter => void
: 進入視圖以後出發每次都會觸發 ionViewWillLeave => void
: 即將離開(僅僅是觸發要離開的動做)時觸發每次都會觸發 ionViewDidLeave => void
: 已經離開頁面時觸發每次都會觸發 ionViewWillUnload => void
: 在頁面即將被銷燬並刪除其元素時觸發ionViewCanEnter => boolean
:在視圖能夠進入以前運行。 這能夠在通過身份驗證的視圖中用做一種「保護」,您須要在視圖能夠進入以前檢查權限ionViewCanLeave => boolean
:在視圖能夠離開以前運行。 這能夠在通過身份驗證的視圖中用做一種「防禦」,您須要在視圖離開以前檢查權限注意: 當你想使用ionViewCanEnter/ionViewCanLeave
進行對路由的攔截時,你須要返回一個Boolen
。返回true
進入下一個視圖,返回fasle
留在當前視圖。
能夠按照下面的代碼感覺一下生命週期的順序
constructor(public navCtrl: NavController) { console.log('觸發構造函數') } /** * 頁面加載完成觸發,這裏的「加載完成」指的是頁面所需的資源已經加載完成,但還沒進入這個頁面的狀態(用戶看到的仍是上一個頁面)。全程只會調用一次 */ ionViewDidLoad () { console.log(`Ionic觸發ionViewDidLoad`); // Step 1: 建立 Chart 對象 const chart = new F2.Chart({ id: 'myChart', pixelRatio: window.devicePixelRatio // 指定分辨率 }) // Step 2: 載入數據源 chart.source(data) chart.interval().position('genre*sold').color('genre') chart.render() } /** * 即將進入Ionic視圖 這時對頁面的數據進行預處理 每次都會觸發 */ ionViewWillEnter(){ console.log(`Ionic觸發ionViewWillEnter`) } /** * 已經進入Ionic視圖 每次都會觸發 */ ionViewDidEnter(){ console.log(`Ionic觸發ionViewDidEnter`) } /** * 頁面即將 (has finished) 離開時觸發 每次都會觸發 */ ionViewWillLeave(){ console.log(`Ionic觸發ionViewWillLeave`) } /** * 頁面已經 (has finished) 離開時觸發,頁面處於非激活狀態了。 每次都會觸發 */ ionViewDidLeave(){ console.log(`Ionic觸發ionViewDidLeave`) } /** * 頁面中的資源即將被銷燬 通常用處不大 */ ionViewWillUnload(){ console.log(`Ionic觸發ionViewWillUnload`) } //守衛導航鉤子: 返回true或者false /** * 在視圖能夠進入以前運行。 這能夠在通過身份驗證的視圖中用做一種「保護」,您須要在視圖能夠進入以前檢查權限 */ ionViewCanEnter(){ console.log(`Ionic觸發ionViewCanEnter`) const date = new Date().getHours() console.log(date) if (date > 22) { return false } return true } /** * 在視圖能夠離開以前運行。 這能夠在通過身份驗證的視圖中用做一種「防禦」,您須要在視圖離開以前檢查權限 */ ionViewCanLeave(){ console.log(`Ionic觸發ionViewCanLeave`) const date = new Date().getHours() console.log(date) if (date > 10) { return false } return true }
Ionic3.X
中並無提供相應的的配置文件,因此咱們須要本身按照下面步驟手動去添加配置文件來對項目進行配置。
config
目錄src |__config |__config.dev.ts |__config.prod.ts
config.dev.ts
/ config.prod.ts
export const CONFIG = { BASE_URL : 'http://XXXXX/api', // API地址 VERSION : '1.0.0' }
build
文件夾,在文件夾中新增webpack.config.js
config文件const fs = require('fs') const chalk =require('chalk') const webpack = require('webpack') const path = require('path') const defaultConfig = require('@ionic/app-scripts/config/webpack.config.js') const env = process.env.IONIC_ENV /** * 獲取配置文件 * @param {*} env */ function configPath(env) { const filePath = `./src/config/config.${env}.ts` if (!fs.existsSync(filePath)) { console.log(chalk.red('\n' + filePath + ' does not exist!')); } else { return filePath; } } // 定位當前文件 const resolveDir = filename => path.join(__dirname, '..', filename) // 其餘文件夾別名 let alias ={ "@": resolveDir('src'), "@components": resolveDir('src/components'), "@directives": resolveDir('src/directives'), "@interface": resolveDir('src/interface'), "@pages": resolveDir('src/pages'), "@service": resolveDir('src/service'), "@providers": resolveDir('src/providers'), "@theme": resolveDir('src/theme') } console.log("當前APP環境爲:"+process.env.APP_ENV) let definePlugin = new webpack.DefinePlugin({ 'process.env': { APP_ENV: '"'+process.env.APP_ENV+'"' } }) // 設置別名 defaultConfig.prod.resolve.alias = { "@config": path.resolve(configPath('prod')), // 配置文件 ...alias } defaultConfig.dev.resolve.alias = { "@config": path.resolve(configPath('dev')), ...alias } // 其餘環境 if (env !== 'prod' && env !== 'dev') { defaultConfig[env] = defaultConfig.dev defaultConfig[env].resolve.alias = { "@config": path.resolve(configPath(env)) } } // 刪除sourceMaps module.exports = function () { return defaultConfig }
tsconfig.json
配合,配置中新增以下內容 這個地方很扯 這個path
相關的須要放在tsconfig.json
的最上面 "baseUrl": "./src", "paths": { "@app/env": [ "environments/environment" ] }
package.json
。配置末尾新增以下內容"config": { "ionic_webpack": "./config/webpack.config.js" }
import {CONFIG} from "@app/env"
若是過咱們想修改Ionic
中其餘的webpack
配置, 那麼能夠像上面那種形式來進行修改。
// 拿到webpack 的默認配置 剩下的還不是隨心所欲 const defaultConfig = require('@ionic/app-scripts/config/webpack.config.js'); // 像這樣去修改配置 defaultConfig.prod.resolve.alias = { "@config": path.resolve(configPath('prod')) } defaultConfig.dev.resolve.alias = { "@config": path.resolve(configPath('dev')) }
首頁設置
有時候咱們須要設置咱們第一次顯示得頁面。那這樣咱們就須要使用NavController
來設置
// app.component.ts public rootPage: any = StartPage; //
路由跳轉
href方式跳轉
:直接在dom中指定要跳轉的頁面,以tabs
中的代碼爲例<!-- 單個跳轉按鈕 [root]="HomeRoot" 是最重要的 --> <ion-tab [root]="HomeRoot" tabTitle="Home" tabIcon="home"></ion-tab>
import { HomePage } from '../home/home' export class TabsPage { // 聲明變量地址 HomeRoot = HomePage constructor() { } }
編程式導航是由NavController
控制
NavController是Nav和Tab等導航控制器組件的基類。 您可使用導航控制器導航到應用中的頁面。 在基本級別,導航控制器是表示特定歷史(例如Tab)的頁面數組。 經過推送和彈出頁面或在歷史記錄中的任意位置插入和刪除它們,能夠操縱此數組以在整個應用程序中導航。當前頁面是數組中的最後一頁,若是咱們這樣想的話,它是堆棧的頂部。 將新頁面推送到導航堆棧的頂部會致使新頁面被動畫化,而彈出當前頁面將導航到堆棧中的上一頁面。
除非您使用NavPush
之類的指令,或者須要特定的NavController
,不然大多數時候您將注入並使用對最近的NavController
的引用來操縱導航堆棧。
// 引入NavController import { NavController } from 'ionic-angular'; import { NewsPage } from '../news/news' export class HomePage { // 注入NavController constructor(public navCtrl: NavController) { // this.navCtrl.push(LoginPage) } goNews () { this.navCtrl.push(NewsPage, { title : '測試傳參' }) } }
相關經常使用API
navCtrl.push(OtherPage, param)
: 跳轉頁面navCtrl.pop()
: Removing a view
移除當前View,至關於返回上一個頁面路由中參參數相關
push(Page, param)
傳參: 這個很簡單也很明白this.navCtrl.push(NewsPage, { title : '測試傳參' })
[navParams]
屬性:和HTML
配合進行傳參import {LoginPage } from'./login'; @Component() class MyPage { params; pushPage: any; constructor(){ this.pushPage= LoginPage; this.params ={ id:123, name: "Carl" } } }
<button ion-button [navPush]="pushPage" [navParams]="params"> Go </button> <!-- 同理在root page上傳遞參數就是下面這種方式 --> <ion-tab [root]="tab1Root" tabTitle="home" tabIcon="home" [rootParams]="userInfo"> </ion-tab
//NavController就是用來管理和導航頁面的一個controller constructor(public navCtrl: NavController, public navParams: NavParams) { //1: 經過NavParams get方法獲取到單個對象 this.titleName = navParams.get('name') //2: 直接獲取全部的參數 this.para = navParams.data }
當重複的須要一個類中的方法時,可封裝它爲服務類,以便重複使用,如http。
provider
,也叫service
。前者是ionic
的叫法,後者是ng
的叫法。建議仔細得學一下Angular
Provider
Ionic
提供了建立指令
ionic g provider http
自動建立的Provider
會自主動在app.module中導入
注意這個須要在app.module中注入
首先導入裝飾器
,再用裝飾器裝飾,這樣,該類就能夠做爲提供者注入到其餘類中以使用:
import { Injectable } from '@angular/core'; @Injectable() export class StorageService { constructor() { console.log('Hello StorageService'); } myAlert(){ alert("服務類的方法") } }
provider
若是是頂級的服務(全局通用服務),須要在app.module.ts
的providers
中註冊後而後使用
import { StorageService } from './../../service/storage.service'; export class LoginPage { userName: string = 'demo' password: string = '123456' constructor( public storageService: StorageService ) { } doLogin () { const para = { userName: this.userName, password: this.password } console.log(para) if (para.userName === 'demo' && para.password === '123456') { this.storageService.setStorage('user', para) } setTimeout(() => { console.log(this.storageService.getStorage('user')) }, 3000) } }
Events是一個
發佈-訂閱
樣式事件系統,用於在您的應用程序中發送和響應應用程序級事件。
這個是不一樣頁面之間交流的核心。主要用於組件的通訊。你也能夠用events
傳遞數據到任何一個頁面。
Events
實例方法
publish(topic, eventData)
: 發佈一個event
subscribe(topic, handler)
: 訂閱一個event
unsubscribe(topic, handler)
取消訂閱一個event
// 發佈event login.ts // 發佈event事件 submitEvent (data) { console.log(1) this.event.publish('user:login', data) } // 訂閱頁面 message.ts constructor(public event: Events ) { // 訂閱event事件 event.subscribe('user:login', (data) => { console.log(data) let obj = { url: 'assets/imgs/logo.png', name: data.username } this.messages.push(obj) }) }
注意點: <font color="red">1: 訂閱必須再發布以前,否則接收不到。打個比喻:好比微信公衆號,你要先關注才能接收到它的推文,否則它再怎麼發推文,你也收不到。2: subscribe
中得this
指向是有點問題的,這裏須要注意一下。</font>
Basic gestures can be accessed from HTML by binding totap
,press
,pan
,swipe
,rotate
, andpinch
events.
Ionic對手勢事件的解釋基本是一筆帶過。
組件之間的通訊:要把一個組件化的框架給玩6了。組件以前的通訊搞明白了是個前提。在Ionic
中,咱們使用Angular
中的方式來實現。
父 => 子
: @Input()
輸入型綁定
把數據從父組件傳到子組件:這個用途最普遍和常見,和recat
中的props很是類似// 父組件定義值(用來傳遞) export class NewsPage { father: number = 1 // 父組件數據 /** * Ionic生命週期函數 */ ionViewDidLoad() { // 父組件數據更改 setTimeout(() => { this.father ++ }, 2000) } } // 子組件定義屬性(用來接收) @Input() child: number // @Input裝飾器標識child是一個輸入性屬性
<!-- 父組件使用 --> <backtop [child]="father"></backtop> <!-- 子組件定義 --> <div class="backtop"> <p (click)="click()">back</p> father數據: {{child}} </div>
get, set
在子組件中對父組件得數據進行攔截來達到咱們想要得結果// 攔截父組件得值 private _showContent: string @Input() // set value set showContent(name: string) { if (name !== '4') { this._showContent = 'no' } else { this._showContent = name } } // get value get showContent () :string { return this._showContent }
ngOnChanges
監聽值得變化// 監聽全部屬性值得變化 ngOnChanges(changes: SimpleChange): void { /** * 從舊值到新值得一次變動 * class SimpleChange { constructor(previousValue: any, currentValue: any, firstChange: boolean) previousValue: any // 變化前得值 currentValue: any // 當前值 firstChange: boolean isFirstChange(): boolean // 檢查該新值是否從首次賦值得來的。 } */ // changes props集合對象 console.log(changes['child'].currentValue) // }
本地變量
互動
父組件不能使用數據綁定來讀取子組件的屬性或調用子組件的方法。但能夠在父組件模板裏,
新建一個本地變量來表明子組件
,而後利用這個變量來讀取子組件的屬性和調用子組件的方法.
經過#childComponent
定義這個組件。而後直接使用childComponent.XXX
去調用。這個的話就有點強大了,可是這個交流時頁面級別的。僅限於在html
定義本地變量而後在html
中進行操做和通訊。也就是父組件-子組件的鏈接必須所有在父組件的模板中進行。父組件自己的代碼對子組件沒有訪問權。
<!-- 父組件 --> <button ion-button color="secondary" full (click)="childComponent.fromFather()">測試本地變量</button> <backtop #childComponent [child]="father" [showContent] = "father" (changeChild)="childCome($event)"></backtop>
// 子組件 // 父子組件經過本地變量交互 fromFather () { console.log(`I am from father`) this.show = !this.show }
父組件調用@ViewChild()
互動
若是父組件的類須要讀取子組件的屬性值或調用子組件的方法,能夠把子組件做爲 ViewChild,注入到父組件裏面。
也就是說@ViewChild()
是爲了解決上面的短板而出現的。
// 父組件 import { Component, ViewChild } from '@angular/core'; export class NewsPage { //定義子組件數據 @ViewChild(BacktopComponent) private childComponent: BacktopComponent ionViewDidLoad() { setTimeout(() => { // 經過child調用子組件方法 this.childComponent.formChildView() }, 2000) } }
子 => 父
: @Output()
: 最經常使用的方法子組件暴露一個EventEmitter
屬性,當事件發生時,子組件利用該屬性emits
(向上彈射)事件。父組件綁定到這個事件屬性,並在事件發生時做出迴應。
// 父組件 // 接收兒子組件得來得值 並把兒子得值賦給父親 childCome (data: number) { this.father = data } // 字組件 // 子向父傳遞得事件對象 @Output() changeChild: EventEmitter<number> = new EventEmitter() // 定義事件傳播器對象 // 執行子組件向父組件通訊 click () { this.changeChild.emit(666) }
<!-- 父組件 --> <backtop [child]="father" [showContent] = "father" (changeChild)="childCome($event)"></backtop>
獲取父組件實例
有的時候咱們也能夠暴力一點獲取父組件的實例去使用它(未驗證)。
constructor( // 註冊父組件 @Host() @Inject(forwardRef(() => NewsPage)) father: NewsPage ) { this.text = 'Hello World'; setTimeout(() => { // 直接經過對象來修改父組件 father.father++ }, 3000) }
父 <=> 子
:父子組件經過服務來通訊
若是咱們把一個服務實例的做用域被限制在父組件和其子組件內,這個組件子樹以外的組件將沒法訪問該服務或者與它們通信
。父子共享一個服務,那麼咱們能夠利用該服務在家庭內部實現雙向通信。
// service import { Injectable } from '@angular/core'; // 標記元數據 // 使用service進行父子組件的雙向交流 @Injectable() export class MissionService { familyData: string = 'I am family data' }
// father component import { MissionService } from './../../service/mission.service'; export class NewsPage { constructor( public missionService: MissionService) { } ionViewDidLoad() { // 父組件數據更改 setTimeout(() => { // 調用修改service中的數據 這個時候父子組件中的service都會改變 this.missionService.familyData = 'change familyData' }, 2000) } } // child component import { Component} from '@angular/core'; import { MissionService } from './../../service/mission.service'; @Component({ selector: 'backtop', templateUrl: 'backtop.html' }) export class BacktopComponent { constructor( public missionService:MissionService ) { console.log(missionService) this.text = 'Hello World'; } // 執行子組件向父組件通訊 click () { // 修改共享信息 this.missionService.familyData = 'change data by child' } }
<!-- 父組件直接使用 --> {{missionService.familyData}} <!-- 子組件 --> <div> servicedata: {{missionService.familyData}} </div>
在service
中使用訂閱也能夠一樣的實現數據的通訊
// mission.service.ts import { Subject } from 'rxjs/Subject'; import { Injectable } from '@angular/core'; // 標記元數據 // 使用service進行父子組件的雙向交流 @Injectable() export class MissionService { familyData: string = 'I am family data' // 訂閱式的共享數據 private Source = new Subject() Status$=this.Source.asObservable() statusMission (msg: string) { this.Source.next(msg) } } // 父組件 // 經過service的訂閱提交信息 emitByService () { this.missionService.statusMission('emitByService') } // 子組件 // 返回一個訂閱器 this.subscription = missionService.Status$.subscribe((msg:string) => { this.text = msg }) ionViewWillLeave(){ // 取消訂閱 this.subscription.unsubscribe() }
高級通訊
ionic-angular中的Events
模塊來進行 父 <=> 子 , 兄 <=> 弟
的高級通訊。Events
模塊在通訊方面具備得天獨厚的優點。具體能夠看上面的示例EventEmitter
模塊// service import { EventEmitter } from '@angular/core'; // 標記元數據 // 使用service進行父子組件的雙向交流 @Injectable() export class MissionService { // Event通訊 來自angular serviceEvent = new EventEmitter() } // 父組件 // 經過Events 模塊高級通訊 接收信息 this.missionService.serviceEvent.subscribe((msg: string) => { this.messgeByEvent = msg }) // 子組件 // 經過emit 進行高級通訊 發送新 emitByEvent () { this.missionService.serviceEvent.emit('emit by event') }
公共組件設置,Angular
倡導的是模塊化開發,因此公共組件的註冊可能稍有不一樣。
在這裏咱們根據Angular
提供的CommonModule
共享模塊,咱們要知道他幹了什麼事兒:
CommonModule
,由於該模塊須要一些經常使用指令。CommonModule
和 FormsModule
CommonModule
和 FormsModule
能夠代替BrowserModule
去使用在shared
文件夾下新建shared.module.ts
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; // 經過從新導出 CommonModule 和 FormsModule,任何導入了這個 SharedModule 的其它模塊,就均可以訪問來自 CommonModule 的 NgIf 和 NgFor 等指令了,也能夠綁定到來自 FormsModule 中的 [(ngModel)] 的屬性了。 // 自定義的模塊和指令 import { ComponentsModule } from './../components/components.module'; import { DirectivesModule } from './../directives/directives.module'; @NgModule({ declarations: [], imports: [ CommonModule, FormsModule ], exports:[ // 導出模塊 CommonModule, FormsModule, ComponentsModule, DirectivesModule ], entryComponents: [ ] }) export class SharedModule {}
注意: 服務要經過單獨的依賴注入系統進行處理,而不是模塊系統
使用了shared
模塊僅僅須要在xxx.module.ts
中引用便可,而後又就可使用shared
中全部引入的公共模塊。
import { NgModule } from '@angular/core'; import { IonicPageModule } from 'ionic-angular'; import { XXXPage } from './findings'; import { SharedModule } from '@shared/shared.module'; @NgModule({ declarations: [ XXXPage, ], imports: [ SharedModule, IonicPageModule.forChild(FindingsPage), ] }) export class XXXPageModule {}
Ionic
中的http模塊是直接採用的HttpClient
這個模塊。這個沒什麼可說的,咱們只須要根據咱們的需求對service
進行修改便可,例如能夠把http
改爲了更加靈活的Promise模式
。你也能夠用Rxjs
的模式來實現。下面這個是個簡單版本的實現:
import { TokenServie } from './token.service'; import { StorageService } from './storage.service'; import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' import { Injectable, Inject } from '@angular/core' import {ReturnObject, Config} from '../interface/index' // 返回數據類型和配置文件 /* Generated class for the HttpServiceProvider provider. */ @Injectable() export class HttpService{ /** * @param CONFIG * @param http * @param navCtrl */ constructor( @Inject("CONFIG") public CONFIG:Config, public storage: StorageService, public tokenService: TokenServie, public http: HttpClient ) { console.log(this.CONFIG) } /** * key to 'name='qweq'' * @param key * @param value */ private toPairString (key, value): string { if (typeof value === 'undefined') { return key } return `${key}=${encodeURIComponent(value === null ? '' : value.toString())}` } /** * objetc to url params * @param param */ private toQueryString (param, type: string = 'get') { let temp = [] for (const key in param) { if (param.hasOwnProperty(key)) { let encodeKey = encodeURIComponent(key) temp.push(this.toPairString(encodeKey, param[key])) } } return `${type === 'get' ? '?' : ''}${temp.join('&')}` } /** * set http header */ private getHeaders () { let token = this.tokenService.getToken() return new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded', 'tokenheader': token ? token : '' }) } /** * http post請求 for promise * @param url * @param body */ public post (url: string, body ? : any): Promise<ReturnObject> { const fullUrl = this.CONFIG.BASE_URL + url console.log(this.toQueryString(body, 'post')) return new Promise<ReturnObject>((reslove, reject) =>{ this.http.post(fullUrl, body, { // params, headers: this.getHeaders() }).subscribe((res: any) => { reslove(res) }, err => { // this.handleError(err) reject(err) }) }) } /** * get 請求 return promise * @param url * @param param */ public get(url: string, params: any = null): Promise<ReturnObject> { const fullUrl = this.CONFIG.BASE_URL + url let realParams = new HttpParams() for (const key in params) { if (params.hasOwnProperty(key)) { realParams.set(`${key}`, params[key]) } } // add time map realParams.set( 'timestamp', (new Date().getTime()).toString() ) return new Promise<ReturnObject>((reslove, reject) =>{ this.http.get(fullUrl, { params, headers: this.getHeaders() }).subscribe((res: any) => { console.log(res) reslove(res) }, err => { // this.handleError(err) reject(err) }) }) } }
Cordova插件使用
Ionic
提供了豐富的基於cordova
的插件,官網介紹,使用起來也很簡單。
下載Cordova
插件
cordova add plugin plugin-name -D npm install @ionic-native/plugin-name
使用插件(從@ionic-native/plugin-name
中導入)
import { StatusBar } from '@ionic-native/status-bar'; constructor(private statusBar: StatusBar) { //沉浸式而且懸浮透明 statusBar.overlaysWebView(true); // 設置狀態欄顏色爲默認得黑色 適合淺色背景 statusBar.styleDefault() // 淺色狀態欄 適合深色背景 // statusBar.styleLightContent() }
項目寫完了,不優化一下 內心怪難受的。
App
啓動頁體驗優化Ionic App
畢竟是個混合App,畢竟尚未達到秒開級別。因此這個時候咱們須要啓動頁來幫助咱們提高用戶體驗,首先在config.xml
種配子咱們的啓動頁相關配置
<preference name="ShowSplashScreenSpinner" value="false" /> <!-- 隱藏加載時的loader --> <preference name="ScrollEnabled" value="false" /> <!-- 禁用啓動屏滾動 --> <preference name="SplashMaintainAspectRatio" value="true" /> <!-- 若是值設置爲 true,則圖像將不會伸展到適合屏幕。若是設置爲 false ,它將被拉伸 --> <preference name="FadeSplashScreenDuration" value="1000" /><!-- fade持續時長 --> <preference name="FadeSplashScreen" value="true" /><!-- fade動畫 --> <preference name="SplashShowOnlyFirstTime" value="false" /><!-- 是否只第一次顯示 --> <preference name="AutoHideSplashScreen" value="false" /><!-- 自動隱藏SplashScreen --> <preference name="SplashScreen" value="screen" /> <platform name="android"> <allow-intent href="market:*" /> <icon src="resources/android/icon/icon.png" /> <splash src="resources/android/splash/screen.png" /><!-- 啓動頁路徑 --> <!-- 下面是各個分辨率的兼容 --> <splash height="800" src="resources/android/splash/screenh.png" width="480" /> <splash height="1280" src="resources/android/splash/screenm.png" width="720" /> <splash height="1600" src="resources/android/splash/screenxh.png" width="960" /> <splash height="1920" src="resources/android/splash/screenxxh.png" width="1280" /> <splash height="2048" src="resources/android/splash/screenxxxh.png" width="1536" /> </platform>
我在這裏關閉了自動隱藏SplashScreen
,由於她的斷定條件是一旦App
出事還完畢就隱藏,這顯然不符合咱們的要求。咱們須要的是咱們的Ionic WebView
程序啓動以後再隱藏。因此咱們在app.component.ts
中藉助@ionic-native/splash-screen
來進行這個操做.
platform.ready().then(() => { // 延遲1s隱藏啓動屏幕 setTimeout(() => { splashScreen.hide() }, 1000) })
這樣一來咱們就能夠完美的欺騙用戶,體驗能好點。
新增--prod參數
"build:android": "ionic cordova build android --prod --release",
Bundle
):把這些模塊串接成一個單獨的捆文件(bundle
)。Token
)。我認爲打包APK
對於一些不瞭解服務端和Android
的前端工程師來講仍是比較費勁的。下面咱們來仔細的說一說這個部分。
第一步進行各個環境的配置
Node
安裝/配置環境變量(我相信這個你已經弄完了)jdk
安裝 (無需配置環境變量)jdk是java
的開發環境支持,你能夠在這裏下載, 提取碼:9p74
。
下載完成後,解壓,直接按照提示安裝,全局點肯定,不出意外,最後的安裝路徑爲:C:\Program Files\Java
jdk安裝完成,在cmd中,輸入java -version
驗證是否安裝成功。我這邊是修改了安裝路徑,若是你不熟悉的話仍是不要修改安裝路徑。出現了下面的log
表示安裝成功
SDK
安裝/配置環境變量:這一部分是重點,稍微麻煩一些。
先下載。
解壓後將重命名的文件夾,跟jdk
放在一個父目錄,便於查找:C:\Program Files\SDK
接着配置環境變量,個人電腦——右鍵屬性——-高級系統設置——-環境變量。
在下面的系統變量(s)中,新建,鍵值對以下:
name: ANDROID_HOME key: C:\Program Files\SDK
新建完系統變量以後在path
中加入全局變量。
在控制檯中輸入android -h
,出現下面的日誌,表示sdk
安裝成功
接下來咱們使用Android Studio進行SDK下載
,Adnroid Studio
下載地址,studio
安裝完以後就要安裝Android SDK Tools,Android SDK platform-tools,Android SDK Build-tools
這些工具包和SDK platform
gradle
安裝/配置環境變量
在SDK
都安裝完了以後咱們再進行gradle
的安裝和配置。
而後一樣安裝在JDK,SDK
的目錄下,便於查找。
和SDK
一樣的配置環境變量:
GRADLE_HOME=C:\Program Files\SDK\gradle-4.1 ;%GRADLE_HOME%\bin
測試命令(查看版本):gradle -v
出現下面的日誌,表示安裝成功
打包以前的環境準備工做都已經作完了,接下來咱們進行打包`apk。
cordova
npm i cordova -g
Android
工程,在Ionic
項目中執行下面命令ionic cordova platform add android
這多是一個很漫長的過程,你要耐心等待,畢竟曙光就在眼前了。
Android
項目以後項目的platform
文件夾下會多出來一個android
文件夾。這下接着執行打包命令。ionic cordova build android
而後你會看到控制檯瘋狂輸出,最後出現下圖代表你已經打包出來一個未簽名的安裝包
APK
簽名APK
不簽名是無法發佈的。這個有兩種方法
jdk
簽名,這裏很少說,想了解的能夠看這篇文章 Android Studio
打簽名包。在AS
上方工具欄build
中選取Generate Signed APK
首先建立一個簽名文件
生成完以後能夠直接用AS
打簽名包
點擊locate
就能看到咱們的apk
包了~ 至此咱們的Android
就ok了,IOS的以後再補上。
因爲Android
的要求不如蘋果那麼嚴,咱們也能夠經過本身的服務器進行程序的更新。下面就是實現一個比較簡單的更新Service
更新咱們主要是使用到下面幾個Cordova
插件
cordova-plugin-file-transfer / @ionic-native/file-transfer
: 線上文件的下載和存儲(官方推薦使用XHR2
,有興趣的能夠看一看)cordova-plugin-file-opener2 / @ionic-native/file-opener
: 用於打開APK文件cordova-plugin-app-version / @ionic-native/app-version
: 用於獲取app的版本號cordova-plugin-file / @ionic-native/file
:操做app上的文件系統cordova-plugin-device / @ionic-native/device
:獲取當前設備信息,主要用於平臺的區分在下載完插件以後咱們來實現一個比較簡陋的版本更新service
,具體解釋我會寫在代碼註釋中,主要分紅兩部分,一部分是具體的更新操做update.service.ts
, 另外一部分是用於存放數據的data.service.ts
data.service.ts
/* * @Author: etongfu * @Description: 設備信息 * @youWant: add you want info here */ import { Injectable } from '@angular/core'; import { Device } from '@ionic-native/device'; import { File } from '@ionic-native/file'; import { TokenServie } from './token.service'; import { AppVersion } from '@ionic-native/app-version'; @Injectable() export class DataService { /******************************APP數據模塊******************************/ // app 包名 private packageName: string = '' // app 版本號 private appCurrentVersion: string = '---' // app 版本code private appCurrentVersionCode:number = 0 // 當前程序運行平臺 private currentSystem: string // 當前userId // app 下載資源存儲路徑 private savePath: string // 當前app uuid private uuid: string /******************************通用數據模塊******************************/ constructor ( public device: Device, public file: File, public app: AppVersion, public token: TokenServie, public http: HttpService ) { // 必須在設備準備完以後才能進行獲取 document.addEventListener("deviceready", () => { // 當前運行平臺 this.currentSystem = this.device.platform // console.log(this.device.platform) // app版本相關信息 this.app.getVersionNumber().then(data => { //當前app版本號 data,存儲該版本號 if (this.currentSystem) { // console.log(data) this.appCurrentVersion = data } }, error => console.error(error)) this.app.getVersionCode().then((data) => { //當前app版本號數字代碼 if (this.currentSystem) { this.appCurrentVersionCode = Number(data) } }, error => console.error(error)) // app 包名 this.app.getPackageName().then(data => { //當前應用的packageName:data,存儲該包名 if (this.currentSystem) { this.packageName = data; } }, error => console.error(error)) // console.log(this.currentSystem) // file中的save path 根據平臺進行修改地址 this.savePath = this.currentSystem === 'iOS' ? this.file.documentsDirectory : this.file.externalDataDirectory; }, false); } /** * 獲取app 包名 */ public getPackageName () { return this.packageName } /** * 獲取當前app版本號 * @param hasV 是否加上V標識 */ public getAppVersion (hasV: boolean = true): string { return hasV ? `V${this.appCurrentVersion}` : this.appCurrentVersion } /** * 獲取version 對應的nuamber 1.0.0 => 100 */ public getVersionNumber ():number { const temp = this.appCurrentVersion.split('.').join('') return Number(temp) } /** * 獲取app version code 用於比較更新使用 */ public getAppCurrentVersionCode (): number{ return this.appCurrentVersionCode } /** * 獲取當前運行平臺 */ public getCurrentSystem (): string { return this.currentSystem } /** * 獲取uuid */ public getUuid ():string { return this.uuid } /** * 獲取存儲地址 */ public getSavePath ():string { return this.savePath } }
update.service.ts
/* * @Author: etongfu * @Email: 13583254085@163.com * @Description: APP簡單更新服務 * @youWant: add you want info here */ import { HttpService } from './../providers/http.service'; import { Injectable, Inject } from '@angular/core' import { AppVersion } from '@ionic-native/app-version'; import { PopSerProvider } from './pop.service'; import { DataService } from './data.service'; import {Config} from '@interface/index' import { FileTransfer, FileTransferObject } from '@ionic-native/file-transfer'; import { FileOpener } from '@ionic-native/file-opener'; import { LoadingController } from 'ionic-angular'; @Injectable() export class AppUpdateService { constructor ( @Inject("CONFIG") public CONFIG:Config, public httpService: HttpService, public appVersion: AppVersion, private fileOpener: FileOpener, private transfer: FileTransfer, private popService: PopSerProvider, // 這就是個彈窗的service private dataService: DataService, private loading:LoadingController ) { } /** * 經過當前的字符串code去進行判斷是否有更新 * @param currentVersion 當前app version * @param serverVersion 服務器上版本 */ private hasUpdateByCode (currentVersion: number, serverVersion:number):Boolean { return serverVersion > currentVersion } /** * 查詢是否有可更新程序 * @param noUpdateShow 沒有更新時顯示提醒 */ public checkForUpdate (noUpdateShow: boolean = true) { // 攔截平臺 return new Promise((reslove, reject) => { // http://appupdate.ymhy.net.cn/appupdate/app/findAppInfo?appName=xcz®ionCode=370000 // 查詢app更新 this.httpService.get(this.CONFIG.CHECK_URL, {}, true).then((result: any) => { reslove(result) if (result.succeed) { const data = result.appUpload const popObj = { title: '版本更新', content: `` } console.log(`當前APP版本:${this.dataService.getVersionNumber()}`) // 存在更新的狀況下 if (this.hasUpdateByCode(this.dataService.getVersionNumber(), data.versionCode)) { // if (this.hasUpdateByCode(101, data.versionCode)) { let title = `新版本<b>V${data.appVersion}</b>可用,是否當即下載?<h5 class="text-left">更新日誌</h5>` // 更新日誌部分 let content = data.releaseNotes popObj.content = title + content // 生成彈窗 this.popService.confirmDIY(popObj, data.isMust === '1' ? true: false, ()=> { this.downLoadAppPackage(data.downloadPath) }, ()=> { console.log('取消'); }) } else { popObj.content = '已經是最新版本!' if(!noUpdateShow) { this.popService.confirmDIY(popObj, data.isMust === '1' ? true: false) } } } else { // 接口響應出現問題 直接提醒默認最新版本 if(!noUpdateShow) { this.popService.alert('版本更新', '已經是最新版本!') } } }).catch((err) => { console.error(err) reject(err) }) }) } /** * 下載新版本App * @param url: string 下載地址 */ public downloadAndInstall (url: string) { let loading = this.loading.create({ spinner: 'crescent', content: '下載中' }) loading.present() try { if (this.dataService.getCurrentSystem() === 'iOS') { // IOS跳轉相應的下載頁面 // window.location.href = 'itms-services://?action=download-manifest&url=' + url; } else { const fileTransfer: FileTransferObject = this.transfer.create(); fileTransfer.onProgress(progress =>{ // 展現下載進度 const present = new Number((progress.loaded / progress.total) * 100); const presentInt = present.toFixed(0); if (present.toFixed(0) === '100') { loading.dismiss() } else { loading.data.content = `已下載 ${presentInt}%` } }) const savePath = this.dataService.getSavePath() + 'xcz.apk'; // console.log(savePath) // 下載而且保存 fileTransfer.download(url,savePath).then((entry) => { // this.fileOpener.open(entry.toURL(), "application/vnd.android.package-archive") .then(() => console.log('打開apk包成功!')) .catch(e => console.log('打開apk包失敗!', e)) }).catch((err) => { console.error(err) console.log("下載失敗"); loading.dismiss() this.popService.alert('下載失敗', '下載異常') }) } } catch (error) { this.popService.alert('下載失敗', '下載異常') // 有異常直接取消dismiss loading.dismiss() } } }
以上咱們就能夠根據直接調用service
去進行更新app.component.ts
// 調用更新 this.appUpdate.checkForUpdate()
說實在的,Hybird
真機調試是真的痛苦。目前比較流行的方式是如下兩種調試方式
Chrome Inspect
調試依靠chrome
的強大能力,咱們能夠把App
中的WebView
中的內容徹底的顯示在chrome端。能夠在web端控制咱們的app中的網頁,仍是先當的炫酷的。如下是操做步驟
chrome://inspect/#devices
404
等等的問題
Chrome
會自動找到須要調試的WebView
使用VConsole
進行調試
這個就更簡單了,直接npm install vconsole
這個庫, 而後在app.component.ts
進行引用
import VConsole from 'vconsole' export class MyApp { constructor() { platform.ready().then(() => { console.log(APP_ENV) // 調試程序 APP_ENV === 'debug' && new VConsole() }) } }
效果以下
若是在打完包以後靜態路徑出來問題,沒有加載出來的話要注意如下狀況
<!-- html中的img標籤直接引用圖片處理 --> <img src="./assets/xxx.jpg"/> <!-- 或者這樣 --> <img src="assets/imgs/timeicon.png" style="width: 1rem;">
/*scss文件中要使用絕對路徑*/ .bg{ background-image: url("../assets/xxx.jpg") }
Android API版本修改
Ionic中如今默認的SDK版本過高了,有些低版本的機器沒發安裝須要修改的有如下這麼幾個部分
<!-- platforms/android/project.properties --> target=android-26 <!-- 和platforms/android/CordovaLib/project.properties --> target=android-26
SDK
和cordova
插件中的坑(暫時不寫)這個東西真的是坑的一塌糊塗,以cordova-plugin-file-opener2
爲例
AS3.0
打包以後Android7.0
如下的手機沒法安裝這個不能算是Ionic
的坑,要算也得是Android Studio3.0
的坑,以前由於不瞭解在打包的時候下面的選項並無勾選上
不加上的時候一直在Android7.0
如下都無法安裝,一直覺得是項目代碼的問題,沒想到是設置的問題,加上了V1
選項以後打也就能夠了,查了一下緣由以下。
上圖中提供的選項實際上是簽名版本選擇
,在AS3.0
的時候新增的選項。
Android 7.0
中引入了APK Signature Scheme v2
,v1
呢是jar Signature
來自JDK
V1:應該是經過ZIP條目進行驗證,這樣APK 簽署後可進行許多修改 - 能夠移動甚至從新壓縮文件。
V2:驗證壓縮文件的全部字節,而不是單個 ZIP 條目,所以,在簽名後沒法再更改(包括 zipalign
)。正因如此,如今在編譯過程當中,咱們將壓縮、調整和簽署合併成一步完成。好處顯而易見,更安全並且新的簽名可縮短在設備上進行驗證的時間(不須要費時地解壓縮而後驗證),從而加快應用安裝速度。
若是不勾選V1,那麼在7.0如下會直接安裝完顯示未安裝,7.0以上則使用了V2的方式驗證。若是勾選了V1,那麼7.0以上就不會使用更加安全的快速的驗證方式。
也能夠在app目錄下的build.gradle
中進行配置
signingConfigs { debug { v1SigningEnabled true v2SigningEnabled true } release { v1SigningEnabled true v2SigningEnabled true } }
這麼一番折騰下來,越到了很多坑。可是也都一一解決了。使用Ionic
最大的感觸就是TS
+Angular
的模塊化開發模式很舒服。並且開發速度上也不至於太慢,對Angular
感興趣的朋友我認爲仍是能夠一試的。
示例代碼請稍後
春節立刻到了,祝各位開發者春節快樂遠離BUG~😁😁😁
原文地址 若是以爲有用得話給個⭐吧