[모바일]검색결과, 144건
- [Node.js] Node With IIS 2016.07.19 (1)
- [AngularJS] 폼(form)과 유효성 검사 2016.07.18
- [Node.js] CORS 설정(Cross Domain 요청 허용) 2016.07.15
- [AngularJS] REST API 통신(with Node.js) 2016.07.15
- [Node.js] socket.io를 활용한 웹채팅 구현 2016.07.15 (5)
- [AngularJS] Route 2016.07.14
- [AngularJS] Directive 2016.07.13
- [AngularJS] $scope과 controller-as 문법 2016.07.12
- [AngularJS] Module 2016.07.12
- [AngularJS] Two-way Data Binding 2016.07.10
Node.js가 콘솔기반의 자체 HTTP 서버를 제공해 준다고는 하지만...
80포트 공유, 멀티 도메인과 서브 도메인 설정, HTTP 요청 로그, 동시 접속자 및 요청 큐 대기 상황, 웹 팜 및 웹 가든 구성, 디렉티브 권한 설정 등등 기존 IIS나 Apache 기반 환경에서 자주 사용했던 웹 서버 관련 사항은 어떻게 해야 하는가?
그리고 기존 IIS와 같은 웹 서버 환경에 추가하여 동시에 운용하고자 하는 경우는?
실무에서는 HTTP 통신서버의 비즈니스 기능 외에 다양한 옵션 및 환경설정이 요구되는데, Node.js가 이 부분을 모두 수용할 수 있는가?
솔직히 아직 Node.js를 복잡한 실무환경에서 제대로 적용해 보지 않은터라, 자세히 알지 못한다.
다만, IIS와 같은 나름 검증된 웹 서버에 호스팅되어 서비스 되면 좋겠다는 생각을 해 보던 차, 다음의 글을 발견했다.
http://www.sysnet.pe.kr/2/0/1556
한번 설정해 봐야 겠다.
이글을 보는 누군가, 기존 IIS 서버등에서 사용했던 많은 웹 서버 기능들을 Node.js 자체 환경에서도 여전히 안정적으로 사용가능한지 말해 줄 사람이 있다면, 댓글을 부탁드립니다.
'모바일 > Javascript' 카테고리의 다른 글
[Webpack] 자바스크립 모듈 번들러, 웹팩(Webpack) (0) | 2017.01.12 |
---|---|
[Angular2] Angular2 선택을 위한 명분 (0) | 2016.12.19 |
[AngularJS] 폼(form)과 유효성 검사 (0) | 2016.07.18 |
[Node.js] CORS 설정(Cross Domain 요청 허용) (0) | 2016.07.15 |
[AngularJS] REST API 통신(with Node.js) (0) | 2016.07.15 |
AngularJS는 HTML 폼을 확장하여 입력요소에 자바스크립트 객체를 바인딩하고, 전체 폼 또는 개별 입력요소에 대한 유효성 검사를 추가할 수 있는 방법을 제공한다.
* 폼의 데이터 바인딩
- 폼의 입력요소와 AngularJS의 상호작용은 (뷰와 컨트롤러의 상호작용이 늘 그렇듯이) $scope 객체를 매개로 이뤄진다. 폼 요소와 $scope 객체의 관계를 확인하기 위해 다음과 같이 간단한 코드를 작성해 본다.
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
var app = angular.module("myApp",[]);
app.controller("myCtrl", function($scope){
$scope.checkScope = function(){
console.log($scope); //$scope객체를 console 로그에서 확인해 본다.
};
});
</script>
<body ng-app="myApp" ng-controller="myCtrl">
<div>
<p>Name: <input type="text" ng-model="name"></p>
<p ng-bind="name"></p>
<button ng-click="checkScope();">check</button>
</div>
</body>
</html>
input 박스에 ng-model 디렉티브를 선언하고, 바로 밑 <p> 요소에 ng-bind 디렉티브를 동일한 이름으로 선언하였다. (ng-bind 대신 '{{ name }} ' 형태의 표현식으로 해도 동일하게 동작한다)
코드를 실행해 보면, input 요소에 입력한 값들이 자동으로 <p> 요소에 반영되어 데이터가 같이 변경되는 것을 확인할 수 있다.
이와 같은 폼 요소에 대한 자동 바인딩 내부에는 $scope 객체가 존재한다.
코드에서는 명시적으로 $scope 객체에 name이라는 속성을 추가한 적은 없지만, ng-model 디렉티브로 인해 입력요소에 값이 입력되는 순간 $scope에 name라는 속성이 자동으로 추가된다.
콘솔로그를 통해 확인해 보자.
코드가 처음 브라우저에 로딩된 순간에는 $scope객체에 name이 속성을 찾을 수 없다. 그러나 input요소에 값을 입력하는 순간 자동으로 $scope객체에 name속성이 (입력 값과 함께) 자동으로 추가된다. 다음 그림은 콘솔로그를 통해 확인해 본 것이다.
결국 뷰와 컨트롤러의 데이터바인딩은 $scope 객체를 통해 이뤄지며, 이는 폼 요소에 대한 ng-model 디렉티브 설정을 통해 $scope객체의 속성과 연결되어 양방향 바인딩이 자동으로 이뤄지는 메커니즘이다.
* 폼의 입력요소
폼의 입력요소에는 다음과 같은 것들이 있다.
- input 엘리먼트: 텍스트박스, 체크박스, 라디오 버튼 등 입력요소
- select 엘리먼트: 여러개의 아이템 중 하나를 선택할 수 있는 입력요소
- textarea 엘리먼트: 여러 행의 글을 작성할 수 있는 입력요소
- button 엘리먼트: 클릭으로 명령을 전송할 수 있는 입력요소
* CheckBox
- 다음 코드는 체크박스에 대한 AngularJS의 임의구현이다. 첫 번째 체크박스의 ng-model 값을 두 번째 체크박스의 ng-checked 디렉티브로 연결하고 있다. 이렇게 하면 두 번째 체크박스는 첫 번째 체크박스의 선택여부에 동기화되어 선택/해제 된다.
그리고 ng-show 디렉티브는 첫 번째 체크박스의 선택여부에 따라 화면에 표시할 지 여부를 결정한다.
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body>
<div ng-app>
<form>
checkbox1: <input type="checkbox" ng-model="chk1">
<br />
checkbox2: <input type="checkbox" ng-checked=chk1">
</form>
<!-- checkbox가 선택된 경우 화면에 표시 -->
<h1 ng-show="chk1">checkbox1 is checked</h1>
</div>
</body>
</html>
* RadioButton
- 다음 코드는 라이오버튼에 대한 AngularJS의 임의구현이다. 라이브버턴 마다 ng-model을 설정하고 {{xxx}} 표현식으로 선택된 값을 출력한다. 그리고 ng-switch계열 디렉티브를 통해 조건식을 구현할 수 있다.
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body ng-app>
<form>
Pick a topic:
<input type="radio" ng-model="rdobtn" value="r1">Radio1
<input type="radio" ng-model="rdobtn" value="r2">Radio2
</form>
<div ng-switch="rdobtn">
<!-- 선택된 값 출력 -->
<h1>checked: {{rdobtn}}</h1>
<!-- 선택된 값에 대한 조건식 로직 -->
<div ng-switch-when="r1">
<h1>radio1 is checked</h1>
</div>
<div ng-switch-when="r2">
<h1>radio2 is checked</h1>
</div>
</div>
</body>
</html>
* SelectBox
- 라이브버튼과 거의 유사한 임의코드이다.
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body ng-app>
<form>
Select a topic:
<select ng-model="selbox">
<option value="">
<option value="v1">value1
<option value="v2">value2
</select>
</form>
<div ng-switch="selbox">
<!-- 선택된 값 출력 -->
<h1>selected: {{selbox}}</h1>
<div ng-switch-when="v1">
<h1>value1 is selected</h1>
</div>
<div ng-switch-when="v2">
<h1>value2 is selected</h1>
</div>
</div>
</body>
</html>
* AngularJS의 폼 확장
- AngularJS는 표준 HTML 폼 요소를 확장한 form 디렉티브와 폼의 상태를 추적하기 위한 FormController 컨트롤러의 인스턴스를 생성해 폼을 관리한다.
HTML페이지의 폼의 name 특성을 통해 폼이름을 지정하면 $scope객체에 지정된 이름으로 폼 객체가 추가되며 하위 폼 요소들도 주어진 이름으로 추가된 폼 객체의 하위 프로퍼티로 자동 추가된다. 이후 이들 이름을 기반으로 뷰 및 컨트롤러에서 폼 요소들을 참조하여 특정 작업을 수행할 수 있다.
폼 요소들이 지정된 name값을 기반으로 $scope 객체에 자동추가 된다는 것을 눈으로 확인해보자.
다음과 같은 입력요소를 하나 가지는 간단한 폼을 만든다. 주의할 점은 폼요소가 모두 로딩된 후 $scope 객체를 확인해야 하므로 <script> 요소를 </body> 직전에 위치 시켰다. (일반적으로 스크립트 태그는 이 위치에 두는 것을 권장하는데, 이는 DOM 구성이 스크립트 로딩과 실행으로 지연되지 않도록 하기 위함으로 모든 DOM이 구성되고 난 후 안정적으로 스크립트를 실행시키기 위함도 있다.)
<html lang="en">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="formCtrl">
<form name="myForm">
UserName:
<input type="text" name="myInput" ng-model="myInput"><br>
</form>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('formCtrl', function($scope) {
console.log($scope);
});
</script>
</body>
</html>
지금 상황에서 input 요소에 ng-model 디렉터리가 꼭 필요한 것은 아니지만, AngularJS는 ng-model 디렉티브가 추가된 입력요소에 한해서 $scope 객체에 등록시키기 때문에 추가한 것이다.
결과로 본 콘솔로그는 다음과 같다.
이렇게 폼 요소의 name 값을 기준으로 $scope객체에 자동추가 되기 때문에, 이후 컨트롤러나 뷰에서 해당 폼 요소의 이름으로 접근 가능한 것이다.
* 폼과 입력필드의 상태(폼 유효성 검사의 기반)
- AngularJS는 폼의 유효성 검사를 위해 폼과 입력요소의 상태값을 지속적으로 감시하고 관리한다.
다음은 각각 폼과 입력필드의 상태를 알 수 있는 참조변수를 보여준다.
|
|
사용자 반응에 따른 폼과 입력요소의 상태값 변화를 눈으로 확인해 보기 위해, 직전의 예제를 다음과 같이 조금 수정해 보자.
<html lang="en">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body>
<div ng-app>
<form name="myForm">
UserName:
<input type="text" name="myInput" ng-model="myInput" required><br>
</form>
<div>
<h1>Form States</h1>
Pristine: {{myForm.$pristine}} <br />
Dirty: {{myForm.$dirty}} <br />
Valid: {{myForm.$valid}} <br />
Invalid: {{myForm.$invalid}} <br />
Submitted: {{myForm.$submitted}} <br />
Error: {{myForm.$error}} <br />
<h1>Input States</h1>
Touched: {{myForm.myInput.$touched}} <br />
Untouched: {{myForm.myInput.$untouched}} <br />
Pristine: {{myForm.myInput.$pristine}} <br />
Dirty: {{myForm.myInput.$dirty}} <br />
Valid: {{myForm.myInput.$valid}} <br />
Invalid: {{myForm.myInput.$invalid}} <br />
Error: {{myForm.myInput.$error}} <br />
</div>
</div>
</body>
</html>
예제를 브라우저로 실행시키고, 입력요소에 값을 입력하거나 포커스를 이동시켜보자. 그러면 각 행위에 따라 폼의 상태값의 변경을 확인할 수 있을 것이다. 이와같은 폼 상태 변화는 AngularJS가 폼 유효성을 검사하는 기반으로 활용된다.
* 폼 초기화
- 폼을 최초 로딩된 상태로 초기화 시키는 것은, 최초값을 보관해 두었다가 다시 그 값으로 회귀 시켜 주면 된다. 다만 데이터 초기화와 AngularJS의 폼 유효성 검사를 위한 상태값까지 모두 초기화 시켜 주는 것이 좋다. 앞서 살펴본바와 같이 AngularJS는 폼 및 입력요소에 대한 상태값을 지속적으로 관리하는데, 이부분까지 모두 초기화 시켜주면 완벽하다.
다음 코드는 폼 초기화를 위한 코드 샘플인데, 데이터 초기화의 경우 원본을 유지한채 복사본을 사용하는 것으로 처리하고 추가로 $setPristine(), $setUntouched() 함수를 호출하여, 폼 변경에 따른 내부 상태값의 변화 역시 같이 초기화 시켜준다.
<html lang="en">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('formCtrl', function($scope) {
//폼 초기화를 위한 초기값 저장
$scope.origin = {firstName:"OriginFirst", lastName:"OriginLast"};
//초기값에 기반하여 복사 수행(이 값은 폼과 양방향 바인딩 됨)
$scope.user = angular.copy($scope.origin);
$scope.resetForm = function() {
//폼 요소들의 입력값을 초기 값으로 변경
$scope.user = angular.copy($scope.origin);
//폼 요소를 최초의 깨끗한 상태로 돌리기
$scope.myForm.$setPristine();
//폼 요소를 한번도 손대지 않은 상태로 돌리기
$scope.myForm.$setUntouched();
};
});
</script>
<body>
<div ng-app="myApp" ng-controller="formCtrl">
<form name="myForm" novalidate>
First Name:<br>
<input type="text" ng-model="user.firstName"><br>
Last Name:<br>
<input type="text" ng-model="user.lastName">
<br><br>
<button ng-click="resetForm();">RESET</button>
</form>
<p>Pristine: {{myForm.$pristine}}</p>
</div>
</body>
</html>
* 폼 유효성 검사
- 지금까지 살펴본 것처럼, AngularJS는 폼의 입력요소들의 변경에 따른 상태값을 지속적으로 관리하며 이를 기반으로 폼의 유효성 검사를 지원한다. 폼의 유효성 검사 기준은 HTML5에 새로이 추가된 유효성 관련 특성(Attribute)에 근거하거나 개발자가 직접 자신만의 유효성 검사 기준을 만들 수 있도록 지원한다.
HTML5의 새로운 입력타입과 특성에 기반한 유효성 검사
- 가장 흔한 유효성 검사는 입력필드에 대한 필수입력 여부와 입력필드 타입에 따른 유효값 검사이다.
다음 코드는 HTML5의 required 특성을 부여한 필수 입력필드에 대한 입력여부와 email 타입의 입력필드에 대한 입력값 유효성 검사 샘플이다. 입력필드에 대한 $valid는 입력값 여부에 따라 true/false 값 중 하나를 반환한다.
<input name="txtName" ng-model="txtName" required>
{{myForm.txtName.$valid}}
<br />
<input name="txtEmail" ng-model="txtEmail" type="email" required>
{{myForm.txtEmail.$valid}}
</form>
AngularJS 디렉티브에 기반한 유효성 검사
- AngularJS가 미리 정의해 둔 디렉티브 기반으로 유효성 검사도 가능하다. 다음 코드는 필수입력 지정과 입력문자의 최소/최대 길이를 지정하여 유효성 검사를 수행한다.
<input name="txtName" ng-model="txtName" ng-required="true" ng-minlength="3"
ng-maxlength="5">
{{myForm.txtName.$valid}}
</form>
입력폼 검사의 일반적 패턴
- 이번에는 좀 더 실용적인 코드 샘플을 보자. 하나의 필수 입력필드와 이메일 입력필드가 존재한다. 각 입력필드 바로 옆에는 유효성 실패에 대한 안내 메시지를 보여준다. ng-show 디렉티브는 주어진 조건값이 참/거짓에 따라 표시여부를 결정한다. 코드에서 주어진 조건은 입력필드에서 포서커를 잃고($touched) 유효성 검사에 실패한 경우($invalid)에 대한 And 조건으로 부여한다.
그리고 submit 버튼은 ng-disabled 디렉티비를 지정하는데 두 입력필드 중 하나라도 유효하지 않으면 비활성화 되도록 조건을 부여한다.
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body ng-app>
<form name="myForm">
<input name="txtName" ng-model="txtName" required autofocus>
<span style="color:red" ng-show="myForm.txtName.$touched && myForm.txtName.$invalid">
<span ng-show="myForm.txtName.$error.required">Username is required.</span>
</span>
<br />
<input type="email" name="txtEmail" ng-model="txtEmail" required>
<span style="color:red" ng-show="myForm.txtEmail.$touched && myForm.txtEmail.$invalid">
<span ng-show="myForm.txtEmail.$error.required">Email is required.</span>
<span ng-show="myForm.txtEmail.$error.email">Invalid email address.</span>
</span>
<br />
<input type="submit" ng-disabled="myForm.txtName.$invalid || myForm.txtEmail.$invalid">
</form>
</body>
</html>
* ngMessages 모듈 이용하기
- ngMessages 서브모듈을 이용하여 유효성 검사에 따른 메시지 표시를 보다 체계적으로 관리할 수 있을 것이다. 이 모듈을 사용하려면 angular-messages.js 파일을 따로 참조해 줘야 한다.
ng-messages 디렉티브에 $error 객체를 바인딩해서 해당 입력요소가 유효한지 여부를 판단할 수 있게 한다. 그리고 ng-message 디렉티브에 검사하고자 하는 유효성 조건의 이름을 지정한다. 예에서는 총 3개(required, minlength, maxlength) 조건을 지정했다.
만일 해당 입력요소에 유효성 오류가 있고, 각 오류가 어떤 검사 기준인지에 따라 서로 다른 메시지를 보여주게 될 것이다.
<html>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular-messages.js"></script>
<script>
(function(angular) {
angular.module('myApp', ['ngMessages']);
})(window.angular);
</script>
<body ng-app="myApp">
<form name="myForm">
Name: <input type="text" name="txtName" ng-model="txtName" required ng-minlength="5" ng-maxlength="10">
<div ng-messages="myForm.txtName.$error">
<div ng-message="required">You did not enter a field</div>
<div ng-message="minlength">Your field is too short</div>
<div ng-message="maxlength">Your field is too long</div>
</div>
</body>
</html>
* 사용자 정의 유효성 검사
- 지금까지는 입력요소의 필수입력여부, 타입 및 길이 등 HTML5의 새로운 특성이나 AngularJS가 내부적으로 지원하는 유효성 검사 기준을 사용하여 체크를 수행하였다.
그러나 실무환경에서는 보다 다양한 유효성 검사 조건이 존재할 수 있다. 예를 들어 입력필드에 들어가는 값은 반드시 숫자여야 하던지, 아님 어떤 정규식에 근거하여 유효성 검사를 수행하기도 한다.
이런 경우, 사용자 정의 유효성 검사기를 새로 만들면 된다. 사용자 정의 유효성 검사를 위해서는 커스텀 디렉티브를 생성하며 기존 디렉티브와의 차이점은 ngModel 모듈을 디렉티브에 추가해야 한다는 점이다.
다음 코드는 정규표현식에 기반한 사용자 정의 유효성 검사 디렉티브 구현을 보여준다. AngularJS 공식문서의 샘플코드이며, 커스텀 디렉티브의 link 함수를 통해 구현한다.
유효성 검사기의 이름은 integer으로 기존 필수입력 조건인 required와 마찬가지로 HTML의 특정 입력요소에 integer라는 이름으로 유효성 기준을 추가할 수 있다.
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
(function(angular) {
var app = angular.module('myApp', []);
var INTEGER_REGEXP = /^\-?\d+$/;
app.directive("integer", function(){
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl){
ctrl.$validators.integer = function(modelValue, viewValue){
if(ctrl.$isEmpty(modelValue)){
//비어 있을 경우 유효한 것으로 간주한다.
return true;
};
if(INTEGER_REGEXP.test(viewValue)){
//정규식과 일치하므로 유효함
return true;
};
//위 두조건을 모두 만족하지 않으므로 유효하지 않음
return false;
};
}
};
});
})(window.angular);
</script>
<body ng-app="myApp">
<form name="myForm">
<input type="text" ng-model="amount" name="amount" integer /><br />
<span ng-show="myForm.amount.$error.integer">정수가 아닙니다.</span>
</form>
</body>
</html>
'모바일 > Javascript' 카테고리의 다른 글
[Angular2] Angular2 선택을 위한 명분 (0) | 2016.12.19 |
---|---|
[Node.js] Node With IIS (1) | 2016.07.19 |
[Node.js] CORS 설정(Cross Domain 요청 허용) (0) | 2016.07.15 |
[AngularJS] REST API 통신(with Node.js) (0) | 2016.07.15 |
[Node.js] socket.io를 활용한 웹채팅 구현 (5) | 2016.07.15 |
웹은 기본적으로 '동일출저정책(Same Origin Policy, SOP)' 정책을 따른다.
이는 보안을 위한 기본정책으로, SOP는 하나의 출처(Origin)에서 로드된 문서나 스크립트가 다른 출처에 존재하는 리소스와 상호작용하지 못하도록 제약을 두는 것이다.
그런데, 간혹 이런 제약이 웹 응용프로그램을 만드는데 걸림돌이 되기도 한다.
Ajax 통신이 활발해 지고, 다른 사이트에 존재하는 Open API와 상호통신이 필요한 경우와 매쉬업(Mash-up)으로 새로운 2차 응용물을 개발하게 되면서.. 등등.. 이는 간혹 걸림돌이 된다.
근래 Node.JS로 서버를 만들고, Aptana Studio 자체 내장 웹서버로 몇 가지 테스트를 하고 있는데, 두 환경은 서로 다른 별도의 포트(Port)에서 웹을 동작시키기 때문에 여지없이 Cross Domain 문제가 발생한다.
호출하는 모든 웹 리소스를, Node.JS로부터 다운로드 받아서 실행하는 구조로 HTTP 서버를 구현하면 SOP 정책을 따를 수 있겠으나, 그렇지 못한 개발환경도 있을 터이고, 실제 서비스 환경에서는 더욱이 서로 다른 도메인(포트 포함)간 상호작용이 필요할 것이다.
SOP 정책을 우회하기 위해서는, 서버 측의 설정이 필요한데, 위 그림에서 친철히 안내하고 있는 것처럼 Access-Control-Allow-Origin 헤더, 즉 크로스 도메인으로 허용할 도메인 목록이 헤더로 제공되어야 한다.
이것을 CORS(Cross-Origin Resource Sharing)를 이용한 SOP 정책 우회라 한다.
Node.JS 환경에서 이 설정을 해보자.
다음과 같이, Node 기반 HTTP 서버의 응답헤더에 추가 해준다.
가장 중요한 설정은, Access-Control-Allow-Origin으로 허용할 도메인(포트)을 추가한다.
여러 도메인이 있을 경우, 중복해서 지정을 하거나 '*' 를 이용해 전체 허용도 가능하다.
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "http://127.0.0.1:8020");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
next();
});
app.get('/users', function(req, res) {
var tempUsers = [{id:'park', city:'pusan'}, {id:'kim', city:'seoul'}];
res.send(tempUsers);
});
app.listen(3002);
console.log("Listening on port 3002");
참고로 127.0.0.1:8020 도메인은, 필자의 Aptana Studio가 생성한 임의 주소이다.
이렇게 설정한 후, 클라이언트에서 요청하면 요청이 성공적으로 이뤄진다.
<script>
window.onload = function(){
var xhr = new XMLHttpRequest();
xhr.onload = function(){
console.log(xhr.response);
};
xhr.open("GET", "http://127.0.0.1:3002/users");
xhr.send();
};
</script>
그리고 언제나 그렇듯이 확장 라이브러리도 있다.
cors라는 이름의 Node.JS 확장인데, npm을 통해 설치하고 간단히 다음처럼 사용할 수 있다. 훨씬 간편하다.
var cors = require('cors')();
app.use(cors);
그래도 언제나 보안은 중요한 문제이니, SOP 정책에 존중하면서, 필요시 아주 제한적으로 적용하는 것을 권장함.
'모바일 > Javascript' 카테고리의 다른 글
[Node.js] Node With IIS (1) | 2016.07.19 |
---|---|
[AngularJS] 폼(form)과 유효성 검사 (0) | 2016.07.18 |
[AngularJS] REST API 통신(with Node.js) (0) | 2016.07.15 |
[Node.js] socket.io를 활용한 웹채팅 구현 (5) | 2016.07.15 |
[AngularJS] Route (0) | 2016.07.14 |
AngularJS는 원격 서버와의 HTTP 기반 통신을 위해 $http서비스를 제공한다.
$http는 브라우저의 XMLHttpRequest 객체나 JOSNP를 이용하여 원격 서버와 통신하기 위한 AngularJS의 서비스다.
이번 글에서는, $http서비스를 이용한 비동기 통신의 기본적 샘플을 구현해 보겠는데, 서버는 간단히 REST API 구현이 가능한 Node.JS를 사용한다.
* 서버 측 코드
- 먼저 Node.JS 기반으로 동작하는 HTTP 서버를 생성한다. express와 bodyParser 등 확장 라이브러리를 npm을 통해 먼저 설치해 둔다.
서버는 간단한 REST 기반 API 3개를 정의하고 있다.
사용자 목록(/users)과 단일사용자(/users/사용자id) 에 대한 GET API와 사용자등록(/users)을 위한 POST API로 구성되어 있다. 파일을 server.js로 저장하고, 쉘을 통해 server.js를 실행한다.
나머지 사항은 주석을 보면 이해될 정도로 심플하다.
var url = require("url");
//post 데이터의 body 속성 접근 및 json파싱 위한 bodyParser 미들웨어 사용
var bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({extended:true}));
app.use(bodyParser.json());
//루트에 대한 get 요청에 응답
app.get("/", function(req, res){
console.log("get:chatClient.html");
//최초 루트 get 요청에 해대, 서버에 존재하는 client.html 파일 전송
res.sendFile("client.html", {root: __dirname});
});
//GET API(http://127.0.0.1:3000/users), 전체사용자 목록
app.get('/users', function(req, res) {
//임의의 사용자 리스트 생성(실제 환경에서는 db 등 연동 결과로 생성)
var tempUsers = [{id:'park', city:'pusan'}, {id:'kim', city:'seoul'}];
//클라이언트에 반환
res.send(tempUsers);
});
//GET API(http://127.0.0.1:3000/users/1), 특정 사용자 id를 지정하여 한명의 사용자 정보 호출
app.get('/users/:id', function(req, res) {
//매개변수로 전달된 사용자 id 값 추출
var id = req.params.id;
//전달받은 사용자 id 그대로 다시 반환
res.send([{id:id, city:'pusan'}]);
});
//POST API, 새로운 사용자 생성
app.post('/users', function(req, res){
//POST 데이터로 넘어온 JSON 추출
var postData = req.body;
console.log(postData);
//클라이언트에 그대로 다시 반환(실제 환경에서는 db 삽입 등 작업)
res.send([postData]);
});
//3000포트로 http 서버 리스닝 시키기
app.listen(3000);
console.log("Listening on port 3000");
* 클라이언트 측 코드
- 자바스크립트와 HTML을 분리하면 좋지만, 글 작성 편의를 위해 하나의 파일로 만든다. 그리고 서버코드에서 지정한 대로, client.html로 저장한다.
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
//원격 서버 URL 지정
var url = "http://127.0.0.1:3000/users/";
//AngularJS 메인모듈 생성
var app = angular.module('myApp', []);
app.controller('userController', function($http) {
//현재 컨텍스트의 this 참조저장
var userCtrl = this;
//GET 요청
//'http://127.0.0.1:3000/users' 또는 'http://127.0.0.1:3000/users/특정ID' 형태의 rest api 호출
userCtrl.getUser = function(userID){
//사용자 id가 매개변수로 넘어온 경우 url에 id 매개변수 붙이기
var id = userID || "";
$http.get(url + id)
.then(function(response){
//응답 성공 시 실행
userCtrl.users = response.data;
userCtrl.response = response;
})
.catch(function(error){
//실패시 실행
console.log('오류 발생');
userCtrl.response = error;
})
.finally(function() {
//성공, 실패 어떤 경우에도 실행
console.log('finish getUser()');
});
};
//POST 요청
userCtrl.postUser = function(data){
//post로 보낼 json 데이터 임의생성
var postData = JSON.stringify({id:'newID', city:'newCity'});
//http 요청객체 생성
var req = {
method: 'POST',
url: url,
headers: { 'Content-Type': 'application/json; charset=UTF-8'},
data: postData
};
$http(req)
.then(function(response){
userCtrl.users = response.data;
userCtrl.response = response;
})
.catch(function(error){
console.log('오류 발생');
userCtrl.response = error;
})
.finally(function() {
console.log('finish postUser()');
});
};
});
</script>
<body>
<div ng-app="myApp" ng-controller="userController as userCtrl">
<button ng-click='userCtrl.getUser();'>1.GET</button>
<button ng-click='userCtrl.getUser("777");'>1.GET(using ID)</button>
<button ng-click="userCtrl.postUser();">1.POST</button>
<!-- 사용자 리스트 -->
<ul>
<li ng-repeat="user in userCtrl.users">
{{ user.id + ', ' + user.city }}
</li>
</ul>
<!-- 응답 객체 정보 출력 -->
<p>
response status: {{userCtrl.response}}
</p>
</div>
</body>
</html>
설명은 주석으로 충분한 듯 보이고, 실행화면은 다음과 같다.
- GET, http://127.0.0.1:3000/users/ 요청
- GET, http://127.0.0.1:3000/users/777 요청
- POST, http://127.0.0.1:3000/users/ 요청
'모바일 > Javascript' 카테고리의 다른 글
[AngularJS] 폼(form)과 유효성 검사 (0) | 2016.07.18 |
---|---|
[Node.js] CORS 설정(Cross Domain 요청 허용) (0) | 2016.07.15 |
[Node.js] socket.io를 활용한 웹채팅 구현 (5) | 2016.07.15 |
[AngularJS] Route (0) | 2016.07.14 |
[AngularJS] Directive (0) | 2016.07.13 |
재밌네... ㅎㅎ
별도의 비표준 플러그인 없이, 웹에서 채팅을 이렇게 쉽게 구현할 수 있는 시대라니...
6년전, 웹 소켓에 대해 나름 정리한 적이 있다.
이미 그 시대는 오래전(?)에 도래했건만, 개인적으로 방치해 두다가, 어제 밤 문득...
웹 소켓과 node.js를 사용해서 채팅 샘플을 구현해 보고 싶다는 생각이 스쳐 지나갔다.
socket.io라는 자바스크립트 라이브러리가 존재한다더니, 이건 뭐.. 다른건 볼 필요도 없네.
socket.io는 Node.js 기반으로 소켓 통신이 가능하도록 해 주는 확장 라이브러리인데, HTML5의 정식표준인 WebSocket을 지원하지만, 미지원 환경에 대한 호환성을 위해 Comet 같은 기술도 하나의 API로 추상화 시킨 양방향 통신 라이브러리이다.
socket.io는 WebSocket이 지원되지 않는 환경에서 실시간 양방향 통신을 구현하기 위해 다음과 같은 fallback 기술을 사용하고 있다.
- FlashSocket, AJAX Long Polling, AJAX Multi part Streaming, IFrame, JSONP Polling
기본적으로 Node.js 설치해 주고, express와 socket.io를 npm을 통해 다음과 같이 설치한다.
- npm install express
- npm install socket.io
이제 환경 설정은 끝이다. 자바스크립트 기반 개발환경은 이렇듯 심플하다. 내가 좋아하는 이유 중 주요한 팩터이다.
* socket.io를 이용한 웹 채팅 구현하기
먼저 다음과 같은 3개의 파일을 준비한다.
1. chatServer.js
- 채팅 서버 역할을 하는 자바스크립트 파일로, Node.JS에 의해 호스팅되며 socket.io 라이브러리를 통해 소켓 서버를 구현한다. 또한 부가적으로 http 리소스 요청에도 응답할 수 있도록 한다.
2. chatClient.js
- 채팅 서버와 통신을 하는 클아이언트 자바스크립트 파일로, 역시 socket.io 라이브러리를 사용하며 채팅을 위한 간단한 DOM 조작을 수행한다.
3. chatClient.html
- chatClient.js가 정의된 html 파일이며, 채팅창과 텍스트 박스로 간단히 구성된 뷰를 제공한다.
먼저 chatServer.js 파일의 코드부터 보자.
var url = require("url");
//루트에 대한 get 요청에 응답
app.get("/", function(req, res){
console.log("get:chatClient.html");
//최초 루트 get 요청에 대해, 서버에 존재하는 chatClient.html 파일 전송
res.sendFile("chatClient.html", {root: __dirname});
});
//기타 웹 리소스 요청에 응답
app.use(function(req, res){
var fileName = url.parse(req.url).pathname.replace("/","");
res.sendFile(fileName, {root: __dirname});
console.log("use:", fileName);
});
//http 서버 생성
var server = require('http').createServer(app);
server.listen(3000);
console.log("listening at http://127.0.0.1:3000...");
//클로저를 사용해, private한 유니크 id를 만든다
var uniqueID = (function(){
var id = 0;
return function(){ return id++; };
})();
//서버 소켓 생성
var socket = require('socket.io').listen(server);
//소켓 Connection 이벤트 함수
socket.sockets.on('connection', function(client){
//클라이언트 고유값 생성
var clientID = uniqueID();
console.log('Connection: '+ clientID);
//서버 receive 이벤트 함수(클라이언트에서 호출 할 이벤트)
client.on('serverReceiver', function(value){
//클라이언트 이베트 호출
socket.sockets.emit('clientReceiver', {clientID: clientID, message: value});
});
});
다음으로 chatClient.js 소스이다.
//클라이언트 소켓 생성
var socket = io.connect('ws://127.0.0.1:3000');
//DOM 참조
var div = document.getElementById('message');
var txt = document.getElementById('txtChat');
//텍스트 박스에 포커스 주기
txt.focus();
//텍스트 박스에 이벤트 바인딩
txt.onkeydown = sendMessage.bind(this);
function sendMessage(event){
if(event.keyCode == 13){
//메세지 입력 여부 체크
var message = event.target.value;
if(message){
//소켓서버 함수 호출
socket.emit('serverReceiver', message);
//텍스트박스 초기화
txt.value = '';
}
}
};
//클라이언트 receive 이벤트 함수(서버에서 호출할 이벤트)
socket.on('clientReceiver', function(data){
//console.log('서버에서 전송:', data);
//채팅창에 메세지 출력하기
var message = '['+ data.clientID + '님의 말' + '] ' + data.message;
div.innerText += message + '\r\n';
//채팅창 스크롤바 내리기
div.scrollTop = div.scrollHeight;
});
};
마지막으로 chatClient.html 파일이다.
<script src="/socket.io/socket.io.js"></script>
<div id="message" style="background-color:#F5F5F5; width:400px; height:200px; OVERFLOW-Y:auto; word-wrap: break-word"></div>
<div id="chatInput">
메시지를 입력하세요 <input type='text' id='txtChat' name="txtChat">
</div>
* 실행흐름
1) 서버 실행
- Node의 쉘기능을 이용해 다음과 같이 chatServer.js를 실행한다. 실행하면 다음 그림과 같이 console 로그가 프롬프트 창에 찍힌다.
2) 클라이언트 실행
- 클라이언트에서는 단순히 브라우저로 지정된 url로 접근하기만 하면 된다.
브라우저로 chatServer.js 서버로 접속하면, 루트 get요청(http://127.0.0.1:3000)에 따른 chatClient.html 파일이 클라이언트로 다운로드 되고, 이 HTML파일이 브라우저에서 실행되면서 chatClient.js 파일에 대한 요청이 다시 이뤄져 자바스크립트 코드가 다운로드 되고 실행된다. 이 클라이언트 자바스크립트가 실행되어 소켓서버와 클라이언트는 서로 연결이 이뤄진다.
다음 그림은 브라우저에서 해당 url로 접근했을 때 서버측 로그화면이다.
* 결과화면
- 총 4개의 사용자가 접근하는 시나리오로 가정하여, 4개의 브라우저를 동시에 사용한다. 각 브라우저에서 해당 url로 접근하고 각자 채팅을 해 본다. 이때 각 클라이언트 구분은 서버측 코드에서처럼 임의의 순처적 고유값을 부여한 숫자로 사용한다.
다음 화면은 브라우저 4개를 띄우고, 채팅을 해본 모습니다. 혼자놀기에 좋다 ㅡ,ㅡ;
지금까지 알아본 샘플 채팅은 채팅 구현을 위한 가장 기본적이면서도 필수적으로 구현되어야 하는 것들만 다뤘다. 실제 업무에서 채팅을 구현한다면 성능과 안정성 등을 위해 다양한 방어코드, 추가기능 코드, 최적화 기법 등의 추가 작업이 필요할 것이다.
기본적인 흐름과 코드를 알았으니, 살을 붙여 나가면 될 일이다.
'모바일 > Javascript' 카테고리의 다른 글
[Node.js] CORS 설정(Cross Domain 요청 허용) (0) | 2016.07.15 |
---|---|
[AngularJS] REST API 통신(with Node.js) (0) | 2016.07.15 |
[AngularJS] Route (0) | 2016.07.14 |
[AngularJS] Directive (0) | 2016.07.13 |
[AngularJS] $scope과 controller-as 문법 (0) | 2016.07.12 |
AngularJS는 URL기반 경로 탐색을 지원하는 라우트(route) 모듈을 제공한다.
라우팅 기능은 SPA(Single Page Application)를 지향하는 프레임워크에서 빠져서는 안되는 중요한 기능이며, 이미 그전의 많은 모바일 자바스크립트 프레임워크(jQTouch, jQuery Mobile, Sench Touch 등)에서도 제공되는 유용한 기능이다.
라우팅 기능은 다수의 사용자 화면(UI)을 가진 AngularJS 응용프로그램 구현 시 특히 유용한 기능으로 개별 사용자 화면을 서로 다른 HTML 파일로 만들고, URL주소에 따라 특정 화면으로로 이동시켜 주는 기능이다. 특히 SPA환경에서의 페이지 교체는 새로고침 없이 단일 페이지에서 HTLM Element의 교체로 이뤄져야 하는데 AngularJS는 이와 같은 기능을 route 모듈과 ng-view 디렉티브의 조합으로 가능케 한다.
라우팅 구현은 아주 심플하며, w3schools.com의 예제는 이를 잘 설명해 주고 있다.
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-route.js"></script>
<script>
var app = angular.module("myApp", ["ngRoute"]);
app.config(function($routeProvider) {
$routeProvider
.when("/", {
templateUrl : "main.htm"
})
.when("/red", {
templateUrl : "red.htm"
})
.when("/green", {
templateUrl : "green.htm"
})
.when("/blue", {
templateUrl : "blue.htm"
});
});
</script>
<body ng-app="myApp">
<p><a href="#/">Main</a></p>
<a href="#red">Red</a>
<a href="#green">Green</a>
<a href="#blue">Blue</a>
<div ng-view></div>
</body>
</html>
먼저 라우팅 모듈이 정의된 angular-route.js에 대한 스크립트 참조를 추가하고, 메인모듈에 ngRoute서브모듈을 주입한다. 그리고 응용프로그램이 실제 동작하기 전에 수행되어야 할 설정옵션을 정의할 수 있는 module.config 메서드에 라우팅 기능을 추가한다.
$routeProvider는 라우팅 기능을 위해 AngularJS에서 기본적으로 제공하는 서비스이며, URL조건과 이에 대응하는 리소스 경로는 $routeProvider.when 메서드에 라우팅 설정 객체를 정의하는 방식으로 구현한다.
ngRoute 모듈을 이용한 라우팅은 ng-view 디렉티브와 연결된다. 라우팅 조건에 맞는 URL 요청이 들어오면 이에 대응하는 리소스이 내용(HTML페이지)을 불러와서 ng-view 디렉티브 동적으로 삽입시킨다.
각각의 HTML 페이지는 새로운 URL요청에 따라 네트워크를 통해 다운로드 받게 되는데, 이때 대상 페이지가 새로고침(reload)되는 것이 아니라, 페이지의 특정 영역(ng-view)에 불러온 HTML 내용을 삽입하는 방식으로 동작하기 때문에 SPA(Single Page Application) 구현이 가능하게 된다.
URL 링크는 #red와 같은 형태로, 해시(#) 기호를 이용하여 작성한다.
* 대체 라우트 지정
- 요청된 URL에 대한 라우팅 정의가 없을 경우, $routeProvider 서비스의 otherwise 메서드로 대체 라우트를 정의할 수 있다. 다음의 코드는 일치하는 URL이 없을 경우 루트(/)로 이동하도록 구현한 것이다.
$routeProvider
.when("/red", {
templateUrl : "red.htm"
})
.otherwise({redirectTo: '/'})
});
* Controller와 함께 사용하기
- 라우팅된 페이지 역시 AngularJS의 영역에 포함된 HTML영역이기에 일반적인 Controller 사용형태를 그대로 구현할 수 있다. 다음 예제는 라우팅에서 Controller를 사용해 각 뷰에 해당하는 컨트롤러를 정의하고 Controller-as 문법을 사용한 코드이다.
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-route.js"></script>
<script>
var app = angular.module("myApp", ["ngRoute"]);
app.config(function($routeProvider) {
$routeProvider
.when("/Page1", {
templateUrl : "Page1.html",
controller : "Page1Controller",
controllerAs: "page1Ctrl"
})
.when("/Page2", {
templateUrl : "Page2.html",
controller : "Page2Controller",
controllerAs: "page2Ctrl"
});
});
app.controller("Page1Controller", function(){
var page1Ctrl = this;
page1Ctrl.msg = "Page1 Controller Property";
});
app.controller("Page2Controller", function(){
var page2Ctrl = this;
page2Ctrl.msg = "Page2 Controller Property";
});
</script>
<body ng-app="myApp">
<a href="#Page1">Page1</a>
<a href="#Page2">Page2</a>
<div ng-view></div>
</body>
</html>
- Page1.html
<p>{{page1Ctrl.msg}}</p>
- Page2.html
<p>{{page2Ctrl.msg}}</p>
* 매개변수와 함께 사용하기
- 지금까지 구현한 예제에서는 실제 리소스 URL만 지정해서 라우팅을 구성하였다. 실무 환경에서는 번번히 URL에 매개변수가 따라 붙는다. 예를 들어 다음과 같이 특정 게시물 ID를 매개변수로 지정해서 해당 게시물로 바로 접근하는 URL을 만들 수 있다.
- http://yourdomain/board/10 (10번 게시물에 대한 URL 스킴)
AngularJS 라우팅 시스템에서는 URL의 매개변수 정보를 얻기 위해서 $routeParams 를 제공한다.
라우팅 경로 설정시 ':(콜론)' 을 사용해서 매개변수명을 추가하고, 컨트롤러에 $routeParams 주입하여 scope객체에 매개변수 값을 노출시킬 수 있다.
다음 코드는 바로 직전 예제에서 매개변수 처리를 추가한 코드이다.
<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-route.js"></script>
<script>
var app = angular.module("myApp", ["ngRoute"]);
app.config(function($routeProvider) {
$routeProvider
.when("/Page1/:param", {
templateUrl : "Page1.html",
controller : "Page1Controller",
controllerAs: "page1Ctrl"
})
.when("/Page2/:param", {
templateUrl : "Page2.html",
controller : "Page2Controller",
controllerAs: "page2Ctrl"
});
});
app.controller("Page1Controller", function($routeParams){
var page1Ctrl = this;
page1Ctrl.msg = "Page1 Controller Property";
page1Ctrl.param = $routeParams['param'];
});
app.controller("Page2Controller", function($routeParams){
var page2Ctrl = this;
page2Ctrl.msg = "Page2 Controller Property";
page2Ctrl.param = $routeParams['param'];
});
</script>
<body ng-app="myApp">
<a href="#Page1/param1">Page1</a>
<a href="#Page2/param2">Page2</a>
<div ng-view></div>
</body>
</html>
- Page1.html
<p>{{page1Ctrl.msg}}</p>
<p>{{page1Ctrl.param}}</p>
- Page2.html
<p>{{page2Ctrl.msg}}</p>
<p>{{page2Ctrl.param}}</p>
URL에서 매개변수 정보를 추출하기 위해 보다 직관적인 $routeParams를 사용했지만, 다음과 같이 $route를 이용해도 동일하게 처리 가능하다
var page1Ctrl = this;
page1Ctrl.msg = "Page1 Controller Property";
page1Ctrl.param = $route.current.params['param'];
});
* 로딩 중 이미지 표시하기
- 이쯤되면, 왠만한 웹개발자들은 한가지 부가기능이 필요하다는 것을 느낄 것 이다. 바로 '로딩 중' 처리이다. 라우팅을 통해 HTML페이지를 내부적으로 요청할 때 지연이 발생하지 않는다는 보장을 할 수 없다. 페이지 로딩의 지연은 페이지 자체의 크기(무게) 때문일 수도 있고, 인터넷 네트워크 통신지연 때문일 수도 있다. 또한 라우팅 시 뷰가 표시되기 전에 반드시 먼저 선행되어야 하는 라이트 의존성 작업이 클 경우에도 지연이 발생할 수 있다.
UX 측면에서 보면, 발생 가능한 정상적 지연상황을 사용자에게 알려주는 것이 좋다. 즉 우리가 흔히들 사용하는 '로딩중' 이미지를 표시해 주면 좋을 것이다.
AngularJS 라우트에서는 이러한 처리를 지원하기 위해 라우트 이벤트를 제공한다. 라우트 이벤트를 통해 라우트가 시작되는 시점과 종료하는 시점에 특별한 작업을 추가할 수 있다. 우리는 여기에 로딩중 이미지를 표시해 볼 것이다.
먼저 다음과 같이, 메인모듈의 run 메서드를 작성한다. 로딩중 이미지는 응용프로그램 전역적으로 적용되어야 하는 기능이기 때문에 모듈의 run 메서드를 이용한다.
$rootScope.$on('$routeChangeStart', function(e, curr, prev){
$rootScope.IsLoading = true;
});
$rootScope.$on('$routeChangeSuccess', function(e, curr, prev){
$rootScope.IsLoading = false;
});
});
$rootScope 객체를 통해, routeChangeStart 이벤트와 routeChangeSuccess 이벤트 리스터를 등록한다.
이 이벤트 핸들러 함수들은 이벤트 객체와 현재페이지, 이전페이지 객체가 매개변수로 제공된다.
우리는 단순히 로딩중 이미지만 표시하면 되기에 특별히 매개변수를 사용하지는 않지만, $rootScope 객체에 IsLoading라는 임의의 속성을 추가시킨다. 이렇게 하면 응용프로그램의 뷰 어느 영역에서든 IsLoading 속성에 접근할 수 있게 된다.
다음으로, HTML 내용을 다음과 같이 작성한다. 로딩중 이미지가 표시될 영역을 추가하고 ng-class에 해당 CSS 클래스와 앞서 추가한 $rootScope 객체의 Loading 속성 값(true or false)을 매칭시킨다.
<a href="#Page1/param1">Page1</a>
<a href="#Page2/param2">Page2</a>
<div ng-view></div>
<div class="loadingModal"></div>
</body>
</html>
마지막으로, CSS를 다음과 같이 작성한다. loader.gif 이미지는 미리 준비해 둔다.
overflow: hidden;
}
body.loading .loadingModal {
display: block;
}
.loadingModal {
display: none;
position: fixed;
z-index: 1000;
top: 0;
left: 0;
height: 100%;
width: 100%;
background: rgba(244, 249, 251, 0.80) url(loader.gif) 50% 50% no-repeat;
}
다음은 로딩중 샘플의 전체 코드이다.
<html>
<style>
body.loading {
overflow: hidden;
}
body.loading .loadingModal {
display: block;
}
.loadingModal {
display: none;
position: fixed;
z-index: 1000;
top: 0;
left: 0;
height: 100%;
width: 100%;
background: rgba(244, 249, 251, 0.80) url(loader.gif) 50% 50% no-repeat;
}
</style>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-route.js"></script>
<script>
var app = angular.module("myApp", ["ngRoute"]);
app.config(function($routeProvider) {
$routeProvider
.when("/Page1/:param", {
templateUrl : "Page1.html",
controller : "Page1Controller",
controllerAs: "page1Ctrl"
})
.when("/Page2/:param", {
templateUrl : "Page2.html",
controller : "Page2Controller",
controllerAs: "page2Ctrl"
});
});
app.controller("Page1Controller", function($route){
var page1Ctrl = this;
page1Ctrl.msg = "Page1 Controller Property";
page1Ctrl.param = $route.current.params['param'];
});
app.controller("Page2Controller", function($routeParams){
var page2Ctrl = this;
page2Ctrl.msg = "Page2 Controller Property";
page2Ctrl.param = $routeParams['param'];
});
app.run(function($rootScope){
$rootScope.$on('$routeChangeStart', function(e, curr, prev){
$rootScope.IsLoading = true;
});
$rootScope.$on('$routeChangeSuccess', function(e, curr, prev){
$rootScope.IsLoading = false;
});
});
</script>
<body ng-app="myApp" ng-class="{loading:IsLoading}">
<a href="#Page1/param1">Page1</a>
<a href="#Page2/param2">Page2</a>
<div ng-view></div>
<div class="loadingModal"></div>
</body>
</html>
* 페이지 호출 대신, 내부 템플릿 사용
- 대부분 복잡한 사용자화면은 HTML를 분리시키고 라우트를 통해 페이지 호출하는 형태로 작성될 것이다. 그러나 간단한 내용이라면 굳이 HTML 페이지로 분리시키지 않고 바로 HTML값을 템플릿으로 지정할 수 있다. templateUrl 대신 template를 사용하면 된다.
* ngRoute 모듈의 한계
- 지금까지 AngularJS의 ngRoute 서브모듈을 이용해 라우팅 기능을 구현해 보았다. ngRoute 모듈은 라우팅을 위해 AngularJS에서 제공하는 기본 모듈로 한 페이지에 오직 한 개의 ng-view 디렉티브만 선언가능하다. 이는 내부적으로 라우트와 뷰 사이에 일대일 관계만이 성립되는 제약때문이다.
만일 여러개의 뷰가 중첩되거나 복잡한 레이아웃(다중뷰, 중첩뷰 등)으로 구성된 페이지에서 고릅 라우팅을 구현하고자 할 경우에는, ui-router 라이브러리를 이용할 것을 권장한다.
- https://github.com/angular-ui/ui-router
'모바일 > Javascript' 카테고리의 다른 글
[AngularJS] REST API 통신(with Node.js) (0) | 2016.07.15 |
---|---|
[Node.js] socket.io를 활용한 웹채팅 구현 (5) | 2016.07.15 |
[AngularJS] Directive (0) | 2016.07.13 |
[AngularJS] $scope과 controller-as 문법 (0) | 2016.07.12 |
[AngularJS] Module (0) | 2016.07.12 |
AngularJS는 정적인 HTML을 확장하여 동적인 응용프로그램으로써의 웹을 구현할 수 있도록 도와주는 자바스크립트 프레임워크이다.
MVC(MVVM) 아키텍처 기반, 양방향 데이터바인딩, SPA 개발, 모듈화 프로그래밍, 라우팅 시스템 등 단순한 웹 페이지를 응용프로그램으로 승격시키기 위한 다양한 기반 기술을 제공하는데, 이 중 Directive(디렉티브)는 HTML 요소 즉 뷰(View)에 대한 확장기능을 제공하는 AngularJS의 중요한 기능 중 하나다.
Directive를 사용하면, 자신의 응용프로그램에 최적화된 뷰와 기능을 캡슐화하여 재사용 가능한 컴포넌트를 만들 수 있다. 즉 Directive를 통해 HTML 태그 및 특성을 입맛에 맞게 재정의 가능하며 이를 통해 '사용자 정의 뷰'를 제작 및 필요에 의해 재사용할 수 있게 된다.
* AngularJS 내장 디렉티브
- AngularJS 내부적으로 HTML 확장을 위해 많은 디렉티비를 정의하고 있다. AngularJS 적용을 위해 HTML Element에 추가로 부여했던 특성들, na-app, ng-init, ng-model 등이 대표적 내장 디렉티브이다.
내장 디렉티비의 목록은 다음 url에서 확인 가능핟.
- http://www.w3schools.com/angular/angular_ref_directives.asp
* 사용자 정의 디렉티브
- AngularJS 디렉티브를 사용해서 HTML을 확장시킬 수 있다고 했는데, 먼저 간단한 예제부터 살펴보자
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
var app = angular.module("myApp", []);
app.directive("userdirective", function() {
return {
template : "<h1>사용자 정의 디렉티브</h1>"
};
});
</script>
<body ng-app="myApp">
<userdirective></userdirective>
</body>
</html>
가장 기본적인 형태로 구현된 코드형태이지만, 너무나도 간단하다.
디렉티브 역시, AngularJS의 Module에 추가하는 형태로 구현되며, 사용자 구미에 맞는 새로운 HTML Element를 정의하고 페이지에서 새로운 요소를 사용하고 있다.
기존에 정해진 HTML 요소 외에, 사용자가 원하는 이름을 새롭게 정의하고 사용할 수 있다는 측면에서 AngualrJS가 HTML을 '도메인 특화 언어(domain-specific language)'로써의 기능을 부여했다고 할 수 있다.
* 디렉티브 Naming
- 여기서 한가지 주의할 점이 있는데, 디렉티브 이름을 카멜표기법(단어와 단어사이의 첫 글자를 대문자료 표기)으로 지정했을 땐, 각 단어연결문자로 '-'를 사용해야 한다는 것이다. 즉 다음과 같이 user와 Directive사이에 '-' 문자로 분리해야 한다.
...
<user-Directive></user-Directive>
* 디렉티브 구현 문법
- 앞서 예제에서는 가장 단순한 형태의 디렉티브 사용법을 보여준 것이다. 디렉티브는 다음과 같이 세 가지 구성요소를 기반으로 동작한다.
1) 디렉티브 정의 객체(DDO, Directive Definition Object)
- 디렉티브 구현을 위한 필수요소로, 디렉티브의 HTML 템플릿, scope, 컴파일 주기 등을 설정한 객체를 정의한다. 앞서 예제에서도 리터럴 객체를 리턴하고 있는데, 이것이 바로 DDO이다.
2) 컨트롤러
- AngularJS의 컨트롤러와 동일한 방식으로 동작한다. 디렉티브를 위한 상태와 기능을 정의하고 외부와의 각종 상호작용을 구현한다. DDO와는 달리 반드시 컨트롤러를 정의해야 하는 것은 아니지만, 제대로 된 실무용 디렉티브에서는 필수로 구현해야 할 것이다.
3) 링크
- 디렉티브에서 정의한 DOM을 조작하는 코드를 구현한다. DOM 요소에 발생하는 이벤트를 가로채서 각종 처리를 수행한다. DDO와는 달리 선택요소로 반드시 구현하지 않아도 된다.
이와 같은 구성요소들이 포함된 디렉티브 정의를 위한 뼈대를 살펴보자.
//디렉티브 생성
//필요한 경우, 생성자 함수에 서비스를 주입해서 사용
//ex: function($rootScope, myService, ..., ) .. 이후 컨트롤러에서 사용가능
app.directive("userdirective", function() {
var linker = function(scope, element, attrs){
//DOM 조작에 관한 코드를 구현한다
//scope: 현재 작업중인 디렉티브에 할당된 scope객체.
//컨트롤러함수의 $scope와 동일. 이를 통해 여기서 컨트롤러 함수 호출 가능
//element: 디렉티브가 선언된 요소에 대한 참조를 jQuery 객체로 감싸서 제공
//attr: 디렉티브가 선언된 요소에 정의된 모든 특성을 배열로 제공
};
var controller = function($scope){
//디렉티브의 뷰에서 사용할 비즈니스 로직(상태와 메서드) 구현
//링크함수와 동일한 scope객체 공유
};
return { //DDO객체
restrict: 'A', //이 디렉티브를 HTML요소의 특성(attribute)으로만 사용하도록 제한
controller: controller, //디렉티브의 컨트롤러함수 지정
controllerAs: 'userdirective' //userdirective라는 이름으로 controller 참조
link: linker //링크함수 지정
//이 디렉티브가 HTML Element로 사용될 경우 다음과 같이 템플릿 지정
//template : "<h1>사용자 정의 디렉티브</h1>" //뷰를 위한 HTML 템플릿 지정
};
});
DDO(디렉티브정의객체)에 보면 restrict 속성이 있는데 이것은 해당 디렉티브를 HTML에서 사용할 때 특정 용도로 제한하기 위해 사용한다. 코드예제에서는 'A'라고 지정했는데 이는 HTML요소의 특성(Attribute)로만 이 디렉티브를 사용하도록 제한한 것이다. 다음은 restrict로 지정가능한 옵션이다.
그리고 template로 HTML을 직접 작성했는데, 외부에 이미 존재하는 html 파일을 지정할 수도 있다. * 사용자 정의 디렉티브 샘플
이때는 template 대신 templateUrl: 'directive.html' 형태로 사용한다.
- 이제 디렉티브의 구성요소를 대부분 사용해서 만들어보자
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
var app = angular.module("myApp", []);
app.directive("mybutton", function(){
var linker = function(scope, element, attrs){
//마우스 over/out 이벤트에 반응하여 투명도를 조절한다
element.bind('mouseover', function(e) {
element.css({'opacity': 0.5});
});
element.bind('mouseout', function(e) {
element.css({'opacity': 1.0});
});
};
var controller = function($scope){
var ctrlMyBtn = this;
ctrlMyBtn.text = "My Button";
ctrlMyBtn.getText = function(){
alert(ctrlMyBtn.text);
}
};
return {
restrict: 'A',
controller: controller,
controllerAs: 'ctrlMyBtn',
link: linker
};
});
</script>
<body ng-app="myApp">
<button mybutton ng-click="ctrlMyBtn.getText();">
{{ctrlMyBtn.text}}
</button>
</body>
</html>
- 예제코드가 그다시 쓸모있는 디렉티브라고는 할 수 없지만, 디렉티브의 구성요소가 어떤식으로 상호작용하여 동작 하는지 파악하기에는 더없이 좋을 것이다.
HTML Element의 특성으로만 사용가능한 디렉터리를 만들고 마우스 오버/아웃에 따른 DOM 투명도 조작과 컨트롤러에 정의된 속성과 메서드와 상호작용하는 예를 보여준다.
결과화면은 다음과 같다.
* 참고자료
- https://docs.angularjs.org/guide/directive
- http://www.w3schools.com/angular/angular_directives.asp
'모바일 > Javascript' 카테고리의 다른 글
[Node.js] socket.io를 활용한 웹채팅 구현 (5) | 2016.07.15 |
---|---|
[AngularJS] Route (0) | 2016.07.14 |
[AngularJS] $scope과 controller-as 문법 (0) | 2016.07.12 |
[AngularJS] Module (0) | 2016.07.12 |
[AngularJS] Two-way Data Binding (0) | 2016.07.10 |
* Scope 객체
AngularJS의 $scope은 뷰(View)와 컨트롤러(Controller)를 연결하는 객체이다.
HTML 페이지(뷰)는 자기자신이 포함된 컨텍스트의 $scope객체를 통해 컨트롤러에서 관리하는 데이터와의 양방향 동기화가 가능하며 컨트롤러가 제공하는 메서드도 호출할 수 있게 된다.
- 출처: https://github.com/Capgemini/ngTraining/wiki/AngularJS-Templates-101
그림에서 보는것과 같이, Scope은 비지니스 로직과 사용자 인터페이스간 연결을 담당하며 Controller는 Scope에 속성과 메서드를 추가하여 뷰에 노출시킨다.
scope 객체를 통해 컨트롤러와 뷰가 상호작용하는 간단한 코드를 보자.
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.name = "Park";
$scope.changeName = function(){
$scope.name = "Kim";
}
});
</script>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
<h1 ng-click="changeName();">{{name}}</h1>
</div>
</body>
</html>
컨트롤러에서 $scope객체에 name속성을 추가하고 값을 할당하였다. 또한 changeName이라는 메서드를 추가하고 name값을 변경하는 코드를 작성했다.
그리고 HTML 페이지에서는 {{name}} 표현식을 사용하여 scope에 정의된 name값을 출력하고 값이 클릭되면 changeName() 함수를 호출하도록 하였다.
이렇듯 컨트롤러에서 $scope 객체를 통해 속성과 함수를 노출하면 뷰에서는 이와 상호작용 할 수 있게 되는 것이다.
* controller-as 문법
AngularJS 1.3부터 추가된 것으로, 뷰에서 컨트롤러를 선언하는 방식에 대한 문법이다.
기존까지는 ng-controller="controllerName" 이라고 선언하는 반면, 이 문법을 사용하면
ng-controller="controllerName as ctlName"과 같이 선언하는 방식이다. as 뒤에 오는 것은 일종의 별칭으로 뷰에서 ctlName이라는 키워드를 통해 컨트롤러에 접근할 수 있게 되는 것이다.
다음 코드는 앞선 예제에서 $scope 객체를 사용하는 대신, controller-as 문법으로 교체한 것이다.
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function() {
var main = this;
main.name = "Park";
main.changeName = function(){
main.name = "Kim";
}
});
</script>
<body>
<div ng-app="myApp" ng-controller="myCtrl as main">
<h1 ng-click="main.changeName();">{{main.name}}</h1>
</div>
</body>
</html>
controller-as 문법의 사용으로 더 이상 컨트롤러 생성자 함수로 $scope 객체를 주입하지 않아도 된다. 그리고 HTML페이지(뷰)에서는 main이라는 이름으로 컨트롤러에 접근할 수 있게 되었다.
내부적으로는 controller-as 문법으로 정의한 컨트롤러의 이름은 여전히 $scope 객체 내부에 해당 일므의 변수로 추가된다.
그리고 참고로, 컨트롤러 코드에서 var main = this 하고 한 것은 필수 사항은 아니다. 단지 자바스크립트이 함수 컨텍스트 상의 this의 의미 모호성을 제거하기 위해 main이라는 임의의 이름으로 this(자기자신)를 미리 할당한 것이다. 이때 이왕이면 뷰에서 사용하는 이름과 똑 같이 하면 가독성이 좋아질 것이다.
사실 AngularJS 팀이 $scope 대신 controller-as 문법을 사용하기를 권장하고 이 문법을 추가한 것은 두 가지 이유 때문이다.
1. 뷰가 참조하는 컨트롤러의 모호성 제거
- $scope 객체를 통해 추가시킨 속성이나 메서드를 뷰에서 접근할 경우, 단지 속성명(메서드명)만 명시하면 된다. 이는 여러 컨트롤러가 복합적으로 사용되는 환경일 경우, 마크업에서 바인딩에 사용한 속성이 정확히 어디에 정의된 것인지에 대한 모호함을 유발시키며 이는 곧 코드의 가독성 및 유지보수성을 저해시킨다.
2. 묵시적 scope 상속의 나쁜 습관 방어
- 모든 scope 객체는 자신의 직계부모로부터 $rootScope에 이르기까지 계층 구조상 모든 부모 객체의 프로토타입을 상속받는다. 뷰에서 scope의 속성이나 메서를 참조할 경우 자신이 속한 컨텍스트의scope에 해당 속성(메서드)가 존재하지 않을 경우, scope 프로토타입 체인을 거슬러 올라가며 부모 객체를 계속 탐색해 나간다. 이런 동작은 프로토타입 기반의 상속 매커니즘을 가진 자바스크립트의 매커니즘과도 일치하며, 어떤 경우에는 편한 기능이기도 하다. 다만 게으른 개발자라면 부모 scope에 존재하는 속성이나 메서드를 그대로 사용하려고 할 것이며, 여러 곳에서 응용프로그램 전체에 걸쳐 공유되는 데이터에 접근하여 마구잡이로 변경하면 예측하지 못하는 문제들이 발생한다.
controller-as 문법은 더 나은 코딩을 위한 권고사항일 뿐이다. 우리는 여전히 $scope를 사용할 수 있다.
* Scope Hierarchies(Scope의
- 모든 AngularJS 응용프로그램은 하나의 root scope를 가지며, 이 root scope를 기준으로 컨트롤러나 디렉티브 내의 scope가 DOM 구조의 계층구조와 유사하게 child scope로 계층화 된다.
(단, DOM 계층구조가 모든 scope 계층구조를 결정하는 것은 아님에 주의하자)
즉 하나의 root scope와 하나 이상의 chind scope 형태의 계층구조로 이뤄지며, 이런 구조는 뷰가 scope 객체를 참조할 경우 자동으로 scope 프로토타입 체인을 거슬러 올라가며 탐색하도록 동작한다.
다음의 코드는 Scope의 계층구조 구성을 보여주며, 하위 계층에서는 상위계층의 속성에 접근할 수 있다는 것을 보여주고 있다.
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
var app = angular.module('myApp', [])
app.controller('ctrl_A', function($scope, $rootScope) {
$scope.name = 'name-A';
$scope.nameA= 'name-A-only';
$rootScope.masterName = 'master';
});
app.controller('ctrl_B', function($scope) {
$scope.name = 'name-B'
$scope.nameB = 'name-B-only'
});
</script>
<body>
<div ng-app="myApp">
<div ng-controller="ctrl_A">
<h1>Controller A</h1>
{{name}}, {{nameA}} , {{nameB}} {{masterName}}
<div ng-controller="ctrl_B">
<h1>Controller B</h1>
{{name}}, {{nameA}} , {{nameB}}, {{masterName}}
</div>
</div>
</div>
</body>
</html>
결과는 다음과 같다.
B컨트롤러는 A컨트롤러 하위계층에 속하며, B컨트롤러에 정의되지 않은 nameA속성과 rootScope의 masterName 속성에 접근할 수 있다. 반면 A컨트롤러는 B컨트롤러에 정의된 nameB 속성에 접근하지 못한다. 이로써, scope는 계층구조로 구성되며(묵시적으로 상속된다) 하위 scope는 자신에게 정의되지 않은 속성(메서드)를 찾기 위해 상위계층으로 거슬러 올라가며 자동으로 탐색한다는 것을 알 수 있다.
이번엔, 앞의 코드를 controller-as 문법을 사용하여 수정해 보자
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
var app = angular.module('myApp', [])
app.controller('ctrl_A', function($rootScope) {
var ctrlA = this;
ctrlA.name = 'name-A';
ctrlA.nameA= 'name-A-only';
$rootScope.masterName = 'master';
});
app.controller('ctrl_B', function() {
var ctrlB = this;
ctrlB.name = 'name-B'
ctrlB.nameB = 'name-B-only'
});
</script>
<body>
<div ng-app="myApp">
<div ng-controller="ctrl_A as ctrlA">
<h1>Controller A</h1>
{{ctrlA.name}}, {{ctrlA.nameA}} , {{ctrlB.nameB}}, {{masterName}}
<div ng-controller="ctrl_B as ctrlB">
<h1>Controller B</h1>
{{ctrlB.name}}, {{ctrlA.nameA}} , {{ctrlB.nameB}}, {{masterName}}
</div>
</div>
</div>
</body>
</html>
코드의 결과는 앞의 $scope 객체를 사용한 것과 동일하다. 하지만 뷰에서 참조하는 속성(메서드)이 정확히 어떤 컨트롤러에 정의된 것인지는 더욱 분명해 졌다.
* 다이제스트 주기(Digest cycle)
- AngularJS는 모델과 뷰의 내용을 자동으로 동기화 시킨다. 그 중심에 scope가 있다. scope는 MVVM 아키텍처 패턴에서 ViewModel에 해당하는 핵심 객체로 이해해도 무방하다.
AngularJS는 scope에 저장된 값이 변경되면 자동으로 뷰도 갱신시킨다. 이것은 AngularJS를 강력하게 만든 양방향 동기화이다. 그런데 이와같은 자동 동기화는 어떤 메커니즘으로 구현될 것일까?
AngularJS는 다이제스트 주기라는 순환적 매커니즘 하에 모델과 뷰를 자동으로 동기화 시킨다.
이 동작은 더티체크(dirty check)라는 개념 위에 구현된 것이며, 이것은 단순히 angular.equals 메서드를 이용하여 속성값이 이전 값과 현재 값을 비교하여 동기화 여부를 결정하는 것이다.
다음 그림은 AngularJS 공식 사이트에 게제된 다이제스트 주기를 표현한 그림이다.
아래 설명은 AngularJS in action의 설명을 요약(조금 수정)한 것이다.
AngularJS는 컴파일 과정을 거치면서 $scope 객체에 정의된 모든 속성에 대한 감시(watch) 표현식을 생성한다. 감시 표현식을 통해 $scope객체의 속성이 변경된 것을 알게 되면 리스너 함수가 호출된다.
간혹 AngularJS가 속성값이 변경되었다는 사실을 알아내지 못하는 경우도 있다. 이런 경우 $apply 객체를 통해 다이제스트 주기를 직접 실행할 수 있다. 대부분 API 호출이나 서드 파트 라이브러리가 수행한 작업을 AngualrJS에게 알리고자 할 때 이 기법을 활용한다.
AngularJS의 자동 바인딩 메커니즘의 개략적인 설명이며, 보다 자세한 이해를 위해서 다음의 블로그에 정리된 글을 읽어 보기를 권장한다.
=> http://www.nextree.co.kr/p8890/
설명이 아주 쉽고 자세히 잘 되어 있으며, 특히 실무에서 자주 맞주칠 수 있는 3rd Party 라이브러리 사용시 자동 바인딩을 위한 $scope.$apply() 함수 사용부분을 빼먹지 않고 봐두기를 바란다.
AngularJS는 기본적으로 위에서 설명한 메커니즘 하에, scope 속성이 변경되면 뷰에 자동으로 변경을 반영시킨다. 하지만 AngularJS의 관리 밖의 영역에서 발생한 이벤트는 인식하지 못하기 때문에 scope 속성값의 변화를 감지하지 못하게 된다. 따라서 $scope.$apply() 함수를 이용해 수동으로 다이제스트 주기를 시작하도록 할 수 있다.
'모바일 > Javascript' 카테고리의 다른 글
[AngularJS] Route (0) | 2016.07.14 |
---|---|
[AngularJS] Directive (0) | 2016.07.13 |
[AngularJS] Module (0) | 2016.07.12 |
[AngularJS] Two-way Data Binding (0) | 2016.07.10 |
[AngularJS] MVC Pattern in Angular (0) | 2016.07.10 |
AngularJS의 Module(모듈)은 평범한 HTML 페이지를 AngularJS 응용프로그램으로 확장시키기 위한 진입점이다. AngularJS는 Module을 통해 controller, service, filter, directive 등을 추가하는 방식으로 동작한다.
또한 전역네임스페이스를 오염시키지 않는 전형적인 모듈패턴을 지향하며 나아가 모듈화 프로그래밍을 가능케 하는 아키텍처링 방법으로 설계되었다.
AngularJS 모듈을 그 역할 관점에서 다음과 같이 다양하게 정의해 볼 수 있을 것이다.
- HTML 페이지를 AngularJS 응용프로그램으로 확장시키기 위한 진입점
- HTML 페이지와 AngularJS가 상호작용하기 위한 매개체
- 연관된 각종 컴포넌트를 포함하는 컨테이너
- AngularJS 응용프로그램의 구성을 논리적으로 조직화하는 단위
- 모듈화 프로그래밍을 지원하는 AngularJS의 아키텍처 지원 기능
* Module 생성과 로딩
- 모듈 생성과 로딩은 모두 angular.module 함수를 통해 이뤄진다. 첫 번째 매개변수는 모듈 이름을 지정하며 두 번째 매개변수는 해당 모듈과 연관된 서브모듈을 배열으로 지정할 수 있다.
서비모듈 지정은 모듈을 생성할 때만 지정 가능하다.
angular.module("myApp", []);
//생성한 모듈 가져오기
var app = angular.module("myApp");
* HTML페이지를 AngularJS 앱으로 확장시키기
- 이렇게 생성한 모듈을 HTML 페이지의 특정 Element에 지정하면 그 Element하위 요소들은 AngularJS 응용프로그램으로 동작가능하게 된다. 이때 ng-app 지시자를 통해 해당 앱이 사용할 모듈이름을 지정한다. (참고로 한 페이지에 모듈은 하나만 지정이 가능한데 만일, 두 개 이상의 서로다른 모듈을 지정한 경우 HTML 페이지 기준으로 가장 상단에 지정된 모듈만이 정상 동작할 것이다.)
var app = angular.module("myApp", []);
</script>
* 모듈에 컴포넌트 추가하기
- 모듈은 연관된 각종 컴포넌트를 포함하는 컨테이너 역할을 하며, 이렇게 필요에 의해 추가된 컴포넌트들을 이용하여 HTML페이지를 동적인 MVC 어플리케이션으로 동작시킬 수 있다.
다음 그림은 모듈을 통해 구성가능한 컴포넌트를 보여준다
(출처: http://www.c-sharpcorner.com/article/module-and-controller-in-angularjs/)
실제로 AngularJS 학습의 대부분은 그림에서 보는 것과 같은 컴포넌트의 사용법을 익히는 것이다. 각 컴포넌트의 상세한 설명은 개별 주제를 다룰 때 보기로 하고 여기서는 대략 다음과 같은 코드로 모듈에 필요한 컴포넌트를 추가할 수 있다는 것만 알고 넘어가자
app.controller("MyCtrl", function() { } ); //모듈에 컨트롤러 정의
app.service("MySvc", function() { } ); //모듈에 서비스 정의
app.directive("MyDir", function() { } ); //모듈에 디렉티브 정의
패턴은 거의 동일하다. 각 컴포넌트에 해당하는 메서드를 호출하고 컴포넌트의 이름과 익명함수를 전달하는 방식으로 동작한다. 모듈 메서드는 다음 그림을 참고하기 바란다.
- AngularJS의 모듈 로딩은 설정단계와 실행단계라는 두 단계를 거치며 각 단계를 위한 코딩블럭이 제공된다.
(설정블럭, Configuration blocks)
- 모듈 로딩의 첫 단계로, 모든 프로바이더를 연결하고 등록한다. 오직 프로바이더와 상수만이 설정블록에 주입될 수 있다.
(실행블럭, Run blocks)
- Injector가 생성된 후에 실행되며, 응용프로그램을 시동하는데 사용된다. 이 시점 이후에 추가적인 시스템 설정이 이뤄지는 것을 방지하기 위해, 실행블록에는 오직 인스턴스와 상수만을 주입할 수 있다.
AngularJS는 응용프로그램을 실행할 때, 해당 모듈의 설정영역을 먼저 실행한 후 실행영역을 실행시킨다. 실행영역은 일종의 main 메소드 같은 역할로 모든 서비스가 설정되고 인젝터가 생성된 후에 실행된다.
설정블럭과 실행블럭에 대한 자세한 내용은 다음 블로그를 참고하기 바란다.
- AngularJS의 설정단계(configuration phase)와 실행단계(run phase)
* 모듈화 프로그래밍(서브 모듈 사용하기)
- AngularJS의 Module은 응용프로그램의 구성을 논리적으로 조직화하는 단위로써도 역할을 한다. 아마 이것이 AngularJS Module의 핵심가치일 것이다.
이로써 모듈화 프로그래밍이 가능해 지는 것인데, 모듈화를 하면 연관된 것끼리는 묶고 서로 다른 것끼리는 분리해서 조직화 가능하며 필요한 모듈을 그때그때 조합해서 하나의 전체 응용프로그램을 완성시킬 수 있다. 이 역시 '관심사의 분리'라는 대원칙이 적용된 SW설계 기법이며 모듈화가 잘 이뤄진 프로그램은 재사용성/생산성/유지보수성이 향상되고 테스트용이성도 높아진다.
(공학적으로 모듈화가 잘 되었다는 것은 '응집도'는 높고 하게 '결합도'는 낮게 구성하는 것이다)
모듈화의 가치는 응용프로그램의 규모에 따라 달라진다.
아주 작고 간단한 프로그램 환경에서는 모듈화가 복잡성만 증가시키는 별 필요없는 기법이 된다.
하지만 프로그램의 크기가 상당하고 여러명의 개발자가 참여하는 개발환경이라면 모듈화는 거의 필수적인 기법이라 할 수 있다.
간단한 시나리오를 가정해 보면, ToDo 리스트를 관리하는 앱을 개발할 경우 다음과 같이 조직화 가능할 것이다.
- 메인모듈, 공용모듈, 사용자모듈, 인증모듈, ToDo 아이템 모듈, ....
순전히 개인적인 구분이지만, 프로그램의 성격과 규모에 따라 모듈화의 기준은 달라질 것이다.
다음 코드예제는 AngularJS Module을 이용해 모듈을 역할별로 분리하고 이를 조합하여 구성한 즉 모듈화 프로그램을 작성한 예이다.
먼저 MyApp이라는 메인 모듈을 생성하고 이 모듈이 의존하는 서브모듈(MyApp.User, MyApp.Common)을 배열로 전달한다.
MyApp.User 모듈은 사용자정보를 추상화한 모듈이며, MyApp.Common 모듈은 전체 응용프로그램에 공통적으로 사용될 상수를 정의한 모듈이다. 이 각각의 모듈은 메인모듈의 서브모듈로 등록되어 메인모듈의 컨트롤러의 생성자 함수에 서브모듈의 컴포넌트가 주입되는 방식으로 동작한다.
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
var myApp = angular.module("MyApp", ["MyApp.User", "MyApp.Common"]);
myApp .controller("MainCtrl", function(userlist, APPLICATION_CONFIG){
var main = this;
main.users = userlist.getUsers();
main.appConfig = APPLICATION_CONFIG;
});
var userModule = angular.module("MyApp.User", []);
userModule.service('userlist', function(){
var user = this;
var mock = [
{name: 'Park', city: 'Pusan'},
{name: 'KIM', city: 'Seoul'},
];
user.getUsers = function(){
return mock;
};
})
var commonModule = angular.module("MyApp.Common", []);
commonModule.constant("APPLICATION_CONFIG", {config1: "value1", config2: "value2"});
</script>
<body>
<div ng-app="MyApp" ng-controller="MainCtrl as main">
<h1>Users</h1>
<ul>
<li ng-repeat="user in main.users">
{{user.name + ', ' + user.city}}
</li>
</ul>
<div>{{ main.appConfig.config1 + ", " + main.appConfig.config2 }}</div>
</div>
</body>
</html>
* AngularJS 내장 모듈과 3rd-Party 모듈
- 앞서 모듈화 코드에서 구현한 모듈은 일종의 사용자 정의 모듈이다. 즉 개발자가 필요에 의해 임의로 생성한 모듈이다.
AngularJS는 유용한 기능을 미리 모듈화 시켜두고 이를 사용할 수 있도록 하고 있다. 이것을 AngularJS 내장모듈이라고 하는데 대표적으로 다음과 같은 것들이 있다.
- ngRoute: URL 라우팅 기능 제공
- ngAnimate: 애니메이션 기능 제공
- ngCookies: 웹 쿠키 기능 제공
더 많은 내장 모듈이 있지만, 차차 알아보기로 한다.
또한 인터넷의 제 3자에 의해 유용한 기능들을 모듈화하여 제공하기도 한다. 마치 플러그인 처럼 필요한 모듈을 가져다가 자신의 응용프로그램에 적용할 수 있다.
다음 사이트는 AngularJS 모듈을 검색하거나 새로운 모듈을 등록할 수 있다.
- http://ngmodules.org/
* 참고자료
- https://docs.angularjs.org/guide/module
- http://www.w3schools.com/angular/angular_modules.asp
'모바일 > Javascript' 카테고리의 다른 글
[AngularJS] Directive (0) | 2016.07.13 |
---|---|
[AngularJS] $scope과 controller-as 문법 (0) | 2016.07.12 |
[AngularJS] Two-way Data Binding (0) | 2016.07.10 |
[AngularJS] MVC Pattern in Angular (0) | 2016.07.10 |
[AngularJS] 일주일 탐방기 (0) | 2016.07.06 |
처음 AngularJS를 접했을 때 가장 눈에 띄는 특징이 바로 양방향 데이터 바인딩(Two-way Data Binding)이었다.
개인적으로, 자동 바인딩의 참신함은 knockout.js에서 그 놀라움을 처음 발견하였는데, 어느덧 더욱 발전된 형태의 양방향 자동바인딩을 AngularJS에서 어여쁘게 제공하고 있지 않은가...
이는 단연 AngularJS의 가장 큰 장점이며, 이 프레임워크를 채택하는데 있어, 가장 주요한 기준일 것이다.
AngularJS의 양방향 바인딩은, 응용프로그램의 '모델'과 이를 표시하는 '뷰'간의 자동 동기화를 지원하는 기능이다. 조금 다른 측면에서 설명하자만 자바스크립트 영역과 HTML 영역간의 데이터 동기화를 지원하는 것이다.
AngularJS 공식 사이트에서는 다른 전형적인 자바스크립트 템플릿 시스템과 AngularJS의 바인딩을 다음과 같이 그림으로 비교하고 있다.
Data Binding in Classical Template Systems - 여타 프레임워크는 단방향 바인딩만 지원 |
Data Binding in Angular Templates - AngularJS는 양방향 바인딩으로 동작 |
가장 보편적으로 사용되는 jQuery의 경우를 보면,
jQuery 탐색 기능을 사용하여 DOM에서 특정 HTML요소를 찾아내어 이벤트를 리스닝하고, DOM요소의 값을 파싱해서 이 값을 가지고 필요한 작업을 수행하고 했다.
AngularJS에서는 단순히 자바스크립트 속성을 정의하고 이것을 HTML에 바인딩만 하면 된다. 그것으로 끝이다.(by AngularJS in Action)
jQuery의 DOM 탐색, 이벤트 리스닝, 수동 동기화 코드로 가능한 바인딩 작업을 AngularJS에서는 극도로 단순화 시킨 것이다.
* 자동 데이터 바인딩
자동 데이터 바인딩을 위한 아주 간단한 코드를 한번 살펴보자
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body>
<div ng-app="">
<p>Input something in the input box:</p>
<p>Name : <input type="text" ng-model="name" placeholder="Enter name here"></p>
<h1>Hello {{name}} </h1>
</div>
</body>
</html>
모델과 뷰의 동기화를 위해 필요한 작업이 이것이 전부이다.
- ng-app 선언: 해당 영역이 AngularJS 응용프로그램으로 동작하도록 선언한다. 여기서는 모듈의 이름을 생략했는데, 실제 규모있는 앱을 제작하는 경우에는 적절한 이름을 지정하는 것을 권장한다.
- ng-model 지정: 모델로 사용할 값을 지정하는 것으로, 여기서는 input 박스에 name이라는 이름으로 모델을 지정했다. ng-model 디렉티브는 모델로부터 뷰로 데이터를 바인딩 시키기 위해 사용하며 이렇게 하면 AngularJS는 자동으로 $scope 객체의 속성으로 추가되어 뷰와 상호작용이 가능해진다.
- {{ }} 표현식: 모델로 지정한 값을 HTML로 바인딩하기 위한 표현식을 기술한다. 모델값이 변경되면 HTML에 표시된 값이 자동으로 즉각 반영될 것이다.
결과화면은 다음과 같댜. input박스의 값을 변경시키면 그 즉시 HTML에 표시된 값도 같이 변경된다.
* 양방향 (자동) 데이터 바인딩
그럼 이제, 좀더 일반적인 형태의 AngularJS 예제를 보자.
아래 코드는, AngularJS에 컨트롤러를 정의하고 이 컨트롤러를 통해 모델 정보에 접근하도록 했다.
이것이 보편적인 AngularJS 코딩 방법이다.
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function() {
main = this;
main.name= "mkex"; //모델로 사용될 정보. HTML영역에 바인딩되어 자동 동기화 됨
//자바스크립트 영역의 값의 변화를 보기 위한 메서드
main.getName = function(){
return alert(main.name);
}
});
</script>
<body>
<div ng-app="myApp" ng-controller="myCtrl as main">
Name: <input ng-model="main.name">
<h1>{{main.name}}</h1>
<button ng-click='main.getName()'>check</button>
</div>
</body>
</html>
input 박스의 데이터 변화는 HTML에 바인딩 된 값 뿐만 아니라, 자바스크립트 영역에 정의된 컨트롤러의 main.name 속성의 값도 자동으로 반영된다. 이것이 양방향 자동 바인딩 인 것이다.
(데이터 바인딩은 사용자가 속성값을 직접 조작할 수 있는 HTML 폼 같은 특별한 경우에는 양방향으로 동작한다 - by AngularJS in Action)
결과화면은 다음과 같다.
이 얼마나 극강의 생산성을 보여주는 기능인가? 자바스크립트 영역에 정의된 각종 데이터와 이 데이터를 투영하는 HTML요소의 자동 바인딩은 웹 응용프로그램의 무한한 가능성을 쉽게 시도할 수 있도록 할 것이다. 자바스크립트 객체만 잘 관리하면 HTML에 표시되는 것은 신경쓰지 않아도 되며 반대로 HTML로 부터 발생한 변경사항 역시 자바스크립트 객체로 바로 반영될 수 있어 SPA 실현은 쉬운 죽 먹기가 될 수 있다는 것이다. 놀라웁다~
* 단방향 데이터 바인딩(One-way Data Binding)
- 항상 그렇듯이, 훌륭한 기능도 필요치 않을 때가 종종 생긴다. AngularJS의 자동 양방향 데이터 바인딩은 진정 휼륭하지만, 어떤 경우에는 이 기능이 오버스펙일 경우가 있다.
또한 양방향 데이터 바인딩은 내부적으로 변경사항을 감시하는 메커니짐을 내포하고 있어 오버헤드가 생길 수 밖에 없다. (세상에 공짜는 없다)
이에, AngularJS 1.3부터는 one-time binding 이라는 일회성 바인딩 기능을 제공한다.
일회성 바인딩을 적용하기 위해 필요한 것은 단지, '더블클론(::)'의 지정 뿐이다.
이전의 예제에서 단방향 데이터 바인딩으로 변경해 보자.
아래 코드를 보면, 바뀐 것이라곤 고작 두 개의 콜론을 추가한 것 뿐이다. 이로써 최초 한번만 바인딩 되고 이후 변경사항은 HTML로 전파되지 않는다.
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function() {
main = this;
main.name= "mkex";
main.getName = function(){
return alert(main.name);
}
});
</script>
<body>
<div ng-app="myApp" ng-controller="myCtrl as main">
Name: <input ng-model="::main.name">
<h1>{{::main.name}}</h1>
<button ng-click='main.getName()'>check</button>
</div>
</body>
</html>
결과를 보면 더욱 명확하다.
최초 mkex라는 자바스크립트 영역의 속성 값이 HTML요소로 동기화 되었으며 이후 input 박스의 변화는 더 이상 HTML요소로 동기화 되지 않는다.
다만 alert 창을 보면 input 박스의 변경은 자바스크립트 속성의 변화만 일으킨다.
'모바일 > Javascript' 카테고리의 다른 글
[AngularJS] $scope과 controller-as 문법 (0) | 2016.07.12 |
---|---|
[AngularJS] Module (0) | 2016.07.12 |
[AngularJS] MVC Pattern in Angular (0) | 2016.07.10 |
[AngularJS] 일주일 탐방기 (0) | 2016.07.06 |
knockout.js (0) | 2013.11.14 |