[AngularJS] Route
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