Ionic 기본 – 기초 정리

여기서는 Ionic 을 사용하는 데에 있어 가장 기초가 되는 프로세스들을 정리해본다.

Ionic 프로젝트 생성하는 법

과거 Ionic 2버전일 때는 프로젝트를 생성할 때, ionic start project-name –v2 처럼 생성시 버전을 명시해야했지만, Ionic 버전 3로 바뀐 지금은 버전을 명시하지 않아도 된다.

ionic start projectname blank

이렇게 하면 빈 프로젝트가 생성된다. 그런데, blank 조차 빼고 ionic start projectname 만 실행하면, 어떤 형식으로 프로젝트를 생성할 것인지 묻는 질문이 뜬다. 방향키를 이용해 선택하면된다. tabs, blank, sidemenu, super 형식 중에 택일한다.

생성된 프로젝트는 다음과 같다.

hooks 폴더는 말그대로 hooks 를 정의하는 곳이다. 즉, cordova 가 컴파일될 때, 디플로이될때, 실행될 때 등등 각 단계별로 해야할 일을 정의하는 것이다. 사실, 별로 건드릴일이 없다.

node_modules 폴더도 npm install 로 설치된 모듈들이 저장된 곳이고, 자동으로 관리되므로 건드릴 일이 없다.

resources, platforms, plugins, www 등의 폴더도 거의 자동으로 관리되므로 건드릴 일이 없다.  platforms 폴더는 ionic cordova platform add android 같은 명령으로 플랫폼을 추가하면 생성된다. plugins 폴더는 cordova camera plugin 같이 native 기능에 관련된 플러그인을 설치하면 자동 생성된다. www 폴더는 웹앱이 컴파일되어 저장되는 곳이다.

우리에게 중요한 것은 src 폴더이고, 그 안에 manifest.json 은 프로그래시브 앱을 만들때 필요한 것이므로 그런 경우가 아니면 건드릴일이 없다.

ionic serve

를 실행하면 ionic 프로젝트가 컴파일되고 브라우저 윈도우가 열리면서 실행된다.

여기서 오른쪽 상단에 있는 toggle device toolbar 버턴을 누르면 실제 디바이스 형태로 에뮬레이션되어 나타난다. 하지만, 완벽한 에뮬레이션은 아니다. 어디까지나 해당 브라우저에 기초한 에뮬레이션이지 기기에 100% 적용된 에뮬레이션은 아니다. 개발 과정에서 여러가지 디버깅 등이 가능하기 때문에 매우 유용하다. (아래 화면은 크롬 브라우저를 사용한 것이다.)

 

IONIC src 폴더 살펴보기

src 폴더 안에 app.module.ts 파일을 살펴보자.

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}

imports 항목 안에 IonicModule.forRoot(MyApp) 에 주목하자.

  • Angular 에서 폼 처리를 위해 필요했던 FormsModule 을 포함하고 있다.
  • app.component.ts 의 MyApp 을 wrapping 하는 역할을 한다. wrapping을 통해 일반 웹앱이 cordova 앱으로 전환될 수 있는 바탕을 제공하는 것이다.
  • Ionic 에 포함된 모든 Angular 컴포넌트에 접근할 수 있도록 해준다.

entryComponents 는 Angular 웹 앱에서는 보지 못했던 것이다. 이 항목은 Angular 가 일종의 팩토리를 만들도록 강제하는 역할을 한다. 컴포넌트의 인스턴스를 준비하도록 하는 것이다. Angular 에서 사용되던 router 와는 달리 Ionic 에서는 Page 를 사용한다. 즉, Ionic에서는 네비게이션의 방법이 다르기 때문에 entryComponents 가 필요하다.

app.component.ts 파일을 살펴보자.

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';

import { HomePage } from '../pages/home/home';
@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  rootPage:any = HomePage;

  constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) {
    platform.ready().then(() => {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.
      statusBar.styleDefault();
      splashScreen.hide();
    });
  }
}

Angular 와 비교해 특이한 것은 contructor 안에 들어있는 platform.ready()이다. 플랫폼이 준비되면 statusBar 스타일을 정의하고 스플래쉬스크린을 숨긴다는 내용일 뿐 별 건 없다.

rootPage:any = HomePage 라는 부분은 처음 로드될 페이지를 정의한다. rootPage 변수는 app.html 에서 가져다 쓰고 있다. 이 부분은 ionic 의 page 네비게이션과 관련된 부분이다.

app.html

<ion-nav [root]="rootPage"></ion-nav>

일반적인 Angular 웹 앱의 경우에는 selector(ex: <app-root></app-root>) 를 사용해 Component를 불러와 View 에 보여준다. 그런데 ionic에서는 그런 selector가 없다. 대신 Page 를 불러와 보여준다. Page는 여러 컴포넌트를 포함할 수 있다.

Ionic navigation

  • Angular Router 를 사용하지 않는다.
  • 물리적인 종이, 즉 겹겹이 쌓여있는 종이 페이지를 상상해보자. 이것이 Ionic 의 네비게이션 개념이다.
  • 처음 앱이 로딩될 때 첫 페이지를 무엇으로 할 지를 반드시 정해야한다. app.html 의 ion-nav 는 바로 그런 기능을 한다.
  • 그 다음 부터는 필요에 따라  new Page 를 가장 상위에 올려 Push 해서 보여준다.
  • 해당 페이지를 없애는 것을 Pop 이라고 한다. Pop 하면 그 아래에 있는 페이지가 보여진다.

페이지 생성

$ ionic generate page users

이렇게 생성된 페이지는 다음과 같이 pages 폴더 안에 나타난다.

이제 pages/home/home.html 을 열고 users 페이지로 이동하는 버튼을 하나 추가해보자.

<ion-header>
  <ion-navbar>
    <ion-title>
      Ionic Blank
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <button ion-button (click)="onGoToUsers()">Users</button>
</ion-content>

onGoToUsers() 메서드를 home.ts 에서 추가해본다.

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

  constructor(public navCtrl: NavController) {

  }
  onGoToUsers(){
    this.navCtrl.push('UsersPage');
  }
}

ionic serve 명령으로 실행해보면 다음과 같이 버튼이 나타나고 버튼을 클릭하면, UsersPage 가 Push되어 나타난다.

 

Ionic2 와 Inoic3 의 페이지 네비게이션 차이

예전의 ionic 2 버전에서는 페이지를 생성하면 users.html, users.scss, users.ts 세개의 파일만 생성되었는데, 버전3부터는 users.module.ts 파일이 추가로 생성된다.

즉, Angular 의 Lazy Loading 과 같에 Ionic 에서도 Lazy Page Loading 이 사용된다. 그리고 버전3 ionic cli 에서는 페이지 생성시에 Lazy Loading 을 기본으로 하고 있다.

이 후 설명은 ionic generate page users 명령을 이용해 users 페이지를 생성했다는 전제 아래 진행되었다. 자동 생성된 Users  페이지는 Lazy Loading 이 될 수 있도록 만들어져있다. (물론, 생성된 페이지를 일반 로딩되도록 app.module.ts 에 등록해주면 일반 로딩도 될 수 있다. 즉, 두가지 방법 모두 사용이 가능하도록 자동으로 파일이 생성된 것이다.)

일반 페이지 네비게이션

일반 페이지 로딩에서는 페이지 컴포넌트를 app.modules.ts 파일의 declarations, entryComponents 에 추가해주어야 한다. 대신, users.module.ts 파일이 필요하지 않고, @IonicPage() 디렉티브를 사용하지 않는다.

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { UsersPage } from "../pages/users/users";

@NgModule({
  declarations: [
    MyApp,
    HomePage,
    UsersPage
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage,
    UsersPage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}

 

그런 다음 home.ts 파일에서 다음과 같이 설정해주고, 마찬가지로 UsersPage를 import 해주어야했다.

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { UsersPage } from "../users/users";

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

  constructor(public navCtrl: NavController) {

  }
  onGoToUsers(){
    this.navCtrl.push(UsersPage);
  }
}

 

Lazy Loading 을 이용한 페이지 설정

다른 방법을 사용하면 코딩이 훨씬 간단하다.  page module(ex: users.module.ts)을 포함하는 이 방법은 Lazy loading 이라고 불린다. 코딩이 간편할 뿐만아니라, 필요할 때만 해당페이지를 불러와 로딩하므로 최초 로딩시간을 줄일 수 있다고한다.

app.modules.ts 에서 아무 것도 추가해줄 필요가 없다. home.ts 파일에서도 UsersPage를 import 할 필요가 없다. 그냥 users 페이지를 생성한 후, 바로 home.ts에서 다음과 같이 설정해주면 된다.

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

  constructor(public navCtrl: NavController) {

  }
  onGoToUsers(){
    this.navCtrl.push('UsersPage');
  }
}

UsersPage 를 Single quote 로 감싸고 있음을 기억하자. 

일반 페이지와 달리 해당 페이지의 ts파일에서 @IonicPage() 디렉티브를 사용해야한다. ex : pages/users/users.ts

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';

@IonicPage()
@Component({
  selector: 'page-users',
  templateUrl: 'users.html',
})
export class UsersPage {

  constructor(public navCtrl: NavController, public navParams: NavParams) {
  }

  ionViewDidLoad() {
    console.log('ionViewDidLoad UsersPage');
  }

}

다음과 같은 page module을 포함해야한다. ex: pages/users/users.module.ts

import { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { UsersPage } from './users';

@NgModule({
  declarations: [
    UsersPage
  ],
  imports: [
    IonicPageModule.forChild(UsersPage),
  ],
})
export class UsersPageModule {}

 

다른 페이지로 변수값 전달하기

UsersPage 에서 UserPage로 사용자 이름을 전달하려면 어떻게 할까? UsersPage는 이미 있으니, UserPage를 생성하자.

$ ionic generate page user

전달하는 페이지 설정 UsersPage

src/pages/users/users.html

<ion-header>
  <ion-navbar>
    <ion-title>The Users</ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
<button ion-button (click)="onLoadUser('Max')">User Max</button>
<hr>
<button ion-button (click)="onLoadUser('Anna')">User Anna</button>
</ion-content>

<ion-footer>
  <p>The Footer</p>
</ion-footer>

src/pages/users/users.ts

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';

@IonicPage()
@Component({
  selector: 'page-users',
  templateUrl: 'users.html',
})
export class UsersPage {

  constructor(public navCtrl: NavController, public navParams: NavParams) {
  }

  ionViewDidLoad() {
    console.log('ionViewDidLoad UsersPage');
  }

  onLoadUser(name: string) {
    this.navCtrl.push('UserPage',{userName:name})
  }

}

 

전달받는 페이지 UserPage

src/pages/user/user.html

<ion-header>
  <ion-navbar>
    <ion-title>{{name}}</ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <p>Hi, {{name}}</p>
</ion-content>

<ion-footer>
  <p>The Footer</p>
</ion-footer>

src/pages/user/user.ts

import { Component, OnInit } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';

@IonicPage()
@Component({
    selector: 'page-user',
    templateUrl: 'user.html',
})
export class UserPage implements OnInit {
    name: string;

    constructor(public navCtrl: NavController, public navParams: NavParams) {
    }

    ionViewDidLoad() {
        console.log('ionViewDidLoad UsersPage');
    }

    ngOnInit(){
        // this.name = this.navParams.data;
        this.name = this.navParams.get('userName');
    }

}

현재 페이지를 View 에서 제거하기 (Pop)

src/pages/user/user.html

<ion-header>
  <ion-navbar>
    <ion-title>{{name}}</ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <p>Hi, {{name}}</p>
  <button ion-button (click)="goBack()">Confirm</button>
</ion-content>

<ion-footer>
  <p>The Footer</p>
</ion-footer>

src/pages/user/user.ts

import { Component, OnInit } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';

@IonicPage()
@Component({
    selector: 'page-user',
    templateUrl: 'user.html',
})
export class UserPage implements OnInit {
    name: string;

    constructor(public navCtrl: NavController, public navParams: NavParams) {
    }

    ionViewDidLoad() {
        console.log('ionViewDidLoad UsersPage');
    }

    ngOnInit(){
        // this.name = this.navParams.data;
        this.name = this.navParams.get('userName');
    }

    goBack(){
        // this.navCtrl.pop();
        this.navCtrl.popToRoot();
    }

}

 

navPush, navParams, navPop 디렉티브를 이용한 네비게이션

Angular 에서는 routerLink 디렉티브를 이용한 네비게이션이 있다. html 페이지에서 a 태그 라인에 routerLink 디렉티브를 포함시켜 다른페이지로 이동할 수 있다.

Ionic 에서 그런 기능을 하는 것이 navPush 디렉티브다.

<button ion-button [navPush]="usersPage"></button>

이 경우 Parameter 값을 같이 넘겨주고 싶으면 navParams 디렉티브를 사용한다.

<button ion-button [navPush]="usersPage" [navParams]="{name:'Mike'}"></button>

ts 파일에서 usersPage 변수에 UsersPage 를 대입해주면된다.

import { UsersPage } from '../users/users';

export class XXXPage {
....
usersPage = UsersPage
constructor(){}
....
}

Parameter 값을 읽는 방법은 똑같다. NavParams 를 Injection 해서 읽어온다.

import { NavParams } from 'ionic-angular';
import { UsersPage } from '../users/users';

export class XXXPage {
....
usersPage = UsersPage
params:string;
constructor(public navParams:NavParams){
   //this.params = this.navParams.data
   this.params = this.navParams.get('name');
}
....
}

마지막으로 html 파일 안에서 pop 기능을 구현하려면, navPop 디렉티브를 사용한다.

<button ion-button navPop>Go Back</button>

페이지 트랜지션

페이지가 바뀔 때 어떤 형식으로 바뀔 것인지를 설정할 수 있다. 에니메이션, 바뀌는 시간 등의 Navigation Options 을 Push나 Pop의 argument 로 넘겨줄 수 있다.

push() 의 경우 세번째 argument, pop()의 경우 첫번째 argument가 Navigation Options 이다. Navigation Options Object 는 다음 내용을 포함할 수 있다.

  • animate (boolean): Whether or not the transition should animate.
  • animation (string): What kind of animation should be used.
  • direction (string): The conceptual direction the user is navigating. For example, is the user navigating forward, or back?
  • duration (number): The length in milliseconds the animation should take.
  • easing (string): The easing for the animation.

예를 들면 다음과 같이 페이지 트랜지션을 설정하면 된다.

this.navCtrl.push(NewPage, {}, {
    direction: 'back', // default for push is 'forward'
    duration: 2000, // 2 seconds
    easing: 'ease-out'
});

 

페이지 라이프 사이클

Angular 라이프 사이클에 더하여 ionic 에서는 페이지에만 작동되는 라이프 사이클이 추가로 정의되어 있다. ionic 페이지 라이프 사이클은 페이지가 아닌 컴포넌트에서는 작동되지 않는다. 페이지가 아닌 컴포넌트에서는 Angular 라이프 사이클만 작동한다.

ionViewCanEnter : Navigation Guard => 이 페이지가 로드되어야하는가? (result: true or false, Promise resolving true or false)

ionViewDidLoad : 페이지가 로드되었을 때 => 페이지 셋업을 위한 코드가 들어가는 부분(※ 주의: 페이지가 처음 로드되었을 때만 작동한다. 이 후 캐쉬된 페이지를 pop, push 를 통해 가져올 때는 작동하지 않는다.)

ionViewWillEnter : 페이지에 진입하려고 할 때, 페이지가 활성화되려고 할 때 ( 캐쉬로 부터 로드될 때도 작동한다. )

ionViewDidEnter : 페이지에 진입했고 활성화된 후 (캐쉬로부터 로드될 때도 작동)

ionViewCanLeave : Navigation Guard => 페이지를 떠나도 될까? (result: true or false, Promise resolving true or false)

ionViewWillLeave : 페이지를 떠나려고 할때, 페이지가 비활성화 되려고 할때

ionViewDidLeave : 페이지를 떠난 후, 페이지가 비활성화 된 후

ionViewWillUnload : 페이지가 캐쉬에서 지워지려고 할때, 페이지가 파괴(destory) 되려고 할 때

아래와 같이 코드를 users.ts 를 작성하고 테스트해보자.

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';

@IonicPage()
@Component({
  selector: 'page-users',
  templateUrl: 'users.html',
})
export class UsersPage {

  constructor(public navCtrl: NavController, public navParams: NavParams) {
  }

  onLoadUser(name: string) {
    this.navCtrl.push('UserPage',{userName:name})
  }

  ionViewCanEnter(): boolean | Promise<boolean> {
    console.log('ionViewCanEnter');
    const rnd = Math.random();
    return rnd>0.5;
  }

  ionViewDidLoad(){
    console.log('ionViewDidLoad');
  }

  ionViewWillEnter(){
    console.log('ionViewWillEnter')
  }

  ionViewDidEnter(){
    console.log('ionViewDidEnter')
  }

  ionViewCanLeave(): boolean | Promise<boolean> {
    const promise = new Promise((resolve,reject)=>{
      setTimeout(()=>{
        resolve(true);
      },1000);
    })
    return promise;
  }

  ionViewWillLeave(){
    console.log('ionViewWillLeave');
  }

  ionViewDidLeave(){
    console.log('ionViewDidLeave');
  }

  ionViewWillUnload(){
    console.log('ionViewWillUnload');
  }

}

 

Ionic Documentation

링크

Scss 사용하기

Ionic은 scss를 기본으로 사용한다. 물론, 기본 css 코드를 여기에 사용해도 되지만, 각 페이지에 대한 스타일은 항상 페이지 태그 안에서 네스팅해야 문제가 생기지않는다. 전체 애플리케이션에 대한 스타일은 src/app/app.scss 에서 정의하도록한다.

또한, theming utilities 를 잘 숙지해두면 애플리케이션 개발에 유용하다.

 

댓글 남기기