Tag Archives: ngApp

AngularJS PhoneCat 튜토리얼 앱 – 정적/동적 템플릿

angularjs-large

정적 템플릿

Angular가 표준 HTML을 어떻게 개선시키는지 설명하기 위해, 순수한 정적 HTML 페이지를 만들고 Angular가 데이터 셋의 결과를 이용하여 이 정적 HTML과 동일한 결과물을 동적으로 만들어내는 템플릿으로 바꿔보는 과정을 진행해 보겠습니다. 여기서는 HTML 페이지에 두개의 휴대폰 기본 정보를 추가해보겠습니다.

angular-phonecat 프로젝트의 저장소를 과정1으로 초기화 하겠습니다.

$ git checkout -f step-1

정적 템플릿을 사용한 app/index.html 의 내용은 다음과 같습니다.

<ul>
  <li>
    <span>Nexus S</span>
    <p>
      Fast just got faster with Nexus S.
    </p>
  </li>
  <li>
    <span>Motorola XOOM™ with Wi-Fi</span>
    <p>
      The Next, Next Generation tablet.
    </p>
  </li>
</ul>

실험

index.html 파일에 다음과 같은 정적 HTML 요소를 추가해 봅시다.

<p>Total number of phones: 2</p>

동적 템플릿

이제, 웹 페이지를 AngularJS를 이용하여 동적으로 변경해볼 시간입니다. 우리는 또한 우리가 추가하려는 컨트롤러의 코드를 검증하는 테스트도 추가할 것입니다.

어플리케이션 코드에 대한 구조적 방법론은 여러가지가 있습니다만 Angular 어플리케이션에서는 Model-View-Controller(MVC) 디자인 패턴을 사용하여 코드를 분리시키고 신경써야 할 부분을 분리할 것을 권장합니다. 이를 염두에 두고 우리의 어플리케이션에 모델, 뷰, 컨트롤러를 약간의 Angular 및 자바스크립트를 사용하여 구현해보도록 하겠습니다. 여기서는 3개의 휴대폰 정보를 데이터를 이용하여 동적으로 생성해 보겠습니다.

angular-phonecat 프로젝트의 저장소를 과정 2로 초기화 하겠습니다.

$ git checkout -f step-2

뷰와 템플릿

Angular에서는 뷰는 HTML 템플릿을 통해 모델을 투영한 결과물입니다. 이 말 뜻은 언제든지 모델이 변경되면 Angular는 적절한 바인딩 지점을 새로 고침 하면서 결과적으로 뷰를 업데이트 하게된다는 뜻입니다. 뷰는 Angular로 하여금 이 템플릿을 통해 만들어집니다.

<html ng-app="phonecatApp">
<head>
  ...
  <script src="bower_components/angular/angular.js"></script>
  <script src="app.js"></script>
</head>
<body ng-controller="PhoneListController">

  <ul>
    <li ng-repeat="phone in phones">
      <span>{{phone.name}}</span>
      <p>{{phone.snippet}}</p>
    </li>
  </ul>

</body>
</html>

우리는 여기서 하드코딩 되어있는 휴대폰 리스트를 ngRepeat 지시자와 두개의 Angular 표현식을 사용하도록 변경하였습니다.

  • <li> 태그에 붙어있는 ng-repeat=”phone in phones” 속성은 Angular의 반복 지시자입니다. 이 반복지시자는 Angular로 하여금 리스트에 있는 각각의 휴대폰 정보마다 <li> 태그를 템플릿으로 사용하여 앨리먼트들을 만들도록 해줍니다.
  • 중첩 중괄호로 표현된 {{phone.name}} 과 {{phone.snippet}}은 표현식의 값으로 치환될 것입니다.

우리는 또한 <body> 태그에 ngController 라고 불리는 새로운 지시자를 사용하여 PhoneListController 컨트롤러를 추가하였습니다. 여기서 다음과 같은 점을 알 수 있습니다.

  • PhoneListController는 <body> 엘리먼트를 (포함하여) 하위 DOM 트리를 담당하게 됩니다.
  • 중첩 중괄호로 표현되어있는 표현식은 우리의 PhoneListController 컨트롤러내에서 초기화 되는 어플리케이션 모델을 가르키는 바인딩을 의미합니다.

우리는 여기서 ng-app=”phonecatApp” 이라는 지시자를 사용하여 Angular Module이 phonecatApp 이라는 이름을 가진 우리의 모듈을 로드하도록 특정하였습니다. 이 모듈은 PhoneListController를 포함합니다.

모델과 컨트롤러

데이터 모델(객체 문자 표기법으로 구성된 폰 정보를 담은 간단한 배열)은 PhoneListController 컨트롤러 내에서 인스턴스화 됩니다. 이 컨트롤러는 $scope 파라미터를 갖는 단순한 생성자입니다.

// Define the `phonecatApp` module
var phonecatApp = angular.module('phonecatApp', []);

// Define the `PhoneListController` controller on the `phonecatApp` module
phonecatApp.controller('PhoneListController', function PhoneListController($scope) {
  $scope.phones = [
    {
      name: 'Nexus S',
      snippet: 'Fast just got faster with Nexus S.'
    }, {
      name: 'Motorola XOOM™ with Wi-Fi',
      snippet: 'The Next, Next Generation tablet.'
    }, {
      name: 'MOTOROLA XOOM™',
      snippet: 'The Next, Next Generation tablet.'
    }
  ];
});

여기서 우리는 PhoneListController라고 불리는 컨트롤러를 선언하였고 그것을 phonecatApp이라는 이름의 Angular 모듈에 등록하였습니다. 여기서 우리는 어플리케이션이 부트스트래핑 하는 과정에서 ngApp 지시자가 phonecatApp 모듈 이름을 특정하였음을 알 수 있습니다.

아직 컨트롤러는 많은것을 하고 있지는 않지만 매우 중요한 역할을 하고 있음을 알 수 있습니다. 우리의 데이터 모델을 컨텍스트로 제공함으로써 컨트롤러는 모델과 뷰 사이에 데이터 바인딩을 확립할 수 있도록 허용 해 줍니다. 우리는 프레젠테이션, 데이터, 논리 컴포넌트를 다음과 같은 방법으로 연결하였습니다.

  • <body> 태그에 위치한 ngController 지시자는 우리의 컨트롤러인 PhoneListController를 가르킵니다. (이 컨트롤러의 선언은 app.js 자바스크립트 파일안에 위치하고 있습니다.)
  • PhoneListController 컨트롤러는 휴대폰 데이터를 우리의 컨트롤러 함수를 통해 주입된 $scope에 붙이는 작업을 수행합니다. 이 scope는 어플리케이션이 선언 되었을 때 만들어진 루트 스코프의 하위 영역에 위치합니다. 이 컨트롤러의 스코프(scope)는 <body ng-controller=”PhoneListController”> 하위의 모든 바인딩에서 사용할 수 있습니다.

스코프

Angular 에서의 스코프라는 개념은 중요합니다. 스코프는 템플릿, 모델, 컨트롤러가 모두 함께 동작하도록 하는 접착재로 간주될 수 있습니다. Angular는 스코프를 사용하여 템플릿, 데이터 모델, 컨트롤러에 포함된 정보를 모델과 뷰가 분리된 상태로 공유하도록 합니다. 하지만 이 모든것들이 동기로 수행됩니다.

모델에 어떤 변화가 발생하면 이것은 뷰에 반영됩니다. 또한 어떤 뷰의 변화가 발생하면 모델에 반영하게 됩니다.

Angular Scope에 대해서 더 알아보고 싶다면 Angular Scope Documentation을 확인하시기 바랍니다.

angularjs-phonecat-tutorial-02

Angular 스코프는 구조적으로 어플리케이션의 루트 스코프까지 도달 가능한 그들의 부모 스코프를 상속받도록 되어있습니다. 결과적으로 스코프에 직접 값을 할당하는것은 페이지의 다른 파트들간에 데이터를 공유하거나 상호작용하는 어플리케이션을 만드는것을 쉽게 해줍니다. 이러한 접근 방법은 프로토타입이나 작은 어플리케이션에는 효과가 있지만 우리의 데이터 모델이 강한 결합을 야기하고 우리의 데이터 모델의 변화에 대한 논리적인 어려움을 야기합니다.

다음 과정에서 우리는 “packaging”을 통해 우리의 코드를 좀 더 정리하고 프레젠테이션 로직을 격리하고, 재사용 가능한 형태의 컴포넌트로 만드는 방법을 배워볼 것입니다.

테스트

“Angular 방식”으로 뷰로부터 컨트롤러를 분리하는것은 개발과정에서도 테스트 코드를 만드는것을 쉽게 해줍니다. 우리의 컨트롤러가 글로벌 네임스페이스 위에서 동작하고 있다면 우리는 다음과 같이 간단하게 목업 스코프 오브젝으를 만들어서 테스트 해볼 수 있습니다.

describe('PhoneListController', function() {

  it('should create a `phones` model with 3 phones', function() {
    var scope = {};
    var ctrl = new PhoneListController(scope);

    expect(scope.phones.length).toBe(3);
  });

});

이 테스트는 PhoneListController를 인스턴스화 하고 스코프에 휴대폰 배열이 3개의 레코드를 갖는지 검증합니다. 이 예제는 Angular에서 유닛 테스트 코드를 만드는것이 얼마나 쉬운지를 보여줍니다. 소프트웨어 개발에서 테스팅은 정말 중요한 부분이므로 이러한 장점으로 인해 개발자들에게 테스트를 만드는것을 권장할 수 있습니다.

글로벌 컨트롤러가 아닌경우의 테스트

실전에서는 컨트롤러 함수들을 글로벌 네임스페이스에 선언하고 싶지 않을 수 있습니다. 대신에 phonecatApp 모듈에 생성자 함수를 등록하는것을 볼 수 있습니다.

이경우 Angular에서는 컨트롤러를 이름으로 받아올 수 있는 $controller 서비스를 제공합니다. 다음은 $contoller를 사용하는 같은 테스트입니다.

describe('PhoneListController', function() {

  beforeEach(module('phonecatApp'));

  it('should create a `phones` model with 3 phones', inject(function($controller) {
    var scope = {};
    var ctrl = $controller('PhoneListController', {$scope: scope});

    expect(scope.phones.length).toBe(3);
  }));

});

위의 코드는 다음과 같은 의미를 가지고 있습니다.

  • 각각의 테스트를 수행하기전에 Angular에게 phonecatApp 모듈을 로드할 것을 알립니다.
  • Angular에게 $controller 서비스를 우리의 테스트 함수에 주입하도록 요청합니다.
  • $controller 를 사용하여 PhoneListController의 인스턴스를 생성합니다.
  • 이 인스턴스를 통해 우리는 3개의 레코드를 갖는 스코프가 정상적으로 만들어지는지 검증합니다.

이전에 이미 언급했듯이 유닛 테스트 파일 (specs)들은 어플리케이션 코드들과 함께 보관되어야 합니다. 우리는 어플리케이션 코드의 파일 이름과 명확히 구별이 가능하도록 추가적인 확장자를 추가하여 이 유닛 테스트 파일을 보관하는것이 좋습니다. 테스트 파일은 여전히 일반적인 자바스크립트 파일이므로 .js 확장자를 유지하는것은 좋겠다고 생각됩니다.

이 튜토리얼에서는 우리는 .spec 접미사를 파일명에 추가하였습니다. 그 결과 something.js 파일에 대응되는 테스트 파일은 something.spec.js가 됩니다. (다른 일반적인 컨벤션은 _spec 또는 _test 접미사를 붙이는 방법입니다)

쓰기 및 실행 테스트

수많은 Angular 개발자들은 쓰기 테스트를 할 때 Jasmin의 Behavior-Driven Development (BDD) 프레임워크를 선호합니다. 비록 Angular는 Jasmin 사용을 필요로하지 않지만 우리는 이 튜토리얼의 테스트에서 Jasmin v2.4를 사용하여 모든 테스트를 작성하였습니다. Jasmin에 대해서 더 많은 것을 알아보고 싶다면 Jasmin 홈페이지를 방문하시기 바랍니다.

angular-seed 프로젝트에서는 Karma를 이용하여 유닛테스트를 진행하도록 설정되어있습니다. 만약 Karma를 실행하는데에 필요한 플러그인이 설치되어있다고 확인하지 못할 경우 npm install 명령을 실행하여 설치할 수 있습니다.

테스트를 실행하고, 그리고 파일들의 변화에 따라 테스트를 재시도 하도록 npm test 를 실행합니다.

  • Karma는 크롬이나 파이어폭스 브라우저의 인스턴스를 자동으로 시작합니다. 그들이 백그라운드에서 실행되도록 그냥 무시하면 됩니다. Karma는 이 브라우저들을 이용하여 테스트를 실행합니다.
  • 만약 당신의 머신에 하나의 브라우저만 설치되어있을 경우 테스트를 수행하기전에 Karma 설정파일인 karma.conf.js 파일을 수정해 주십시오. 이 설정파일은 프로젝트의 루트 디렉토리에 위치하고 있으며 browser 항목을 수정하면 됩니다.
  • 당신은 터미널을 통해 다음과 같은 결과를 확인할 수 있습니다. 테스트 수행을 성공하였네요!

INFO [karma]: Karma server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
INFO [Chrome 49.0]: Connected on socket … with id …
Chrome 49.0: Executed 1 of 1 SUCCESS (0.05 secs / 0.04 secs)

  • 테스트를 재실행하기 위해서는 단지 소스 또는 테스트 .js 파일에 어떤 변화가 발생하면 됩니다. Karma는 변화가 발생했음을 알리고 당신을 위해서 테스트를 재실행할것입니다. 정말 멋지죠?

Karma가 브라우저를 열었을 때 이를 최소화 하지 마십시오. 특정 OS에서는 최소화된 브라우저에 최소한의 메모리를 할당하여 Karma 테스트가 엄청나게 느려지게 만드는 문제가 있습니다.

실험

index.html 파일에 다른 바인딩을 추가해 봅시다.

<p>Total number of phones: {{phones.length}}</p>

컨트롤러에 새로운 모델 프로퍼티를 만들고 그것이 템플릿에 바인딩 되도록 해봅시다.

// In controller
$scope.name = 'world';

// In template
<p>Hello, {{name}}!</p>

app/app.spec.js 안의 컨트롤러 유닛 테스트를 위의 변화를 반영하도록 수정해봅시다.

expect(scope.name).toBe('world');

index.html 안에 간단한 테이블을 출력하는 반복 지시자를 사용해 봅시다.

<table>
  <tr><th>Row number</th></tr>
  <tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i}}</td></tr>
</table>

expect(scope.phones.length).toBe(3) 대신에 toBe(4)를 사용하여 유닛 테스트가 실패하도록 해봅시다.

참고 :
https://docs.angularjs.org/tutorial/step_01
https://docs.angularjs.org/tutorial/step_02

AngularJS PhoneCat 튜토리얼 앱 – 부트스트래핑

angularjs-large

튜토리얼중에 이 단계에서는 AngularJS PhoneCat 어플리케이션의 가장 중요한 소스코드들에 대해 알아보게 됩니다. 또한 당신은 angular-seed 를 번들로 포함하고 있는 개발중인 서버를 시작하고 그것을 브라우저에서 실행해보는것을 배울 수 있게 됩니다.

이글을 계속해서 읽어보기전에 앞서 당신은 먼저 개발환경을 세팅하고 필요한 의존성 있는 모듈들을 설치해야 합니다. 이러한 내용은 이전에 작성한 글 [AngularJS PhoneCat 튜토리얼 앱 – 개요]를 확인하시기 바랍니다.

먼저 기존에 Github 저장소에서 클론 받은 angular-phonecat 디렉토리에서 다음의 명령을 실행합니다.

$ git checkout -f step-0

이 명령은 당신의 워크스페이스를 튜토리얼의 스텝 0 상태로 초기화 해줍니다.

앞으로 이 튜토리얼을 진행함에 따라 당신은 해당 튜토리얼의 순번에 맞는 숫자를 가진 태그로 변경하는 작업을 반복하게 될 것입니다. 이 과정은 당신이 이 로컬 저장소에서 작업한 내용이 사라지는 변화를 야기할 수 있습니다.

이전에 다음의 명령을 실행한 적이 없다면 다음의 명령을 실행하여 의존성 있는 모듈들을 설치하도록 합니다.

$ npm install

어플리케이션을 실행하여 브라우저에서 직접 확인해보고 싶다면 새로운 별개의 터미널/커맨드라인창을 실행하여 npm start 명령을 실행하여 웹서버를 구동하시기 바랍니다. 웹서버가 정상적으로 실행되었다면 새로운 브라우저 창을 열어 http://localhost:8000/index.html 를 열어서 확인해볼 수 있습니다.

만약에 이미 master 브랜치 상태에서 앱을 구동하여 웹브라우저에서 실행해 보았다면 이전의 마스터 버전이 캐시되어 계속해서 보여질 수 있습니다. 이 경우 웹브라우저에서 단지 새로고침을 하는것으로 변경된 웹페이지를 확인할 수 있습니다.

이제 웹브라우저에서 페이지의 내용을 확인할 수 있습니다만 별로 흥미를 끄는 결과는 아닐것입니다. 하지만 괜찮습니다(?). 이 HTML 페이지는 “Nothing here yet!” 를 출력할 것이며 아래와 같은 방법으로 페이지가 만들어지게 됩니다. 여기서 앞으로 우리가 진행하기 위해 알아야 하는 몇가지 핵심이 되는 Angular 엘리먼트를 확인할 수 있습니다.

<!doctype html>
<html lang="en" ng-app>
  <head>
    <meta charset="utf-8">
    <title>My HTML File</title>
    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />
    <script src="bower_components/angular/angular.js"></script>
  </head>
  <body>

    <p>Nothing here {{'yet' + '!'}}</p>

  </body>
</html>

이 코드가 무엇을 하는가?

ng-app 속성

<html ng-app>

이 ng-app 속성은 ngApp 이라는 이름의 Angular 지시자를 나타냅니다. (Angular는 이러한 속성의 표기법으로 케밥케이스(kebab-case, 중간에 하이픈-을 사용하는 방법)를 사용하며 여기에 대응하는 구현부의 지시자는 카멜케이스(camelCase, 중간에 대문자를 사용하는 방법)를 사용합니다. 이 지시자는 HTML 앨리먼트가 Angular 로 하여금 우리의 어플리케이션의 루트 어플리케이션으로 인식하도록 해줍니다. 이 지시자는 개발자로 하여금 Angular가 HTML 페이지 전체 혹은 그 일부분만을 AngularJS 어플리케이션으로 다룰지를 알려주는 역할을 합니다.

ngApp 지시자에 대해 서 많은것을 알아보고 싶다면 API 레퍼런스문서를 참고하시기 바랍니다.

angular.js 스크팁트 태그

<script src="bower_components/angular/angular.js"></script>

이 코드는 이 지시자가 포함된 HTML 페이지가 모두 다운로드 되었을 때 실행되어야 하는 콜백을 등록하는 작업을 수행하는 angular.js 스크립트를 다운로드 하도록 하는 코드입니다. 이 콜백이 실행되면 Angular는 ngApp 지시자를 검색합니다. Angular가 이 지시자를 찾는다면 이 ngApp 지시자가 정의된 DOM 엘리먼트를 루트로 하는 어플리케이션을 초기 구동(Bootstrap) 하게 됩니다.

어플리케이션을 부트스트래핑 하는 과정에 대해 더 많은것을 알아보고 싶다면 개발자 가이드의 Bootstrap 섹션을 참고하시기 바랍니다.

중복 중괄호 사용을 통한 표현식 바인딩

Nothing here {{'yet' + '!'}}

이 데모에서 Angular 템플릿에서 사용가능한 두가지 핵심 기능을 알 수 있습니다.

  • 중복 중괄호 사용을 통한 바인딩: {{ }}
  • 이 바인딩 내부에서 사용가능한 간단한 표현식: ‘yet’ + ‘!’

이 바인딩은 Angular가 내부의 표현식을 해석하고 그 결과를 바인딩이 위치한 DOM 의 일부에 삽입하는 역할을 합니다. 이 튜토리얼을 계속 진행하면서 알 수 있겠지만 한번의 삽입뿐만 아니라 이 표현식의 결과값이 바뀔때마다 바인딩은 효율적이고 지속적으로 그 결과를 반영합니다.

Angular 표현식은 Angular에 의해 해석 가능한 형태의 자바스크립트 코드와 같은 형태를 가지고 있으며 이때에 Angular는 글로벌 컨텍스트(window) 스코프 안이 아닌 현재의 모델 스코프의 컨텍스트안에서 동작을 하게 됩니다.

기대했듯이 이 템플릿이 Angular에 의해서 처리되면 다음과 같은 텍스트를 가진 HTML 페이지가 만들어집니다.

Nothing here yet!

부트스트래핑 Angular 어플리케이션

ngApp 지시자를 사용함으로 써 Angular 어플리케이션이 초기 구동하는것을 자동으로 실행되도록 하는것은 매우 쉽고 대부분의 상황에 적절합니다. 하지만 스크립트 로더를 사용하는 경우등의 고급 사용의 경우 명령형/수작업 을 통해 어플리케이션을 초기 구동할 수 있습니다.

부트스트랩 페이즈가 진행되는 동안 발생하는 중요한 3가지가 있습니다.

  • 의존성 주입을 위한 인젝터(Injector) 가 생성됩니다.
  • 이 인젝터는 우리 어플리케이션의 모델의 컨텍스트가 되어줄 루트 스코프를 생성합니다.
  • Angular는 ngApp 루트 앨리먼트로부터 시작하는 DOM을 “컴파일”하며 이 과정에서 발견된 지시자들과 바인딩을 처리합니다.

어플리케이션이 구동되면, 이후는 브라우저로부터 모델을 변화를 야기할 이벤트(마우스 클릭, 키 입력, HTTP 응답)를 기다리게 됩니다. 이러한 이벤트가 발생하면 Angular는 이 이벤트가 모델들중에 어떤 변화를 일으키는지를 찾아보고 변화를 찾았다면 Angular는 모든 영향을 끼칠 바인딩을 갱신함으로 써 뷰에 변화를 발생시킵니다.

우리 어플리케이션의 구조는 매우 단순합니다. 템플릿은 하나의 지시자와 하나의 정적 바인딩을 포함하고 있고 우리의 모델은 비어있습니다. 이제 이것을 변경해 봅시다!

angularjs-phonecat-tutorial-01

작업 디렉토리안의 이 모든 파일들은 무엇인가요?

당신의 작업 디렉토리 안의 대부분의 파일들은 일반적으로 새로운 AngularJS 프로젝트를 시작할때 일반적으로 사용되는 angular-seed 프로젝트로부터 온 것입니다. 이 기초 프로젝트는 AngularJS 프레임워크가 기본적으로 설정 되어있으며(Bower를 통해 app/bower_components/ 안에 생성) 일반적인 웹 어플리케이션을 개발을 하거나 테스트하는데에 필요로 하는 툴들을 포함하고 있습니다. (npm을 통해 실행)

이 프로젝트에서는 튜토리얼의 목적을 위해 angular-seed 프로젝트로부터 몇가지 수정이 있었습니다.

  • 예제 앱 삭제
  • 사용하지 않는 의존 모듈들 삭제
  • 스마트폰 이미지들을 /app/img/phones 디렉토리 안에 추가
  • 스마트폰 데이터 파일들을 /app/phones/ 디렉토리 안에 추가
  • Twitter Bootstrap 의존성을 bower.json 파일에 추가

실험

다음의 수학적 연산을 하는 새로운 표현식을 index.html에 추가해보고 결과를 확인해 봅시다.

<p>1 + 2 = {{1 + 2}}</p>

참고 : https://docs.angularjs.org/tutorial/step_00