리오집사의 기억저장소

*이 글은 https://javebratt.com/firebase-objects-ionic-2-app/ 의 내용을 번역한거다.

오랜만에 ionic으로 하이브리드 앱을 개발하려고 보니 너무 많은 것들이 바뀌어 있었다.

ionic3로 무작정 시작했는데, angular4와 typescript라는 생소한 언어는 무작정 도전할 수 있는 게 아니었다.

거기다 typescript도 생소한데 여기에 firebase까지 사용하려니 난감했다.

그러다 발견한 이  자료! 


너무 큰 도움이 되었다. 다른 초급자 분들께도 추천해주고 싶어서 막가파 번역을 진행했다.

이 글에 있는 것은 My Event Manager만 포함된 내용으로, 모든 내용이 포함된 PDF를 보려면 돈을 지불해야 한다.

Chapter2를 읽어본 나로써는 별로 아깝지 않을 것 같은 금액이다. 



Chapter 2. 첫번째 파이어베이스 기반 ionic 앱 개발하기

1. Ionic & Firebase: 설치 과정

구현할 내용 :

이 챕터에서, 이벤트들을 처리하는 앱을 만들 것이다.

이 앱의 목표는 다음과 같다.

  • 사용자 인증

  • CRUD(데이터 작성, 읽기, 업데이트 및 삭제)

  • Firebase .transaction() 사용

  • ionic-native를 사용해 사진 찍기

  • Firebase 스토리지에 파일 업로드하기

  • 보안 규칙의 이해

이 앱 전체 소스 코드는 Github repository 에 있다.

앱 프로젝트 생성

$ cd Development
$ ionic start MyEventManager blank
$ cd MyEventManager

프로젝트와 함께 제공되는 npm 패키지들

Ionic CLI 를 이용하여 새로운 프로젝트를 생성하면, 많은 일들을 처리해야 한다. 그 중 하나는 프로젝트에 필요한 npm 패키지들이 있는지 확인하는 것이다. 즉, start command는 모든 필요 사항을 설치하는 것이며, 그것은 아래와 같은 package.json 파일에서 확인할 수 있다.


{
"name": "MyEventManager",
"author": "JAVEBRATT",
"homepage": "https://javebratt.com/",
"private": true,
"scripts": {
"clean": "ionic-app-scripts clean",
"build": "ionic-app-scripts build",
"ionic:build": "ionic-app-scripts build",
"ionic:serve": "ionic-app-scripts serve"
},
"dependencies": {
"@angular/common": "4.1.3",
"@angular/compiler": "4.1.3",
"@angular/compiler-cli": "4.1.3",
"@angular/core": "4.1.3",
"@angular/forms": "4.1.3",
"@angular/http": "4.1.3",
"@angular/platform-browser": "4.1.3",
"@angular/platform-browser-dynamic": "4.1.3",
"@ionic-native/core": "3.12.1",
"@ionic-native/splash-screen": "3.12.1",
"@ionic-native/status-bar": "3.12.1",
"@ionic/storage": "2.0.1",
"ionic-angular": "^3.6.0",
"ionicons": "3.0.0",
"rxjs": "5.4.0",
"sw-toolbox": "3.6.0",
"zone.js": "0.8.12"
},
"devDependencies": {
"@ionic/app-scripts": "^2.1.3",
"@ionic/cli-plugin-cordova": "1.5.0",
"@ionic/cli-plugin-ionic-angular": "1.4.0",
"ionic": "3.6.0",
"typescript": "2.3.4"
},
"version": "0.0.1",
"description": "An Ionic project"
}

이 글을 언제 읽었는지에 따라, 이 패키지들은 변했을 수도 있다.

Firebase 설치

새로운 패키지 설치는 간단하다. 새로운 터미널을 열고, 아래의 명령어를 실행하면 된다.


$ npm install firebase --save

최신 버전의 Firebase JavaScript SDK가 설치된다.

Pages와 Providers 생성 및 가져오기

우선 잠시 시간을 내어 우리가 사용할 모든 page와 provider를 만들것이다. 터미널을 열고, 우선 아래와 같이 프로젝트 내에서 페이지를 생성한다.


$ ionic generate page EventDetail
$ ionic generate page EventCreate
$ ionic generate page EventList
$ ionic generate page Login
$ ionic generate page Profile
$ ionic generate page ResetPassword
$ ionic generate page Signup

generate page 명령은 방금 생성한 클래스의 이름을 딴 폴더를 생성한다. EventDetail을 통해 자세히 알아보자.

ionic generate page EventDetailevent-detail 폴더를 생성하고, 이 폴더 안에 네 개의 파일을 생성한다.

event-detail.html은 사용자가 보여지게 되는 화면인 HTML 코드를 작성하는 뷰 파일이다.

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

<ion-content padding>
</ion-content>

event-detail.module.ts 는 과거 app.module.ts 에서 페이지를 선언하고 초기화하는데 사용했던 모듈 파일을 ionic 팀에서 코드 분할 및 지연 로드를 도입해 만들어졌다. 즉, 각 페이지는 자신의 모듈 선언 파일을 가져온다. 이 방법으로, 사용자가 앱을 실행할 때마다 앱의 모든 페이지를 로드하는 대신, homepage만 로드한 다음 필요한 페이지를 로드할 수 있다.


import { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { EventDetailPage } from './event-detail';

@NgModule({
 declarations: [
   EventDetailPage,
],
 imports: [
   IonicPageModule.forChild(EventDetailPage),
],
 exports: [
   EventDetailPage
]
})
export class EventDetailPageModule {}

event-detail.scss 파일은 styles 파일이다.


page-event-detail {}

마지막으로, event-detail.ts 파일은 EventDetail 클래스에 있는 모든 기능을 선언할 우리의 class이다.


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

@IonicPage()
@Component({
 selector: 'page-event-detail',
 templateUrl: 'event-detail.html',
})
export class EventDetailPage {
 constructor(public navCtrl: NavController, public navParams: NavParams) {}
}

이제 모든 페이지를 만들었으므로, provider를 만들 것이다. provider를 가장 쉽게 설명하면, dependency를 만드는 조리법이다. 이러한 방법은, 다양한 코드 aspect에 대해 서로 다른 provider를 가질 수 있게 한다.

예를 들어, 우리는 인증과 관련된 모든 것을 처리하는 인증 공급자를 만들 수 있다. 이 방법은 페이지에서 인증 provider의 login() 함수를 호출할 때, 페이지는 공급자가 어떻게 작동하고 처리하는지 신경쓰지 않는다.

이 애플리케이션에서는, 세 개의 provider가 필요하다. 인증, 모든 이벤트 관리를 처리, 사용자 프로파일을 처리하는 provider가 그것이다.


$ ionic generate provider Auth
$ ionic generate provider Event
$ ionic generate provider Profile

위와 같이 provider 생성 명령을 사용할 때마다, Ionic CLI가 몇 가지 작업을 수행한다. 생성된 내용이 무엇인지 확인하기 위해 provider 중 하나를 살펴보자.

ionic generate provider Auth를 실행하면, CLI는 providers 폴더 내에 auth라는 폴더를 만들고, 이 폴더는 auth.ts 파일을 갖게 된다. 아래는 src/providers/auth/auth.ts에 작성된 내용이다.


import { Injectable } from '@angular/core';

@Injectable()
export class AuthProvider {
 constructor() {}
}

몇 가지 다른 것들을 가지고 있지만, 우리가 신경써야할 것은 클래스에 삽입할 수 있는 실제 제공자/모듈을 만드는 @Injectable() 데코레이터이다.

지금 가서 app.module.ts를 확인해보면, CLI가 새로운 Provider에 대한 import를 작성해두고 @NgModule 내부의 providers array에 추가했음을 확인할 수 있다.

이 단계에서 $ ionic serve 를 실행했을 때, 브라우저에서 오류없이 실행되어야 정상이다.

사용해야할 cordova plugin 설치하기

플러그인은, 이 앱에서 Camera를 사용하기 위한 ionic-native만을 사용할 것이다.

터미널을 열고 다음을 타이핑한다.


$ ionic cordova plugin add cordova-plugin-camera --save
$ npm install --save @ionic-native/camera

우리는 두가지를 설치했다. native 기능 사용을 위한 코르도바 플러그인, 또 하나는 angular 친화적인 코드를 작성할 수 있도록 도와주는 ionic-native 패키지이다. 즉, 플러그인 코드를 ionic의 모든 코드와 유사하게 작성할 수 있게 해준다.

설치한 플러그인을 동작시키기 위해서는, 두 가지 해야할 일이 있다. 그 중 첫번째는 app.module.ts 내의 provider's array 배열에 삽입하는 것이다. 이를 위해 app.module.ts 파일을 열고, 플러그인을 import 한 다음 삽입한다.


import { Camera } from '@ionic-native/camera';
@NgModule({
 declarations: [...],
 imports: [...],
 bootstrap: [IonicApp],
 entryComponents: [...],
 providers: [
        ...,
    Camera,
    ...
]
})
export class AppModule {}

이제 카메라 플러그인을 앱 내의 어디에서나 사용할 수 있게 되었지만, 한걸음 더 나아가 볼 것이다.

이미 알고 있겠지만, 플러그인은 브라우저에서 동작하지 않는다. ionic serve 명령을 이용하면, 실제 휴대폰이나 에뮬레이터에서만 작동한다.

왜 그렇게 동작하는지 이해하지만, 브라우저의 자동 리로드를 이용하면 작업이 더 빨라진다. 운 좋게도, 새로운 버전의 ionic native는 우리에게 mock provider를 제공한다. 우리는 개발하는 동안 "Mock" 클래스를 만들 수 있으며, 플러그인을 호출할 때마다 mock 클래스를 사용할 것이다. 예를 들면 다음과 같다:

  • 사진을 찍으면, 카메라는 Firebase Storage에 업로드하고 사진을 표시하는데 사용할 base64 문자열을 반환한다.

우리는 우리가 선택한 base64 문자열을 반환하는 CameraMock class를 만들것이다. 이 방법은 모바일 디바이스에서 테스트하는 것이 아닌 브라우저에서 테스트가 가능하도록 하여, 앱 개발할 때의 속도를 줄여 줄 것이다.

앱의 기능을 구축하고 나면, 다양한 기기에서 플러그인을 테스트하고 적절한 Camera class를 사용하여 배포할 수 있다.

이를 염두에 두고, mock 클래스를 만든 다음, 우리의 providers array에 해당 클래스(app.module.ts 파일 안의 모든 클래스)를 사용하도록 타이핑한다.


import { Camera } from '@ionic-native/camera';
class CameraMock extends Camera {
 getPicture(options){
   return new Promise( (resolve, reject) => {
     resolve(`TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIG
J1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3a
GljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ug
b2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmV
yYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2
YgYW55IGNhcm5hbCBwbGVhc3VyZS4=`);
  });
}
}
@NgModule({
 declarations: [...],
                imports: [...],
 bootstrap: [IonicApp],
 entryComponents: [...],
                   providers: [
                   ...,
                  {provide: Camera, useClass: CameraMock},
                   ...
                  ]
})
export class AppModule {}

앱을 배포하기 전에, 이 코드를 삭제하는 것을 잊지 말길 :P

다음 단계로

이제 준비가 되었다면, 이 챕터의 다음 파트로 넘어가서 앱을 초기화하고 auth 모듈을 만들어보자.

2. User Authentication

이 모듈에서는 전자 메일 및 암호 인증 시스템을 만드는 방법을 배우게 되며, 이를 통해 사용자는 모든 앱에서 대부분 수행하게 되는 세 가지 기본적인 작업을 수행할 수 있게 된다.

  • 새 계정 생성

  • 기존 계정에 로그인

  • 비밀번호 재설정 이메일

Firebase 앱 초기화

파이어베이스를 사용하기 위해, 첫번째로 해야 하는 일은 Firebase 데이터베이스에 연결하는 방법을 Ionic에 알려주는 것이다.

이를 위해서는 src/app/app.component.ts 파일에서 작업을 해야 한다.

이 파일에서 제일 먼저 해야 할 일은 파이어베이스를 import 하는 것이다. 그러면 해당 파일은 이것이 필요하다는 것을 인지하고 이것에 필요한 methods에 액세스할 수 있게 된다.

파일 상단의 import 라인에 다음을 추가한다.


import firebase from 'firebase';

그 다음, constructor로 이동하여 아래의 코드 단편을 추가한다:


firebase.initializeApp({
 apiKey: "",
 authDomain: "",
 databaseURL: "",
 storageBucket: "",
 messagingSenderId: ""
});

키의 값들은 Firebase 콘솔에서 찾을 수 있다 :


이후 Firebase 콘솔에서 추가적으로, 앱의 인증 방법을 활성화해야 한다.

Authentication>Sing-In Method> Email and Password > Enable


Listening to the authentication state

이제 Ionic은 Firebase 애플리케이션과 대화하는 방법을 알고 있으므로, 인증 리스너를 만들 것이다.

이것은 Firebase Authentication에 연결하고 사용자 상태의 변경 사항을 수신 대기한다.

예를 들어서, 계정을 만들고 잠시 동안 앱을 사용하다가 닫은 다음 다시 사용하면, 앱이 이미 사용자임을 이해하고 다시 로그인하는 것 대신 홈페이지로 이동한다.

먼저 해야할 일은 app.component.ts 내부의 rootPage에서 assignment를 제거하는 것이다. (이유는 잠시 후 설명한다)


rootPage: any;

이후, Firebase의 onAuthStateChanged()를 사용하기 위한 인증 리스너를 만들 것이다.(Firebase 공식 문서)

아래 코드는 인증 변경이 발생할 경우 observer가 트리거되고 내부 기능이 다시 실행되는 코드이다.


const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
 if (!user) {
   this.rootPage = 'login';
   unsubscribe();
} else {
   this.rootPage = HomePage;
   unsubscribe();
}
});

자, onAuthStateChanged() 함수가 어떻게 동작하는지 알아보자:

이것은 어떻게 인증 상태를 listening하는 걸까? 파이어베이스를 사용하여 로그인하거나 회원가입을 할 때, 그것은 auth 객체를 로컬 저장소에 저장된다.

이 객체는 사용자 이메일, 이름, ID와 같은 인증에 이용되는 사용자 프로필에 대한 정보를 갖고 있다.

그래서 onAuthStateChanged() 함수는 해당 객체를 찾아 사용자가 이미 있는지 여부를 확인한다.

만약 사용자가 존재하지 않으면, 사용자 변수는 null이 되고, if문의 상태는 트리거되어 rootPage='login' 만든다.

반대로 사용자가 존재하면, 이것은 사용자의 정보를 return하며, 이러한 경우 사용자가 다시 앱에 로그인 할 필요가 없기 때문에, listener는 사용자를 HomePage로 보낸다.

The unsubscribe(); is because we are telling the function to call itself once it redirects the user, this is because the onAuthStateChanged() returns the unsubscribe function for the observer.

이 말의 의미는, 다시 실행하지 않는 한 인증 상태를 listening하는 것을 멈춘다는 것이다. (이것은 누군가 앱을 열 때마다 매번 실행 됨)

기술적으로 unsubscribe()를 제거하고, 함수가 listening하도록 할 수 있지만, 대부분의 경우 구독 취소를 하는 것이 좋다. 왜냐하면 함수가 트리거하는 인증 상태가 갑자기 변경될 수 있기 때문이다.

인증 Provider 만들기

이제 인증 서비스를 만들 것이다. 앱의 설정 부분에서 우리가 필요한 모든 provider를 만들고, 이 중에서 AuthProvider를 사용하여 앱과 파이어베이스 간의 상호작용에 연관된 모든 인증을 처리할 것이다.

providrs/auth/auth.ts 파일을 열고, 이것이 가진 모든 메소드를 지우고 다음과 같은 상태로 만든다:


import {Injectable} from '@angular/core';
@Injectable()
export class AuthProvider {
 constructor() {}
}

여기서 상단에, 먼저 할 것은 파이어베이스를 가져오는 것이다.

import firebase from 'firebase';

constructor 바로 앞에 두 개의 변수를 생성한다.

public fireAuth:firebase.auth.Auth;
public userProfileRef:firebase.database.Reference;

fireAuth는 인증 객체를 보유할 것이고, userProfileRef는 파이어베이스 데이터베이스의 /userProfile/ 노드를 가리킬 것이다.

이제 constructor 안에서 초기화 해준다.


public fireAuth:firebase.auth.Auth;
public userProfileRef:firebase.database.Reference;
constructor() {
 this.fireAuth = firebase.auth();
 this.userProfileRef = firebase.database().ref('/userProfile');
}

우리가 이것들을 생성한 이유는 이러한 references를 파일 곳곳에서 사용할 것이기 때문이며, 이 방법은 우리가 미래에 references를 수정할 필요가 있을 때 처리하기 쉽게 만든다.

이제 이 파일에서 네 개의 함수를 만들어야 된다.

  • 존재하는 계정에 로그인하기

  • 새로운 계정 만들기

  • 비빌번호 reset 메일 전송

  • 앱으로부터 로그아웃하기

첫 번째로 만들 함수는 LOGIN 함수다. 두 개의 string 매개변수(email, password)를 갖는 loginUser() 함수를 만든다:

loginUser(email: string, password: string){...}

그리고 함수 안해 파이어베이스 로그인을 만든다:


loginUser(email: string, password: string): firebase.Promise<any> {
 return this.fireAuth.signInWithEmailAndPassword(email, password);
}

여기서는 signInWithEmailAndPassword() 메서드를 사용한다. 이 메서드는 이메일과 비밀번호를 이용하여 사용자에게 로그인한다.

Firebase의 작동 방식은, 일반적인 'username' 로그인이 없기 때문에, 사용자는 유효한 이메일을 username으로 사용해야 한다.

함수에서 에러가 나면, 이것은 에러 코드와 메세지를 돌려준다. 예를 들면 유효하지 않은 이메일이나 비밀번호가 그것이다.

함수를 모두 통과하면, 사용자는 로그인되고, 파이어베이스는 인증 객체를 로컬 저장소에 저장하고 유저를 Promise에게 반환한다.

  • NOTE : 만약 promises를 처음 접한다면(내가 처음으로 아이오닉을 했던 것처럼) 걱정마라. 인증 모듈을 만든 후에 promises가 무엇이며 어떻게 동작하는지 최선을 다해 설명할 것이다.

두번째 함수는 가입 기능이다, 그러나 이것은 항상 새로운 사용자가 계정을 만드는 것이 전부가 아니라, 사용자의 이메일을 데이터베이스에 저장하는 것도 포함되어야 한다.

  • SIDE-NOTE : 데이터베이스와 인증은 연결되어 있지 않다. 즉, 새로운 사용자를 만드는 것이 이것의 정보를 데이터베이스에 저장한다는 것을 의미하진 않는 것이다. 이것은 단지 앱의 인증 모듈 내에 저장되는 것이며, 때문에 우리는 이 정보를 복사하여 수동으로 데이터베이스에 넣어주어야 한다.


signupUser(email: string, password: string): firebase.Promise<any> {
 return this.fireAuth.createUserWithEmailAndPassword(email, password)
.then( newUser => {
 this.userProfileRef.child(newUser.uid).set({
 email: email
});
});
}

여기서는 createUserWithEmailAndPassword()가 새로운 사용자를 만드는데 이용되었다.

이 함수는 쿨하다. 왜냐하면 사용자를 생성하고 난 후 자동으로 사용자를 로그인하기 때문에(항상 그렇지는 않지만) 로그인 함수를 다시 호출 할 필요가 없기 때문이다.

이 함수는 새로운 사용자를 생성하고 앱에 로그인 할 때 몇 가지 코드를 실행하는 Promise(이것에 대해서는 나중에 얘기할거다)를 반환한다:

this.userProfileRef.child(newUser.uid).set({ email: email });

그것은 데이터베이스 내의 userProfile 노드에 대한 reference이다.

그 reference는 userProfile 노드 내에 새로운 노드를 만들고, UID는 노드를 식별한다. UID는 파이어베이스에서 사용자에 대해 생성된 자동 ID이며, 고유 식별자이다.

또한, 이 노드에 이메일이라는 속성을 추가하여, 새 사용자의 이메일 주소로 채운다.



이제 사용자가 암호를 기억하지 못할 때 암호를 재설정 할 수 있는 기능이 필요하다.


resetPassword(email: string): firebase.Promise<void> {
 return this.fireAuth.sendPasswordResetEmail(email);
}

여기서는 sendPasswordResetEmail()이 쓰였으며, 이것은 void Promise를 반환하는데, 이 의미는 promise를 반환하더라도 promise는 비어 있다는 뜻이다. 따라서 주로 다른 작업을 수행하는데 사용된다.

그리고선 파이어베이스가 리셋 로그인을 처리한다. 암호 재설정 링크를 사용자에게 발송하고 나면, 사용자는 힘들이지 않고 그들의 비밀번호를 쉽게 변경할 수 있다.

이제 마지막으로 로그아웃 함수를 만들 차례다.


logoutUser(): firebase.Promise<void> {
 return this.fireAuth.signOut();
}

이것은 어떠한 인수도 취하지 않고, 단지 현재 사용자를 확인하고 그를 로그아웃 시킨다.

또한 void promise를 반환시킨다. 보통 이것을 사용자를 다른 페이지로 이동시킬 때 사용한다.(아마 LoginPage로 이동시킬것이다.)

로그아웃할때 고민하는 것 중 하나가, 때때로 앱이 데이터베이스 reference를 여전히 listening 하고 있는 경우이며, 이러한 경우 여러분의 보안 규칙을 설정할 때 에러를 발생기킨다. 이를 해결하는 방법은 간단히, 로그 아웃하기 전에 참조를 해제해주면 된다.


logoutUser(): firebase.Promise<void> {
 this.userProfileRef.child(this.fireAuth.currentUser.uid).off();
 return this.fireAuth.signOut();
}

이제 모든 함수를 사용할 준비가 되었다. 다음으로, 이제 실제 페이지들을 만들 것이다: 로그인, 가입, 비밀번호 재설정

3. 인증 페이지

이제 Firebase<<>>Ionic 인증 관련 통신을 모두 처리할 수 있는 AuthProvider라는 완벽한 서비스 또는 Provider를 보유하게 되었다. 이제 사용자가 보게 될 실제 페이지를 만들어야 한다. 로그인 페이지부터 시작해보자:

로그인 페이지

pages/login/login.html 을 열고 이메일과 패스워드를 받는 간단한 로그인 폼을 만든다:


<ion-header>
 <ion-navbar color="primary">
   <ion-title>
    Log In
   </ion-title>
 </ion-navbar>
</ion-header>
<ion-content padding>
 <img src="http://placehold.it/300x100">
 <form [formGroup]="loginForm" (submit)="loginUser()" novalidate>
   <ion-item>
     <ion-label stacked>Email</ion-label>
     <ion-input formControlName="email" type="email"
                placeholder="Your email address"
                [class.invalid]="!loginForm.controls.email.valid &&
                                 loginForm.controls.email.dirty"></ion-input>
   </ion-item>
   <ion-item class="error-message" *ngIf="!loginForm.controls.email.valid
                                          && loginForm.controls.email.dirty">
     <p>Please enter a valid email.</p>
   </ion-item>
   <ion-item>
     <ion-label stacked>Password</ion-label>
     <ion-input formControlName="password" type="password"
                placeholder="Your password"
                [class.invalid]="!loginForm.controls.password.valid &&
                                 loginForm.controls.password.dirty"></ion-input>
   </ion-item>
   <ion-item class="error-message"
             *ngIf="!loginForm.controls.password.valid &&
                    loginForm.controls.password.dirty">
     <p>Your password needs more than 6 characters.</p>
   </ion-item>
   <button ion-button block type="submit" [disabled]="!loginForm.valid">
    Login
   </button>
 </form>
 <button ion-button block clear (click)="goToSignup()">
  Create a new account
 </button>
 <button ion-button block clear (click)="goToResetPassword()">
  I forgot my password
 </button>
</ion-content>

이건 단지 Ionic 컴퍼넌트를 사용한 기본적인 HTML 양식으로, 폼 유효성 검사도 사용되었다.(그리고 우리는 TypeScript 파일에 폼 유효성 검사를 추가할 것이다.)

이제 약간의 스타일을 줄 시간이다. 그러나 이것을 해내느라 시간을 낭비하지 않고, 단지 약간의 여백과 .invalid 클래스만을 추가할 것이다.

login.scss 를 열고 스타일을 추가한다:


page-login {
 form {
   margin-bottom: 32px;
   button {
     margin-top: 20px;
  }
}
 p {
   font-size: 0.8em;
   color: #d2d2d2;
}
 ion-label {
   margin-left: 5px;
}
 ion-input {
   padding: 5px;
}
 .invalid {
   border-bottom: 1px solid #FF6153;
}
 .error-message .item-inner {
   border-bottom: 0 !important;
}
}

마지막으로 실제 TypeScript코드를 작성해야 할 순간이 왔다. login.ts 파일을 열면, 아래와 유사한 모습을 띄고 있을 것이다:


import { Component } from '@angular/core';
import { IonicPage, NavController } from 'ionic-angular';
@IonicPage({
 name: 'login'
})
@Component({
 selector: 'page-login',
 templateUrl: 'login.html',
})
export class LoginPage {
 constructor(public navCtrl: NavController) {...}
}

첫번째로 추가해야 할 것은 import할 목록들이다. 필요한 모든 것들이 이용 가능하다:


import { Component } from '@angular/core';
import {
 IonicPage,
 Loading,
 LoadingController,
 NavController,
 AlertController } from 'ionic-angular';
import { FormBuilder, Validators, FormGroup } from '@angular/forms';
import { EmailValidator } from '../../validators/email';
import { AuthProvider } from '../../providers/auth/auth';
import { HomePage } from '../home/home';

importing한 내용들은 다음과 같다 :

  • LoadingFormGroup 는 로딩 컴포넌트의 변수와 폼 변수의 타입을 선언하기 위해 사용할 것이다.

  • LoadingController 와 AlertController은 페이지 안에서 경고 팝업과 로딩 컴퍼넌트를 사용하기 위해 이용할 것이다.

  • FormBuilder, Validators those are used to get form validation going on with angular.

  • AuthProvider는 우리가 만든 인증 provider로, 로그인 함수를 이용하기 위해 사용할 것이다.

  • EmailValidator is a custom validator I created, it makes sure that when the user is going

    to type an email address, it follows this format: something @ something . something.

src/validators/email.ts 파일을 만들고 다음과 같이 채운다:


import { FormControl } from '@angular/forms';
export class EmailValidator {
 static isValid(control: FormControl){
   const re = /^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$/
  .test(control.value);
   if (re){ return null; }
   return { "invalidEmail": true };
}
}

모두 import 했으면, constructor(생성자) 안에 모든 제공자(provider)를 주입할 것이므로, 클래스 내부에서 모든 provider를 사용할 수 있게 될 것이다 :


constructor(public navCtrl: NavController,
           public loadingCtrl: LoadingController, public alertCtrl: AlertController,
           public authProvider: AuthProvider, public formBuilder: FormBuilder) {...}

이제는 이 constructor 앞에 두 개의 전역 변수를 만들거다. 하나는 로그인 폼을 처리하고 다른 하나는 로딩 구성 요소를 처리하는 것이다.


public loginForm: FormGroup;
public loading: Loading;
constructor(public navCtrl: NavController,
           public loadingCtrl: LoadingController, public alertCtrl: AlertController,
           public authProvider: AuthProvider, public formBuilder: FormBuilder) {...}

constructor안에, form을 초기화해준다:


constructor(public navCtrl: NavController,
           public loadingCtrl: LoadingController, public alertCtrl: AlertController,
           public authProvider: AuthProvider, public formBuilder: FormBuilder) {
 this.loginForm = formBuilder.group({
   email: ['', Validators.compose([Validators.required,
                                   EmailValidator.isValid])],
   password: ['', Validators.compose([Validators.minLength(6),
                                      Validators.required])]
});
}

위 코드는 constructor 내부에서 formBuilder를 사용해 필드를 초기화하고, 필요한 유효성 검사기를 제공한다.

FormBuilder에 대해 자세히 알고 싶다면, Aungular 문서를 참조.

이제 로그인 페이지에 필요한 우리의 함수를 만든다:


loginUser(): void {
 if (!this.loginForm.valid){
   console.log(this.loginForm.value);
} else {
   this.authProvider.loginUser(this.loginForm.value.email,
                               this.loginForm.value.password)
    .then( AuthProvider => {
     this.loading.dismiss().then( () => {
       this.navCtrl.setRoot(HomePage);
    });
  }, error => {
     this.loading.dismiss().then( () => {
       let alert = this.alertCtrl.create({
         message: error.message,
         buttons: [
          {
             text: "Ok",
             role: 'cancel'
          }
        ]
      });
       alert.present();
    });
  });
   this.loading = this.loadingCtrl.create();
   this.loading.present();
}
}

이 로그인 함수는 양식(form) 필드의 값을 가져 와서 AuthProvider 서비스의 loginUser함수에 전달한다. 또한 Ionic의 로딩 구성 요소를 불러 사용자가 시각적으로 로드 중이라는 것을 이해할 수 있도록 한다.

goToSignup(): void { this.navCtrl.push('signup'); }
goToResetPassword(): void { this.navCtrl.push('reset-password'); }

위 두 가지는 간단한 것으로, SignupPage 또는 ResetPasswordPage로 사용자를 보내는 역할을 한다.

비밀번호 갱신 페이지

reset-password.html 파일을 열고, 우리가 로그인 페이지에서 만들었던 것과 거의 유사한 form을 만든다. 이메일 필드만.(form 이름이 달라야 한다는 것을 잊으면 안된다!)


<ion-header>
 <ion-navbar color="primary">
   <ion-title>
    Reset your Password
   </ion-title>
 </ion-navbar>
</ion-header>
<ion-content padding>
 <img src="http://placehold.it/300x100">
 <form [formGroup]="resetPasswordForm" (submit)="resetPassword()" novalidate>
   <ion-item>
     <ion-label stacked>Email</ion-label>
     <ion-input formControlName="email" type="email"
                placeholder="Your email address"
                [class.invalid]="!resetPasswordForm.controls.email.valid &&
                                 resetPasswordForm.controls.email.dirty">
     </ion-input>
   </ion-item>
   <ion-item class="error-message"
             *ngIf="!resetPasswordForm.controls.email.valid &&
                    resetPasswordForm.controls.email.dirty">
     <p>Please enter a valid email.</p>
   </ion-item>
   <button ion-button block type="submit"
           [disabled]="!resetPasswordForm.valid">
    Reset your Password
   </button>
 </form>
</ion-content>

reset-password.scss 파일에 기본 여백 및 테두리를 추가한다:


page-reset-password {
 form {
   margin-bottom: 32px;
   button {
     margin-top: 20px;
  }
}
 p {
   font-size: 0.8em;
   color: #d2d2d2;
}
 ion-label {
   margin-left: 5px;
}
 ion-input {
   padding: 5px;
}
 .invalid {
   border-bottom: 1px solid #FF6153;
}
 .error-message .item-inner {
   border-bottom: 0 !important;
}
}

다음과 비슷한 구조로 되어 있을 reset-password.ts를 연다:


import { Component } from '@angular/core';
import { IonicPage, NavController } from 'ionic-angular';
@IonicPage({
 name: 'reset-password'
})
@Component({
 selector: 'page-reset-password',
 templateUrl: 'reset-password.html',
})
export class ResetPasswordPage {
 constructor(public navCtrl: NavController) {...}
}

이전과 마찬가지로, imports를 추가하고, 우리의 서비스를 constructor 안에 삽입한다:


import { Component } from '@angular/core';
import { IonicPage, NavController, AlertController } from 'ionic-angular';
import { FormBuilder, Validators, FormGroup } from '@angular/forms';
import { AuthProvider } from '../../providers/auth/auth';
import { EmailValidator } from '../../validators/email';
@IonicPage({
 name: 'reset-password'
})
@Component({
 selector: 'page-reset-password',
 templateUrl: 'reset-password.html',
})
export class ResetPasswordPage {
 public resetPasswordForm: FormGroup;
 constructor(public navCtrl: NavController,
              public authProvider: AuthProvider, public formBuilder: FormBuilder,
              public alertCtrl: AlertController) {
   this.resetPasswordForm = formBuilder.group({
     email: ['', Validators.compose([Validators.required,
                                     EmailValidator.isValid])],
  });
}
}

그런 다음 비밀번호 재설정 기능을 만든다:


resetPassword(){
 if (!this.resetPasswordForm.valid){
   console.log(this.resetPasswordForm.value);
} else {
   this.authProvider.resetPassword(this.resetPasswordForm.value.email)
    .then((user) => {
     let alert = this.alertCtrl.create({
       message: "We just sent you a reset link to your email",
       buttons: [
        {
           text: "Ok",
           role: 'cancel',
           handler: () => { this.navCtrl.pop(); }
        }
      ]
    });
     alert.present();
  }, (error) => {
     var errorMessage: string = error.message;
     let errorAlert = this.alertCtrl.create({
       message: errorMessage,
       buttons: [{ text: "Ok", role: 'cancel' }]
    });
     errorAlert.present();
  });
}
}

로그인과 마찬가지로 폼 필드의 값을 가져와 AuthProvider 서비스로 보내고 Firebase의 응답을 받는 동안 로드하는 구성 요소를 표시합니다.

가입 페이지

먼저 할 일은 뷰를 생성하는 것이다. signup.html을 이렇게 만든다:


<ion-header>
 <ion-navbar color="primary">
   <ion-title>
    Create an Account
   </ion-title>
 </ion-navbar>
</ion-header>
<ion-content padding>
 <img src="http://placehold.it/300x100">
 <form [formGroup]="signupForm" (submit)="signupUser()" novalidate>
   <ion-item>
     <ion-label stacked>Email</ion-label>
     <ion-input formControlName="email" type="email"
                placeholder="Your email address"
                [class.invalid]="!signupForm.controls.email.valid &&
                                 signupForm.controls.email.dirty">
     </ion-input>
   </ion-item>
   <ion-item class="error-message"
             *ngIf="!signupForm.controls.email.valid &&
                    signupForm.controls.email.dirty">
     <p>Please enter a valid email.</p>
   </ion-item>
   <ion-item>
     <ion-label stacked>Password</ion-label>
     <ion-input formControlName="password" type="password"
                placeholder="Your password"
                [class.invalid]="!signupForm.controls.password.valid &&
                                 signupForm.controls.password.dirty">
     </ion-input>
   </ion-item>
   <ion-item class="error-message"
             *ngIf="!signupForm.controls.password.valid &&
                    signupForm.controls.password.dirty">
     <p>Your password needs more than 6 characters.</p>
   </ion-item>
   <button ion-button block type="submit" [disabled]="!signupForm.valid">
    Create an Account
   </button>
 </form>
</ion-content>

signup.scss 파일에 약간의 마진을 추가한다:


page-signup {
 form {
   margin-bottom: 32px;
   button { margin-top: 20px; }
}
 p {
   font-size: 0.8em;
   color: #d2d2d2;
}
 ion-label { margin-left: 5px; }
 ion-input { padding: 5px; }
 .invalid { border-bottom: 1px solid #FF6153; }
 .error-message .item-inner { border-bottom: 0 !important; }
}

마지막으로 signup.ts 파일을 열고, 필요한 서비스를 import하고 그것들을 constructor에 삽입한다:


import { Component } from '@angular/core';
import { IonicPage,
       NavController,
       Loading,
       LoadingController,
       AlertController } from 'ionic-angular';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AuthProvider } from '../../providers/auth/auth';
import { EmailValidator } from '../../validators/email';
import { HomePage } from '../home/home';
@IonicPage({
 name: 'signup'
})
@Component({
 selector: 'page-signup',
 templateUrl: 'signup.html',
})
export class SignupPage {
 public signupForm:FormGroup;
 public loading:Loading;
 constructor(public navCtrl: NavController,
              public authProvider: AuthProvider, public formBuilder: FormBuilder,
              public loadingCtrl: LoadingController, public alertCtrl:
              AlertController){
   this.signupForm = formBuilder.group({
     email: ['', Validators.compose([Validators.required,
                                     EmailValidator.isValid])],
     password: ['', Validators.compose([Validators.minLength(6),
                                        Validators.required])]
  });
}
}

이제 signup 함수를 만든다:


signupUser(){
 if (!this.signupForm.valid){
   console.log(this.signupForm.value);
} else {
   this.authProvider.signupUser(this.signupForm.value.email,
                                this.signupForm.value.password)
    .then(() => {
     this.loading.dismiss().then( () => {
       this.navCtrl.setRoot(HomePage);
    });
  }, (error) => {
     this.loading.dismiss().then( () => {
       let alert = this.alertCtrl.create({
         message: error.message,
         buttons: [
          {
             text: "Ok",
             role: 'cancel'
          }
        ]
      });
       alert.present();
    });
  });
   this.loading = this.loadingCtrl.create();
   this.loading.present();
}
}

이제 완전한 인증 기능을 갖춘 앱을 갖게 되었다. 이제, 아까 언급했던 것처럼 Promises에 대해 조금 얘기해 볼 것이다.

4. Promises

Promises 는 JS의 중요한 부분이다. 특히 클라우드와 연관된 애플리케이션을 구현할 때, 그것들은 JSON API를 가져오거나 AJAX 작업을 수행할 때 사용되기 때문에, 다음 페이지를 이용하여 이것을 설명해보려 한다.

A. Promise 란 무엇인가

promise는 현재와 끝 사이에 일어날 것이다. 그것은 미래에 발생할 것이지만 즉작적으로 일어나지는 않을 것이다.

그러나 이게 무슨 의미란 말인가?

이것을 이해하기 위해서 다른 것을 배울 필요가 있다; 첫째로, JS는 거의 비동기적으로 수행된다는 것이다. 이것의 의미는 function을 call 했을 때, 이 함수는 실행중일 때 모든 것을 중단시키지는 않는다는 것이다.

이것은 함수의 기능이 모두 실행되지 않은 경우에도, function 아래의 코드들은 계속 실행되고 있다.

이것을 더 자세하게 설명하기 위한 간단을 예를 들어보자.


firebase.auth().signInWithEmailAndPassword("email", "password");
console.log(firebase.auth().currentUser);
console.log("This is a random string");

다른 언어를 사용해 왔다면, 당신은 코드가 이렇게 동작할 것이라고 기대할 것이다:

1. 사용자 로그인
2. 현재 사용자를 콘솔에 기록
3. 임의의 문자열을 콘솔에 기록

그러나, JS는 비동기이기 때문에 위와 같이 동작하지 않으며, 아래와 비슷하게 동작한다:

1. 사용자 로그인 기능을 호출한다.
2. 아직 사용자가 없기 때문에 null 또는 undefined가 기록됨
3. 임의의 문자열을 콘솔에 기록
4. 사용자 로그인을 완료했다.
5. 새 사용자를 반영하기 위해 콘솔에 대한 로그를 업데이트한다.(sometimes)

이것은 기본적으로 찾을 수 있는 모든 것을 완료되기를 기다리지 않고 실행하고 있는다.

말하자면, 하나의 promise가 다음과 같이 말할 수 있을 때 Promises가 온다:

"야, 지금 당장은 내가 데이터가 없어, 여기 차용증서가 있고, 데이터를 돌려받는대로 돌려줄께."

더욱이, .then() 을 사용해서 이러한 promises를 캐치할 수 있다. 위에 있는 예를 아래의 순서대로 수행되길 원한다면:


1. 사용자에게 로그인한다.
2. 현재 사용자를 콘솔에 기록한다.
3. 난수를 콘솔에 기록한다.

우리는 이러한 방식으로 작성해야 한다.

firebase.auth().signInWithEmailAndPassword("email","password")
.then( user=>{
 console.log(user);
 console.log("This is a random string");
})

이러한 방법은 JS가 나머지 코드를 실행핼 때까지 기다리게 된다.

B. Create your Promises

당신은 JS에서 거의 모두 만들어지는 Promises에 의존할 필요가 없다; 당신 또한 당신의 promises를 만들 수 있다.

data provider나 서비스를 작성 중이며, 파이어베이스에더 데이터를 가져 오려는 상황을 가정해보자.(맞다, 우리는 기본적으로 promise를 반환한다는 것을 알고 있다.) 그러나 Firebase의 promise를 반환하는 대신, 데이터를 조작한 후 이것을 반환하려면 어떻게 해야 할까?

다음과 같이 쉽게 할 수 있다.


firebase.auth().signInWithEmailAndPassword("j@javebratt.com", "123456")
.then( user => {
  return new Promise( (resolve, reject) => {
     if (user) {
        user.newPropertyIamCreating = "New value I'm adding";
        resolve(user);
    } else {
        reject(error);
    }
  });
});

Firebase의 promise를 캐치한 그곳에서, user object를 수정한 다음 이 object를 새로운 promise로 반환한다.(또는 오류를 반환한다)

Promise는 resolve(해결)와 reject(거절)의 두 가지 인수를 취하고, resolve에는 promise 안에 무엇을 반환해 놓을지를 결정하기 위해 사용, 오류가 있는 경우를 잡기 위해 reject를 사용한다.

다음 장에서는 실시간 데이터베이스 작업 방법을 이해하는데 도움이 되는 몇 가지 CRUD를 수행해보려 한다.

5. Create, Read , Update, and Delete your data.

우리는 이제 인증에 대해 배웠고 promise 가 어떻게 동작하는지 알았다. 이제 앱에 더 많은 기능을 추가할 차례다. 우리는 사용자의 프로필 페이지를 만드는 동안, 데이터베이스의 개체로 작업할 것이다.

A. Setup

가장 먼저 할 일은 이 튜토리얼에서 필요한 모든 부분들을 setup 하는 것이다. 우리는 profile 페이지와 profile data provider를 만들 것이다.

그러나 우리는 이미 첫번째 장에서 이러한 실제 파일들을 만들었었음을 기억해라. 이제 우리는 그것을 building하는 과정만이 필요하다.

첫 번째 할 일은 profile 페이지에 대한 링크를 만드는 것이다. 그래서 home.html로 가 goToProfile() 함수를 호출하는 헤더에 버튼을 만든다.


<ion-header>
 <ion-navbar>
   <ion-title>Home Page</ion-title>
   <ion-buttons end>
     <button ion-button icon-only (click)="goToProfile()">
       <ion-icon name="person"></ion-icon>
     </button>
   </ion-buttons>
 </ion-navbar>
</ion-header>

이제 home.ts 파일로 가서 우리의 사용자를 profile 페이지로 보내는 기능을 만든다:


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

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

export class HomePage {
 constructor(public navCtrl: NavController) {}
 
 goToProfile(){
   this.navCtrl.push('profile');
}
}

B. User Profile Provider 만들기

이제 살짝 더 복잡한 조각으로 들어가보자. profile provider를 만드려고 한다. 이 제공자에 대한 아이디어는 우리가 우리의 profile 정보를 firebase의 실시간 데이터베이스에 저장하고, profile 데이터로부터 이메일과 패스워드를 변경할 수 있게 한다는 것이다.

이 provider는 다음과 같은 기능을 가져야 한다:

  • 사용자 프로필 얻기

  • 사용자 이름 업데이트

  • 사용자의 생년월일 업데이트

  • 실시간 데이터베이스와 인증 데이터 모두에서 사용자의 전자 메일 업데이트

  • 사용자 암호 변경

먼저 할 일은 Firebase를 가져오는 것이다. 따라서 src/providers/profile/profile.ts를 열고 firebase를 추가한다:

import firebase from 'firebase';

authentication provider와 마찬가지로, 두 개의 변수를 만들고 초기화한다.


public userProfile:firebase.database.Reference;
public currentUser:firebase.User;

constructor() {
 firebase.auth().onAuthStateChanged( user => {
   if (user){
     this.currentUser = user;
     this.userProfile = firebase.database().ref('/userProfile')
      .child(user.uid);
  }
});
}

우리는 userProfile를 현재 로그인한 사용자의 데이터베이스 레퍼런스로 사용하고, currentUser는 사용자 객체가 될 것이다.

우리는 onAuthStateChanged() 안에 래핑한다. 왜냐하면 우리가 동기적으로 사용자를 얻는다면, provider가 초기화될때 null을 반환할 가능성이 있기 때문이다.

onAuthStateChanged() 함수를 사용해서 변수를 할당하기 전에, 먼저 사용자를 확인해야 한다.

이제 함수를 생성할 수 있다.. 데이터베이스에서 사용자의 프로필을 반환하는 함수부터 시작해보자:


getUserProfile(): firebase.database.Reference {
 return this.userProfile;
}

이미 userProfile을 초기화 해두었기 때문에 이 함수에서 이것을 반환할 수 있으며, 우리는 profile 페이지에서 결과를 처리할 것이다.

다음은 사용자의 이름을 업데이트하는 함수를 만드는 것이다:


updateName(firstName: string, lastName: string): firebase.Promise<void> {
 return this.userProfile.update({
 firstName: firstName,
 lastName: lastName,
});
}

우리는 오직 firstName과 lastName 속성만 업데이트하길 원하기 때문에 update()를 사용하고 있다. 만약 데이터베이스에 write하는 .set()을 이용하면, 사용자의 profile에 있는 모든 데이터가 지워지고 first name과 second name은 대체된다.

.update() 또한 promise를 반환하지만, 이것은 void이다. 이 의미는 이 안에 아무것도 가지지 않았다는 것을 뜻하며, 단지 작업이 완료한 시점에 이후 다른 작업을 수행하기 위해 사용된다.

다음 함수는 사용자의 생일을 업데이트 하는 것이다. 이것은 updateName() 함수와 매우 닮았으며, 다른 property를 업데이트 한다는 것만 다르다:


updateDOB(birthDate: string): firebase.Promise<any> {
 return this.userProfile.update({
   birthDate: birthDate,
});
}

이제 조금 복잡한 이메일 주소를 바꾸는 것을 해 보려고 한다. 이것은 좀 복잡하다. 왜냐하면 데이터베이스의 이메일 변경뿐만 아니라, 이것을 인증 서비스에서도 변경해야 하기 때문이다.

이는 사용자가 앱에 로그인 할 때 사용하는 이메일을 변경한다는 것을 의미하며, 변경한 이메일 기능을 호출하여 마술처럼 로그인할수는 없다.

이는 계정을 삭제하고 기본 이메일 주소를 설정하며 비밀번호를 변경하는 등 보안에 민감한 일부 작업은 사용자가 최근 로그인 한 상태여야 하기 때문이다.

이러한 작업 중 하나를 수행하고 사용자가 너무 오래 전에 로그인하면 오류와 함께 작업이 실패한다. 이 경우, 사용자의 새 로그인 자격 증명을 얻고 자격 증명을 전달하여 다시 인증함으로써 사용자를 다시 인증해야 한다.

나는 앞으로 나아가서 그 기능을 창조하고 당신이 더 잘 이해할 수 있도록 할 것이다 :


updateEmail(newEmail: string, password: string): firebase.Promise<any> {
 const credential = firebase.auth.EmailAuthProvider
.credential(this.currentUser.email, password);
 
 return this.currentUser.reauthenticateWithCredential(credential)
.then( user => {
   this.currentUser.updateEmail(newEmail).then( user => {
     this.userProfile.update({ email: newEmail });
  });
});
}

위 함수에 대한 설명:

  • 여기서는 firebase.auth.EmailAuthProvider.credentail();을 사용하고 있다; 인증 정보 object를 생성하기 위해, Firebase는 이것을 인증에 사용한다.

  • 인증 정보 객체를 재 인증 함수에 전달할 것이므로, Firebase가 이메일을 변경하려는 사용자가 계정을 소유 한 실제 사용자인지 확인하기 위해 이를 수행하는 것이 가장 좋다. 예를 들어 사용자가 최근에 등록한 전자 메일과 암호인 경우, 최근에 통과한 전자 메일과 암호를 전달하지만, 그렇지 않은 경우 사용자가 잠시 동안 전화를 사용하지 않는 시나리오를 피하기 위해 다시 요청을 시도하고, 다른 사람이 이 작업을 시도한다.

  • re-authenticate 함수가 완료된 후 .updateEmail()을 호출하고 새 전자 메일 주소를 전달하면, updateEmail ()은 이름에서 알 수 있듯이 사용자의 이메일 주소를 업데이트한다.

  • 사용자의 전자 메일 주소가 authentication service에서 업데이트 된 후, 데이터베이스에서 프로필 참조(profile reference)를 호출하고 거기에서 전자 메일을 새로 고친다.

까다로운 updateEmail()을 구현했으므로, 이제 updatePassword()를 쉽게 구현할 수 있다.


updatePassword(newPassword: string, oldPassword:
   string):firebase.Promise<any>{
 const credential = firebase.auth.EmailAuthProvider
  .credential(this.currentUser.email, oldPassword);
 
 return this.currentUser.reauthenticateWithCredential(credential)
.then( user => {
   this.currentUser.updatePassword(newPassword).then( user => {
     console.log("Password Changed");
  }, error => {
     console.log(error);
  });
});
}

지금 가지고있는 것을 분석해 보자. 이제 응용 프로그램과 Firebase 간의 모든 프로필 관련 상호 작용을 처리 할 수있는 완벽한 기능을 갖춘 Provider가 있다.

기능을 복사 / 붙여 넣지 않고도 앱 내부의 어디에서나 이러한 함수를 호출 할 수 있기 때문에 유용하다.

이제는 이것들은 작동 중이며, 프로필 페이지를 만들 예정이며, 이것은 사용자 프로필 정보를 표시, 추가 및 업데이트하는 페이지가 될 것이다.

C. 프로필 페이지 만들기

우리는 이것을 세가지 부분, view, design, code로 분해할 것이다

첫번째로 우리가 할 일을 시작하기 전에 그 배경에 대한 로직에 대해 설명하겠다.

프로필 관련 업데이트를 하기 위해 여러가지 view를 갖는 대신, 이것에 대한 하나의 View만 가지고, 프로필을 업데이트할 필요가 있을 경우 한 번의 클릭으로 작은 팝업이 나타나서 view를 떠나지 않고 정보를 추가할 수 있도록 했다.

이에 대한 HTML 코드는 다음과 같다:


<ion-header>
 <ion-navbar color="primary">
   <ion-title>Profile</ion-title>
   <ion-buttons end>
     <button ion-button icon-only (click)="logOut()">
       <ion-icon name="log-out"></ion-icon>
     </button>
   </ion-buttons>
 </ion-navbar>
</ion-header>

<ion-content padding>
 <ion-list>
   <ion-list-header>
    Personal Information
   </ion-list-header>
   
   <ion-item (click)="updateName()">
     <ion-grid>
       <ion-row>
         <ion-col col-6>
          Name
         </ion-col>
         <ion-col col-6 *ngIf="userProfile?.firstName ||
                               userProfile?.lastName">
          {{userProfile?.firstName}} {{userProfile?.lastName}}
         </ion-col>
         <ion-col col-6 class="placeholder-profile"
                  *ngIf="!userProfile?.firstName">
           <span>
            Tap here to edit.
           </span>
         </ion-col>
       </ion-row>
     </ion-grid>
   </ion-item>
   
   <ion-item>
     <ion-label class="dob-label">Date of Birth</ion-label>
     <ion-datetime displayFormat="MMM D, YYYY" pickerFormat="D MMM YYYY"
         [(ngModel)]="birthDate" (ionChange)="updateDOB(birthDate)">
     </ion-datetime>
   </ion-item>
   
   <ion-item (click)="updateEmail()">
     <ion-grid>
       <ion-row>
         <ion-col col-6>
          Email
         </ion-col>
         <ion-col col-6 *ngIf="userProfile?.email">
          {{userProfile?.email}}
         </ion-col>
         <ion-col col-6 class="placeholder-profile"
                  *ngIf="!userProfile?.email">
           <span>
            Tap here to edit.
           </span>
         </ion-col>
       </ion-row>
     </ion-grid>
   </ion-item>
   
   <ion-item (click)="updatePassword()">
     <ion-grid>
       <ion-row>
         <ion-col col-6>
          Password
         </ion-col>
         <ion-col col-6 class="placeholder-profile">
           <span>
            Tap here to edit.
           </span>
         </ion-col>
       </ion-row>
     </ion-grid>
   </ion-item>
 </ion-list>
</ion-content>

이것은 이해하기 굉장히 쉽다.

참고 : 주요 개체 인 userProfile? .firstName에 사용 된 물음표는 Elvis 연산자라고하며, 해당 속성에 액세스하거나 액세스하려고하기 전에 먼저 개체가 있는지 확인한다.

값이 없다면 "수정하려면 여기를 누르십시오"라는 placeholder를 표시하면, 사용자는 프로필 항목을 선택할 수 있도록 터치해야한다는 것을 알 수 있다.

다른 부분은 생년월일 부분이다:


<ion-item>
 <ion-label class="dob-label">Date of Birth</ion-label>
 <ion-datetime displayFormat="MMM D, YYYY" pickerFormat="D MMM YYYY"
  [(ngModel)]="birthDate" (ionChange)="updateDOB(birthDate)"></ion-datetime>
</ion-item>

우리는 DoB를 업데이트하기 위해 modal을 열 수 있었지만, (ionChange) 함수를 사용하여 같은 페이지에서 모든 것을 처리 할 수 있도록 했다 :)

이제 HTML이 제자리에 있으므로 스타일을 만들 것입니다.


page-profile {
 ion-item ion-label {
   margin: 0 !important;
}
 ion-list {
   margin: 0;
   padding: 0;
}
 ion-list-header {
   background-color: #ECECEC;
   margin: 0;
   padding: 0;
}
 ion-item {
   padding: 0 !important;
}
 ion-datetime {
   padding-left: 3px !important;
}
 .item-inner {
   border: none !important;
   padding: 0;
}
 .popover-content {
   min-height: 0 !important;
   max-height: 88px !important;
}
 .profile-popover {
   margin-top: -1px !important;
}
 .placeholder-profile {
   color: #CCCCCC;
}
 .dob-label {
   color: #000000 !important;
   padding: 10px !important;
   max-width: 50% !important;
}
}

이제 이 페이지를 위한 기능들을 코딩하기 위한 준비가 되었다. 먼저 우리가 사용하고 삽입하는 모든 것들을 import 하자:


import { Component } from '@angular/core';
import { IonicPage, NavController, AlertController } from 'ionic-angular';
import { ProfileProvider } from '../../providers/profile/profile';
import { AuthProvider } from '../../providers/auth/auth';

@IonicPage({
 name: 'profile'
})

@Component({
 selector: 'page-profile',
 templateUrl: 'profile.html',
})
export class ProfilePage {
 
 constructor(public navCtrl: NavController,
 public alertCtrl: AlertController, public profileProvider: ProfileProvider,
 public authProvider: AuthProvider){}
}

사용자가 업데이트 할 데이터를 캡처하고 잘못되었을 경우 경고를 표시하기 위해 AlertController를 import했다.

또한 필요한 기능들을 사용하기 위해 우리의 profile provider와 authentication provider를 import하였다.

우리 함수를 추가하기 위해서 우리는 사용자 프로파일을 가지고 있어야하며, 현재 생성자(constructor) 바로 앞에 화면에 현재 데이터를 표시하기 위해 먼저 두 개의 변수를 생성한다 :


export class ProfilePage {
 public userProfile: any;
 public birthDate: string;
 constructor(public navCtrl: NavController,
      public alertCtrl: AlertController, public profileProvider: ProfileProvider,
      public authProvider: AuthProvider){}
}


반응형

공유하기

facebook twitter kakaoTalk kakaostory naver band