사용자 프로필 저장소의 경로를 계속 따라가면서 이전에 보았던 것은 다음과 같습니다. Node.js 및 Couchbase NoSQL로 만드는 방법 뿐만 아니라 웹 클라이언트 프런트엔드에 대한 앵귤러를 사용하여. 이를 현재 모든 애플리케이션의 표준인 모바일 애플리케이션으로 가져가려면 어떻게 해야 할까요?
시중에는 다양한 모바일 프레임워크가 있으며, 운 좋게도 이전 예제에서 사용했던 Angular를 지원하는 프레임워크도 있습니다. 이제 클라이언트 프론트엔드를 모바일로 변환하는 방법을 살펴보겠습니다. 네이티브 스크립트 및 Angular.
계속 진행하기 전에 이전 튜토리얼 두 개를 모두 완료했다고 가정합니다. 프로필 스토어 백엔드 만들기 를 켜고 다른 하나는 프로필 스토어 웹 프런트엔드 만들기. 또한 개발 환경이 Android, iOS 또는 둘 다 모바일 개발을 위해 구성된 속성이라고 가정합니다.
이 애플리케이션의 이벤트 흐름은 웹 버전에서 본 것과 일치합니다.
Angular 프로젝트로 새 네이티브 스크립트 만들기
NativeScript CLI를 설치 및 구성했다고 가정하고 다음을 실행하여 새 프로젝트를 만듭니다:
1 |
tns create 프로필-프로젝트-ns --ng |
그리고 --ng
플래그를 사용하는 것이 중요한 이유는 NativeScript Core 프로젝트가 아닌 Angular 프로젝트를 생성한다는 것을 의미하기 때문입니다.
현재로서는 NativeScript CLI는 컴포넌트를 생성하는 Angular CLI 기능을 공유하지 않습니다. 따라서 각 HTML 및 TypeScript 파일을 수동으로 생성해야 합니다.
Mac 또는 Linux를 사용하는 경우 NativeScript 프로젝트 내에서 다음을 실행합니다:
1 2 3 4 5 6 7 8 9 10 11 12 |
mkdir -p 앱/로그인 mkdir -p 앱/등록 mkdir -p 앱/블로그 mkdir -p 앱/블로그 터치 앱/로그인/로그인.컴포넌트.html 터치 앱/로그인/로그인.컴포넌트.ts 터치 앱/등록/등록.컴포넌트.html 터치 앱/등록/등록.컴포넌트.ts 터치 앱/블로그/블로그.컴포넌트.html 터치 앱/블로그/블로그.컴포넌트.ts 터치 앱/블로그/블로그.컴포넌트.html 터치 앱/블로그/블로그.컴포넌트.ts |
Windows를 사용하는 경우 해당 디렉터리와 파일을 수동으로 만들면 됩니다. 정말로 원한다면 웹 프로젝트에서 이러한 디렉토리와 파일을 이전 튜토리얼.
각 화면을 표현할 컴포넌트 정의하기
웹 버전과 같은 방향에서 시작하여 사용자 로그인에 중점을 두겠습니다. 프로젝트의 app/login/login.component.ts 파일을 열고 다음 코드를 포함하세요:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
가져오기 { 구성 요소 } 에서 '@angular/core'; 가져오기 { Http, 헤더, 요청 옵션 } 에서 "@angular/http"; 가져오기 { 라우터 } 에서 "@각/라우터"; 가져오기 "rxjs/Rx"; @구성 요소({ moduleId: 모듈.id, 선택기: '앱 로그인', templateUl: './login.component.html' }) 내보내기 클래스 로그인 컴포넌트 { public 입력: any; 생성자(비공개 http: Http, 비공개 라우터: 라우터) { 이.입력 = { "이메일": "", "비밀번호": "" }; } public 로그인() { 만약(이.입력.이메일 && 이.입력.비밀번호) { let 헤더 = new 헤더({ "콘텐츠 유형": "application/json" }); let 옵션 = new 요청 옵션({ 헤더: 헤더 }); 이.http.post("http://localhost:3000/login", JSON.문자열화(이.입력), 옵션) .지도(결과 => 결과.json()) .구독(결과 => { 이.라우터.탐색(["/blogs"], { "queryParams": 결과 }); }); } } } |
위의 코드는 두 가지 예외를 제외하고 웹 버전에서 보았던 코드와 동일합니다. 컴포넌트 단위로 CSS 파일을 만들지 않았기 때문에 CSS 참조를 제거했습니다. 또한 moduleId
를 사용하여 상대 경로가 컴포넌트에서 작동할 수 있도록 했습니다. 이 두 가지 항목은 NativeScript와는 관련이 없는 Angular 관련 항목입니다.
HTML은 상황이 달라지는 곳입니다. 프로젝트의 앱 로그인/로그인 컴포넌트.html 파일을 열고 다음 XML 마크업을 포함합니다:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<액션바 title="{N} 프로필 저장소"></액션바> <스택 레이아웃 클래스="form"> <스택 레이아웃 클래스="입력 필드"> <라벨 텍스트="이메일" 클래스="레이블 글꼴-가중-볼드 M-B-5"></라벨> <텍스트 필드 클래스="입력" [(ngModel)]="input.email" 자동 대문자화 유형="none"></텍스트 필드> <스택 레이아웃 클래스="hr-light"></스택 레이아웃> </스택 레이아웃> <스택 레이아웃 클래스="입력 필드"> <라벨 텍스트="비밀번호" 클래스="레이블 글꼴-가중-볼드 M-B-5"></라벨> <텍스트 필드 클래스="입력" 보안="true" [(ngModel)]="입력.비밀번호" 자동 대문자화 유형="none"></텍스트 필드> <스택 레이아웃 클래스="hr-light"></스택 레이아웃> </스택 레이아웃> <스택 레이아웃 클래스="입력 필드"> <버튼 클래스="btn btn-primary" 텍스트="로그인" (탭)="login()"></버튼> </스택 레이아웃> <스택 레이아웃 클래스="입력 필드"> <라벨 텍스트="여기에서 등록하세요." [nsRouterLink]="['/register']" 클래스="텍스트 중심"></라벨> </스택 레이아웃> </스택 레이아웃> |
네이티브스크립트에는 UI를 위한 자체 마크업이 있습니다. Angular와 동일한 규칙이 적용되지만 UI 컴포넌트를 만드는 방식은 약간 다릅니다.
예를 들어, 우리는 여전히 [(ngModel)]
속성 대신 div
태그가 있습니다. 스택 레이아웃
태그.
이제 등록 컴포넌트를 살펴보겠습니다. 프로젝트의 앱/등록/등록.component.ts 파일을 열고 다음 TypeScript를 포함합니다:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
가져오기 { 구성 요소 } 에서 '@angular/core'; 가져오기 { Http, 헤더, 요청 옵션 } 에서 "@angular/http"; 가져오기 { 라우터 } 에서 "@각/라우터"; 가져오기 "rxjs/Rx"; @구성 요소({ moduleId: 모듈.id, 선택기: '앱-등록', templateUl: './register.component.html' }) 내보내기 클래스 등록 컴포넌트 { public 입력: any; public 생성자(비공개 http: Http, 비공개 라우터: 라우터) { 이.입력 = { "이름": "", "성": "", "이메일": "", "비밀번호": "" }; } public 등록() { 만약(이.입력.이메일 && 이.입력.비밀번호) { let 헤더 = new 헤더({ "콘텐츠 유형": "application/json" }); let 옵션 = new 요청 옵션({ 헤더: 헤더 }); 이.http.post("http://localhost:3000/account", JSON.문자열화(이.입력), 옵션) .지도(결과 => 결과.json()) .구독(결과 => { 이.라우터.탐색(["/로그인"]); }); } } } |
다시 말하지만, 위 코드에서 이전 예제와 비교하여 변경한 유일한 사항은 CSS 제거와 moduleId
추가.
크로스 플랫폼 웹 및 모바일 애플리케이션을 만드는 데 있어 나쁘지 않죠?
TypeScript 로직을 구동하는 UI의 HTML은 app/register/register.component.html 파일은 다음과 같습니다:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<액션바 title="{N} 프로필 저장소"></액션바> <스택 레이아웃 클래스="form"> <스택 레이아웃 클래스="입력 필드"> <라벨 텍스트="이름" 클래스="레이블 글꼴-가중-볼드 M-B-5"></라벨> <텍스트 필드 클래스="입력" [(ngModel)]="입력.이름" 자동 대문자화 유형="none"></텍스트 필드> <스택 레이아웃 클래스="hr-light"></스택 레이아웃> </스택 레이아웃> <스택 레이아웃 클래스="입력 필드"> <라벨 텍스트="성" 클래스="레이블 글꼴-가중-볼드 M-B-5"></라벨> <텍스트 필드 클래스="입력" [(ngModel)]="입력.성" 자동 대문자화 유형="none"></텍스트 필드> <스택 레이아웃 클래스="hr-light"></스택 레이아웃> </스택 레이아웃> <스택 레이아웃 클래스="입력 필드"> <라벨 텍스트="이메일" 클래스="레이블 글꼴-가중-볼드 M-B-5"></라벨> <텍스트 필드 클래스="입력" [(ngModel)]="input.email" 자동 대문자화 유형="none"></텍스트 필드> <스택 레이아웃 클래스="hr-light"></스택 레이아웃> </스택 레이아웃> <스택 레이아웃 클래스="입력 필드"> <라벨 텍스트="비밀번호" 클래스="레이블 글꼴-가중-볼드 M-B-5"></라벨> <텍스트 필드 클래스="입력" 보안="true" [(ngModel)]="입력.비밀번호" 자동 대문자화 유형="none"></텍스트 필드> <스택 레이아웃 클래스="hr-light"></스택 레이아웃> </스택 레이아웃> <스택 레이아웃 클래스="입력 필드"> <버튼 클래스="btn btn-primary" 텍스트="등록" (탭)="register()"></버튼> </스택 레이아웃> <스택 레이아웃 클래스="입력 필드"> <라벨 텍스트="여기에서 로그인하세요." [nsRouterLink]="['/로그인']" 클래스="텍스트 중심"></라벨> </스택 레이아웃> </스택 레이아웃> |
마지막 두 가지 구성 요소는 우리가 이미 경험하고 있는 것과 다르지 않을 것입니다.
프로젝트의 앱/블로그/블로그.컴포넌트.ts 파일을 열고 다음 TypeScript 코드를 포함합니다:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
가져오기 { 구성 요소, OnInit } 에서 '@angular/core'; 가져오기 { Http, 헤더, 요청 옵션 } 에서 "@angular/http"; 가져오기 { 라우터, 활성화된 경로 } 에서 "@각/라우터"; 가져오기 { 위치 } 에서 "@각형/공통"; 가져오기 "rxjs/Rx"; @구성 요소({ moduleId: 모듈.id, 선택기: 'app-blogs', templateUl: './blogs.component.html' }) 내보내기 클래스 블로그 컴포넌트 구현 OnInit { 비공개 sid: 문자열; public 항목: 배열<any>; public 생성자(비공개 http: Http, 비공개 라우터: 라우터, 비공개 경로: 활성화된 경로, 비공개 위치: 위치) { 이.항목 = []; } public ngOnInit() { 이.위치.구독(() => { let 헤더 = new 헤더({ "권한 부여": "무기명 " + 이.sid }); let 옵션 = new 요청 옵션({ 헤더: 헤더 }); 이.http.get("http://localhost:3000/blogs", 옵션) .지도(결과 => 결과.json()) .구독(결과 => { 이.항목 = 결과; }); }); 이.경로.쿼리 매개변수.구독(매개변수 => { 이.sid = 매개변수["sid"]; let 헤더 = new 헤더({ "권한 부여": "무기명 " + 매개변수["sid"] }); let 옵션 = new 요청 옵션({ 헤더: 헤더 }); 이.http.get("http://localhost:3000/blogs", 옵션) .지도(결과 => 결과.json()) .구독(결과 => { 이.항목 = 결과; }); }); } public create() { 이.라우터.탐색(["/blog"], { "queryParams": { "sid": 이.sid } }); } } |
앞서 언급한 두 가지를 제외하고는 위에서 볼 수 있는 변경 사항이 없으므로 페이지의 HTML로 이동해도 됩니다.
프로젝트의 앱/블로그/블로그.컴포넌트.html 파일을 열고 다음 HTML 마크업을 포함합니다:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<액션바 title="{N} 프로필 저장소"> <액션 항목 텍스트="New" ios.위치="오른쪽" (탭)="create()"></액션 항목> <탐색 버튼 텍스트="뒤로"></탐색 버튼> </액션바> <그리드 레이아웃 행="*, 자동" 열="*"> <ListView [항목]="항목" 클래스="list-group" 행="0" col="0"> <ng-템플릿 let-항목="item"> <스택 레이아웃 클래스="목록-그룹-아이템"> <라벨 텍스트="{{ entry.title }}" 클래스="h2"></라벨> <라벨 텍스트="{{ entry.content }}"></라벨> </스택 레이아웃> </ng-템플릿> </ListView> <스택 레이아웃 행="1" col="0" 패딩="10" 배경색="#F0F0F0"> <라벨 텍스트="로그아웃" [nsRouterLink]="['/로그인']"></라벨> </스택 레이아웃> </그리드 레이아웃> |
프로필 스토어 API와 웹 프론트엔드가 제공하는 최종 구성 요소로 이 애플리케이션을 마무리해 보겠습니다.
프로젝트의 앱/블로그/블로그.컴포넌트.ts 파일을 만들고 이 파일을 포함하세요:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
가져오기 { 구성 요소, OnInit } 에서 '@angular/core'; 가져오기 { Http, 헤더, 요청 옵션 } 에서 "@angular/http"; 가져오기 { 활성화된 경로 } 에서 "@각/라우터"; 가져오기 { 위치 } 에서 "@각형/공통"; 가져오기 "rxjs/Rx"; @구성 요소({ moduleId: 모듈.id, 선택기: 'app-blog', templateUl: './blog.component.html' }) 내보내기 클래스 블로그 컴포넌트 구현 OnInit { 비공개 sid: 문자열; public 입력: any; public 생성자(비공개 http: Http, 비공개 경로: 활성화된 경로, 비공개 위치: 위치) { 이.입력 = { "title": "", "content": "" }; } public ngOnInit() { 이.경로.쿼리 매개변수.구독(매개변수 => { 이.sid = 매개변수["sid"]; }); } public 저장() { 만약(이.입력.title && 이.입력.콘텐츠) { let 헤더 = new 헤더({ "콘텐츠 유형": "application/json", "권한 부여": "무기명 " + 이.sid }); let 옵션 = new 요청 옵션({ 헤더: 헤더 }); 이.http.post("http://localhost:3000/blog", JSON.문자열화(이.입력), 옵션) .지도(결과 => 결과.json()) .구독(결과 => { 이.위치.뒤로(); }); } } } |
CSS 파일을 복사하지 않은 경우 잊지 말고 컴포넌트
블록에서 볼 수 있습니다.
이 타입스크립트와 함께 사용할 HTML UI는 프로젝트의 앱/블로그/블로그 컴포넌트.html 파일을 열면 다음과 같이 표시됩니다:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<액션바 title="{N} 프로필 저장소"> <탐색 버튼 텍스트="뒤로"></탐색 버튼> </액션바> <스택 레이아웃 클래스="form"> <스택 레이아웃 클래스="입력 필드"> <라벨 텍스트="제목" 클래스="레이블 글꼴-가중-볼드 M-B-5"></라벨> <텍스트 필드 클래스="입력" [(ngModel)]="input.title" 자동 대문자화 유형="none"></텍스트 필드> <스택 레이아웃 클래스="hr-light"></스택 레이아웃> </스택 레이아웃> <스택 레이아웃 클래스="입력 필드"> <라벨 텍스트="콘텐츠" 클래스="레이블 글꼴-가중-볼드 M-B-5"></라벨> <텍스트 보기 클래스="입력" [(ngModel)]="input.content" 자동 대문자화 유형="none"></텍스트 보기> <스택 레이아웃 클래스="hr-light"></스택 레이아웃> </스택 레이아웃> <스택 레이아웃 클래스="입력 필드"> <버튼 클래스="btn btn-primary" 텍스트="저장" (탭)="save()"></버튼> </스택 레이아웃> </스택 레이아웃> |
지금 이 순간에도 이 모든 NativeScript XML 마크업에 대해 머리를 긁적거리고 있을 것입니다. Angular가 UI와 함께 작동하는 방식은 변경되지 않았지만 NativeScript 마크업에 대해 자세히 알고 싶다면 공식 문서. 에 익숙해지세요. 스택 레이아웃
, 그리드 레이아웃
및 개별 UI 컴포넌트 태그.
한데 모으기
NativeScript용 Angular 컴포넌트를 모두 만들었지만 Angular 라우터를 통해 통합하지는 않았습니다.
이 가이드의 웹 버전에서는 경로 정보가 app.module.ts 파일에 저장합니다. 그렇게 할 수는 있지만, 네이티브스크립트는 이를 별도의 파일로 분리했습니다.
프로젝트의 앱/앱.라우팅.ts 파일을 열고 다음을 포함하세요:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
가져오기 { NgModule } 에서 "@angular/core"; 가져오기 { 네이티브스크립트 라우터 모듈 } 에서 "네이티브스크립트-각/라우터"; 가져오기 { 경로 } 에서 "@각/라우터"; 가져오기 { 로그인 컴포넌트 } 에서 "./로그인/로그인.컴포넌트"; 가져오기 { 등록 컴포넌트 } 에서 "./register/register.component"; 가져오기 { 블로그 컴포넌트 } 에서 "./blogs/blogs.component"; 가져오기 { 블로그 컴포넌트 } 에서 "./blog/blog.component"; const 경로: 경로 = [ { 경로: "", redirectTo: "/로그인", 경로 일치: "full" }, { 경로: "로그인", 컴포넌트: 로그인 컴포넌트 }, { 경로: "등록", 컴포넌트: 등록 컴포넌트 }, { 경로: "블로그", 컴포넌트: 블로그 컴포넌트 }, { 경로: "blog", 컴포넌트: 블로그 컴포넌트 } ]; @NgModule({ 수입: [네이티브스크립트 라우터 모듈.forRoot(경로)], 수출: [네이티브스크립트 라우터 모듈] }) 내보내기 클래스 앱 라우팅 모듈 { } |
위의 코드 중 상당수는 새 프로젝트 템플릿과 함께 제공되었습니다. 각 컴포넌트를 가져와서 경로를 만들었습니다.
마찬가지로 컴포넌트는 프로젝트의 앱/앱.모듈.ts 파일을 만듭니다. 이 파일을 열고 다음을 포함합니다:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
가져오기 { NgModule, NO_ERRORS_SCHEMA } 에서 "@angular/core"; 가져오기 { 네이티브 스크립트 모듈 } 에서 "nativescript-angular/nativescript.module"; 가져오기 { 앱 라우팅 모듈 } 에서 "./app.routing"; 가져오기 { NativeScriptHttpModule } 에서 "네이티브스크립트-각형/http"; 가져오기 { 네이티브 스크립트 양식 모듈 } 에서 "네이티브스크립트-각형/양식"; 가져오기 { 앱 컴포넌트 } 에서 "./app.component"; 가져오기 { 로그인 컴포넌트 } 에서 "./로그인/로그인.컴포넌트"; 가져오기 { 등록 컴포넌트 } 에서 "./register/register.component"; 가져오기 { 블로그 컴포넌트 } 에서 "./blogs/blogs.component"; 가져오기 { 블로그 컴포넌트 } 에서 "./blog/blog.component"; @NgModule({ 부트스트랩: [ 앱 컴포넌트 ], 수입: [ 네이티브 스크립트 모듈, 앱 라우팅 모듈, NativeScriptHttpModule, 네이티브 스크립트 양식 모듈 ], 선언: [ 앱 컴포넌트, 로그인 컴포넌트, 등록 컴포넌트, 블로그 컴포넌트, 블로그 컴포넌트 ], 공급자: [], 스키마: [ NO_ERRORS_SCHEMA ] }) 내보내기 클래스 앱 모듈 { } |
각 컴포넌트를 가져와서 추가하는 것 외에도 선언
배열과 같은 몇 가지 모듈도 가져왔습니다. NativeScriptHttpModule
그리고 네이티브 스크립트 양식 모듈
. 순수 각도에서는 이를 HttpModule
그리고 폼 모듈
.
이론적으로는 애플리케이션을 바로 사용할 수 있습니다.
iOS용 앱 전송 보안 문제(ATS) 해결하기
Node.js 및 Couchbase API가 로컬에서 실행되고 있기 때문에 HTTPS가 아닌 HTTP를 사용하고 있습니다. iOS는 HTTP 리소스에 액세스하려고 하면 오류를 발생시킵니다.
이 문제는 ATS 정책을 추가하면 쉽게 해결할 수 있습니다.
프로젝트의 앱/앱_자원/iOS/정보 plist 를 열고 다른 XML과 함께 다음을 추가합니다:
1 2 3 4 |
<dict> <키>NSAllowsArbitraryLoads</키> <true /> </dict> |
위는 기본적으로 모든 HTTP 엔드포인트를 화이트리스트에 추가합니다. 프로덕션 환경에서는 안전하지 않지만 테스트 환경에서는 안전합니다.
네이티브스크립트 애플리케이션의 ATS에 대한 자세한 내용은 이전에 작성한 글에서 확인할 수 있습니다, 네이티브스크립트에서 iOS 9 앱 전송 보안 문제 해결.
결론
방금 웹 클라이언트 프런트엔드를 NativeScript와 Angular를 사용하여 모바일로 변환하는 것이 얼마나 쉬운지 보셨을 것입니다. 사용자 프로필 스토어 예제는 순식간에 자바스크립트 스택을 사용하는 전체 스택 예제가 되었습니다. 다음과 같은 Node.js가 있었습니다. 카우치베이스 서버 백엔드, Angular 웹 프론트엔드, NativeScript 모바일 프론트엔드를 지원합니다.
다음 단계 또는 옵션은 API에 대한 HTTP 호출이 아닌 Couchbase Mobile 컴포넌트를 사용하는 것입니다.
안녕하세요,
이 코드를 실행하려고 했지만 POST가 작동하지 않습니다. 등록하려고 할 때 아래 오류가 발생합니다. 온라인에서 다음을 확인했습니다. http://localhost 가 안드로이드 SDK에서 작동하지 않습니다. IP 주소 10.0.2.2와 127.0.0.1을 모두 사용하지만 여전히 동일한 오류가 발생합니다.
JS: 오류 상태의 응답: 200 URL: null