첫 번째 AngularJS 앱을 위한 단계별 자습서
게시 됨: 2022-03-11AngularJS 란 무엇입니까?
AngularJS는 Google에서 개발한 JavaScript MVC 프레임워크로, 이를 통해 잘 구조화되고 쉽게 테스트할 수 있으며 유지 관리 가능한 프런트 엔드 애플리케이션을 구축할 수 있습니다.
왜 사용해야 합니까?
아직 AngularJS를 시도하지 않았다면 놓치고 있는 것입니다. 프레임워크는 코드를 줄이고 유연성을 높이는 모듈 방식으로 잘 구조화된 풍부한 클라이언트 측 응용 프로그램을 빌드하는 데 도움이 되는 긴밀하게 통합된 도구 집합으로 구성됩니다.
AngularJS는 마크업에 기능을 추가하고 강력한 동적 템플릿을 만들 수 있도록 하는 지시문을 제공하여 HTML을 확장합니다. 필요를 채우고 모든 DOM 조작 논리를 추상화하는 재사용 가능한 구성 요소를 만들어 고유한 지시문을 만들 수도 있습니다.
또한 양방향 데이터 바인딩을 구현하여 HTML(보기)을 JavaScript 개체(모델)에 원활하게 연결합니다. 간단히 말해서, 이것은 모델의 모든 업데이트가 DOM 조작이나 이벤트 처리(예: jQuery 사용) 없이도 뷰에 즉시 반영된다는 것을 의미합니다.
마지막으로, 나는 서버 통신에 관한 유연성 때문에 Angular를 좋아합니다. 대부분의 JavaScript MVC 프레임워크와 마찬가지로 RESTful 웹 API를 통해 앱을 제공할 수 있는 한 모든 서버 측 기술로 작업할 수 있습니다. 그러나 Angular는 코드를 극적으로 단순화하고 API 호출을 재사용 가능한 서비스로 추상화할 수 있는 XHR 위에 서비스도 제공합니다. 결과적으로 모델과 비즈니스 로직을 프런트 엔드로 이동하고 백엔드에 구애받지 않는 웹 앱을 빌드할 수 있습니다. 이 게시물에서는 한 번에 한 단계씩 그 작업을 수행합니다.
자, 어디서부터 시작해야 할까요?
먼저 빌드하려는 앱의 특성을 결정합시다. 이 가이드에서는 백엔드에 너무 많은 시간을 할애하지 않기를 원하므로 스포츠 피드 앱과 같이 인터넷에서 쉽게 얻을 수 있는 데이터를 기반으로 작성합니다!
저는 우연히 자동차 경주와 Formula 1의 열렬한 팬이 되었기 때문에 autosport API 서비스를 백엔드로 사용할 것입니다. 운 좋게도 Ergast의 직원들은 우리에게 완벽할 무료 모터스포츠 API를 제공할 만큼 친절합니다.
우리가 무엇을 만들 것인지 살짝 엿보려면 라이브 데모를 살펴보십시오. 데모를 멋지게 꾸미고 Angular 템플릿을 보여주기 위해 WrapBootstrap에서 Bootstrap 테마를 적용했지만 이 기사는 CSS에 관한 것이 아니므로 예제에서 추상화하고 생략하겠습니다.
시작하기 튜토리얼
일부 상용구로 예제 앱을 시작하겠습니다. Angular-seed 프로젝트는 부트스트랩을 위한 훌륭한 스켈레톤을 제공할 뿐만 아니라 Karma 및 Jasmine을 사용한 단위 테스트의 토대를 마련하기 때문에 권장합니다(이 데모에서는 테스트를 수행하지 않으므로 그냥 지금은 제쳐두고, 단위 및 종단 간 테스트를 위한 프로젝트 설정에 대한 자세한 내용은 이 자습서의 2부를 참조하세요.
편집(2014년 5월): 이 튜토리얼을 작성한 이후로 Angular-seed 프로젝트는 몇 가지 큰 변화를 겪었습니다(Bower를 패키지 관리자로 추가한 것을 포함). 프로젝트 배포 방법에 대해 의문이 있는 경우 참조 가이드의 첫 번째 섹션을 빠르게 살펴보세요. 이 튜토리얼의 2부에서 Bower는 다른 도구들보다 더 자세히 설명되어 있습니다.
자, 이제 저장소를 복제하고 종속성을 설치했으므로 앱의 골격은 다음과 같습니다.
이제 코딩을 시작할 수 있습니다. 레이싱 챔피언십에 대한 스포츠 피드를 만들려고 하므로 가장 관련성이 높은 보기 인 챔피언십 테이블부터 시작하겠습니다.
범위 내에 정의된 드라이버 목록이 이미 있고(가독성을 위해) CSS를 무시하면 HTML은 다음과 같이 보일 수 있습니다.
<body ng-app="F1FeederApp" ng-controller="driversController"> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table> </body>
이 템플릿에서 가장 먼저 알 수 있는 것은 표현식("{{" 및 "}}")을 사용하여 변수 값을 반환한다는 것입니다. AngularJS 개발에서 표현식을 사용하면 원하는 값을 반환하기 위해 일부 계산을 실행할 수 있습니다. 일부 유효한 표현식은 다음과 같습니다.
-
{{ 1 + 1 }}
-
{{ 946757880 | date }}
-
{{ user.name }}
실제로 표현식은 JavaScript와 유사한 스니펫입니다. 그러나 매우 강력함에도 불구하고 표현식을 사용하여 더 높은 수준의 논리를 구현해서는 안 됩니다. 이를 위해 지시문을 사용합니다.
기본 지시문 이해하기
두 번째로 알 수 있는 것은 일반적인 마크업에서는 볼 수 없는 ng-attributes
의 존재입니다. 지시사항입니다.
높은 수준에서 지시문은 지정된 동작을 DOM 요소에 첨부(또는 변환, 교체 등)하도록 AngularJS에 지시하는 마커(예: 속성, 태그 및 클래스 이름)입니다. 우리가 이미 본 것을 살펴 보겠습니다.
ng-app
지시문은 앱의 범위를 정의하는 부트스트랩을 담당합니다. AngularJS에서는 동일한 페이지 내에 여러 앱을 가질 수 있으므로 이 지시문은 각각의 고유한 앱이 시작하고 끝나는 위치를 정의합니다.ng-controller
지시문은 뷰를 담당할 컨트롤러를 정의합니다. 이 경우 드라이버 목록(driversList
)을 제공하는driversController
를 표시합니다.ng-repeat
지시문은 가장 일반적으로 사용되는 것 중 하나이며 컬렉션을 반복할 때 템플릿 범위를 정의하는 역할을 합니다. 위의 예에서, 이것은driversList
의 각 드라이버에 대해 테이블의 한 줄을 복제합니다.
컨트롤러 추가
물론 컨트롤러가 없는 뷰는 아무 소용이 없습니다. controllers.js에 driversController
를 추가해 보겠습니다.
angular.module('F1FeederApp.controllers', []). controller('driversController', function($scope) { $scope.driversList = [ { Driver: { givenName: 'Sebastian', familyName: 'Vettel' }, points: 322, nationality: "German", Constructors: [ {name: "Red Bull"} ] }, { Driver: { givenName: 'Fernando', familyName: 'Alonso' }, points: 207, nationality: "Spanish", Constructors: [ {name: "Ferrari"} ] } ]; });
컨트롤러에 매개변수로 전달하는 $scope
변수를 눈치채셨을 것입니다. $scope
변수는 컨트롤러와 뷰를 연결해야 합니다. 특히 템플릿 내에서 사용될 모든 데이터를 보유합니다. 여기에 추가하는 모든 항목(예: 위의 예제의 driversList
)은 보기에서 직접 액세스할 수 있습니다. 지금은 더미(정적) 데이터 배열로 작업해 보겠습니다. 이 배열은 나중에 API 서비스로 대체할 것입니다.
이제 다음을 app.js에 추가합니다.
angular.module('F1FeederApp', [ 'F1FeederApp.controllers' ]);
이 코드 줄을 사용하여 실제로 앱을 초기화하고 앱이 의존하는 모듈을 등록합니다. 나중에 해당 파일( app.js
)로 돌아올 것입니다.
이제 index.html
에 모든 것을 함께 넣어 봅시다.
<!DOCTYPE html> <html> <head> <title>F-1 Feeder</title> </head> <body ng-app="F1FeederApp" ng-controller="driversController"> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table> <script src="bower_components/angular/angular.js"></script> <script src="bower_components/angular-route/angular-route.js"></script> <script src="js/app.js"></script> <script src="js/services.js"></script> <script src="js/controllers.js"></script> </body> </html>
모듈로 사소한 실수, 이제 앱을 부팅하고 (정적) 드라이버 목록을 확인할 수 있습니다.
참고: 앱을 디버깅하고 브라우저 내에서 모델과 범위를 시각화하는 데 도움이 필요한 경우 Chrome용 멋진 Batarang 플러그인을 살펴보는 것이 좋습니다.
서버에서 데이터 로드
컨트롤러의 데이터를 뷰에 표시하는 방법을 이미 알고 있으므로 실제로 RESTful 서버에서 라이브 데이터를 가져올 때입니다.
HTTP 서버와의 통신을 용이하게 하기 위해 AngularJS는 $http
및 $resource
서비스를 제공합니다. 전자는 XMLHttpRequest 또는 JSONP 위에 있는 레이어에 불과하지만 후자는 더 높은 수준의 추상화를 제공합니다. 우리는 $http
를 사용할 것입니다.
컨트롤러에서 서버 API 호출을 추상화하기 위해 다음을 services.js
에 추가하여 데이터를 가져오고 $http
주변의 래퍼 역할을 하는 사용자 지정 서비스를 생성해 보겠습니다.
angular.module('F1FeederApp.services', []). factory('ergastAPIservice', function($http) { var ergastAPI = {}; ergastAPI.getDrivers = function() { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK' }); } return ergastAPI; });
처음 두 줄로 새 모듈( F1FeederApp.services
)을 만들고 해당 모듈 내에 서비스를 등록합니다( ergastAPIservice
). $http
를 해당 서비스에 대한 매개변수로 전달합니다. 이것은 Angular의 의존성 주입 엔진에 우리의 새로운 서비스가 $http
서비스를 필요로 하거나 의존함을 알려줍니다.
비슷한 방식으로 Angular에 새 모듈을 앱에 포함하도록 알려야 합니다. 기존 코드를 다음으로 교체하여 app.js
에 등록해 보겠습니다.
angular.module('F1FeederApp', [ 'F1FeederApp.controllers', 'F1FeederApp.services' ]);
이제 controller.js
를 약간 수정하고 ergastAPIservice
를 종속성으로 포함하기만 하면 됩니다.
angular.module('F1FeederApp.controllers', []). controller('driversController', function($scope, ergastAPIservice) { $scope.nameFilter = null; $scope.driversList = []; ergastAPIservice.getDrivers().success(function (response) { //Dig into the responde to get the relevant data $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings; }); });
이제 앱을 다시 로드하고 결과를 확인하세요. 템플릿을 변경하지 않았지만 범위에 nameFilter
변수를 추가했습니다. 그 변수를 사용하도록 합시다.
필터
엄청난! 우리는 기능적인 컨트롤러를 가지고 있습니다. 그러나 드라이버 목록만 표시됩니다. 목록을 필터링할 간단한 텍스트 검색 입력을 구현하여 몇 가지 기능을 추가해 보겠습니다. <body>
태그 바로 아래 index.html
에 다음 줄을 추가해 보겠습니다.
<input type="text" ng-model="nameFilter" placeholder="Search..."/>
이제 ng-model
지시문을 사용하고 있습니다. 이 지시문은 텍스트 필드를 $scope.nameFilter
변수에 바인딩하고 해당 값이 입력 값과 항상 최신 상태인지 확인합니다. 이제 index.html을 한 번 더 방문하여 ng-repeat
지시문이 포함된 행을 약간 조정해 보겠습니다.

<tr ng-repeat="driver in driversList | filter: nameFilter">
이 줄은 ng-repeat
에게 데이터를 출력하기 전에 driversList
배열이 nameFilter
에 저장된 값으로 필터링되어야 함을 알려줍니다.
이 시점에서 양방향 데이터 바인딩이 시작됩니다. 검색 필드에 값이 입력될 때마다 Angular는 연결된 $scope.nameFilter
가 새 값으로 업데이트되도록 즉시 확인합니다. 바인딩은 양방향으로 작동하기 때문에 nameFilter
값이 업데이트되는 순간 연결된 두 번째 지시문(즉, ng-repeat
)도 새 값을 가져오고 보기가 즉시 업데이트됩니다.
앱을 새로고침하고 검색창을 확인하세요.
이 필터는 사용하지 않는 속성을 포함하여 모델의 모든 속성에서 키워드를 찾습니다. Driver.givenName
및 Driver.familyName
으로만 필터링하고 싶다고 가정해 보겠습니다. 먼저 $scope.driversList = [];
바로 아래에 driversController
를 추가합니다. 선:
$scope.searchFilter = function (driver) { var keyword = new RegExp($scope.nameFilter, 'i'); return !$scope.nameFilter || keyword.test(driver.Driver.givenName) || keyword.test(driver.Driver.familyName); };
이제 index.html
로 돌아가서 ng-repeat
지시문이 포함된 행을 업데이트합니다.
<tr ng-repeat="driver in driversList | filter: searchFilter">
앱을 한 번 더 새로고침하면 이름으로 검색할 수 있습니다.
경로
우리의 다음 목표는 각 드라이버를 클릭하고 경력 세부 정보를 볼 수 있는 드라이버 세부 정보 페이지를 만드는 것입니다.
먼저 이러한 다양한 애플리케이션 경로 를 처리하는 데 도움이 되는 $routeProvider
서비스( app.js
에 있음)를 포함시켜 보겠습니다. 그런 다음 챔피언십 테이블과 드라이버 세부 정보를 위한 두 가지 경로를 추가합니다. 다음은 새로운 app.js
.
angular.module('F1FeederApp', [ 'F1FeederApp.services', 'F1FeederApp.controllers', 'ngRoute' ]). config(['$routeProvider', function($routeProvider) { $routeProvider. when("/drivers", {templateUrl: "partials/drivers.html", controller: "driversController"}). when("/drivers/:id", {templateUrl: "partials/driver.html", controller: "driverController"}). otherwise({redirectTo: '/drivers'}); }]);
그 변경으로 http://domain/#/drivers
로 이동하면 driversController
를 로드하고 partials/drivers.html
에서 렌더링할 부분 보기를 찾습니다. 하지만 기다려! 아직 부분적인 견해가 없잖아요? 그것들도 만들어야 합니다.
부분 보기
AngularJS를 사용하면 경로를 특정 컨트롤러 및 보기에 바인딩할 수 있습니다.
그러나 먼저 이러한 부분 보기를 렌더링할 위치를 Angular에 알려야 합니다. 이를 위해 ng-view
지시문을 사용하여 index.html
을 수정하여 다음을 미러링합니다.
<!DOCTYPE html> <html> <head> <title>F-1 Feeder</title> </head> <body ng-app="F1FeederApp"> <ng-view></ng-view> <script src="bower_components/angular/angular.js"></script> <script src="bower_components/angular-route/angular-route.js"></script> <script src="js/app.js"></script> <script src="js/services.js"></script> <script src="js/controllers.js"></script> </body> </html>
이제 앱 경로를 탐색할 때마다 Angular는 연결된 보기를 로드하고 <ng-view>
태그 대신 렌더링합니다. 우리가 해야 할 일은 partials/drivers.html
이라는 파일을 만들고 여기에 챔피언십 테이블 HTML을 넣는 것뿐입니다. 또한 이 기회를 사용하여 드라이버 이름을 드라이버 세부 정보 경로에 연결할 것입니다.
<input type="text" ng-model="nameFilter" placeholder="Search..."/> <table> <thead> <tr><th colspan="4">Drivers Championship Standings</th></tr> </thead> <tbody> <tr ng-repeat="driver in driversList | filter: searchFilter"> <td>{{$index + 1}}</td> <td> <img src="img/flags/{{driver.Driver.nationality}}.png" /> <a href="#/drivers/{{driver.Driver.driverId}}"> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </a> </td> <td>{{driver.Constructors[0].name}}</td> <td>{{driver.points}}</td> </tr> </tbody> </table>
마지막으로 세부 정보 페이지에 표시할 내용을 결정하겠습니다. 운전자에 대한 모든 관련 정보(예: 출생, 국적)를 최근 결과가 포함된 표와 함께 요약하면 어떻습니까? 이를 위해 services.js
에 다음을 추가합니다.
angular.module('F1FeederApp.services', []) .factory('ergastAPIservice', function($http) { var ergastAPI = {}; ergastAPI.getDrivers = function() { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK' }); } ergastAPI.getDriverDetails = function(id) { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/driverStandings.json?callback=JSON_CALLBACK' }); } ergastAPI.getDriverRaces = function(id) { return $http({ method: 'JSONP', url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/results.json?callback=JSON_CALLBACK' }); } return ergastAPI; });
이번에는 특정 운전자에게만 해당되는 정보를 검색할 수 있도록 서비스에 운전자의 ID를 제공합니다. 이제 controllers.js
를 수정합니다.
angular.module('F1FeederApp.controllers', []). /* Drivers controller */ controller('driversController', function($scope, ergastAPIservice) { $scope.nameFilter = null; $scope.driversList = []; $scope.searchFilter = function (driver) { var re = new RegExp($scope.nameFilter, 'i'); return !$scope.nameFilter || re.test(driver.Driver.givenName) || re.test(driver.Driver.familyName); }; ergastAPIservice.getDrivers().success(function (response) { //Digging into the response to get the relevant data $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings; }); }). /* Driver controller */ controller('driverController', function($scope, $routeParams, ergastAPIservice) { $scope.id = $routeParams.id; $scope.races = []; $scope.driver = null; ergastAPIservice.getDriverDetails($scope.id).success(function (response) { $scope.driver = response.MRData.StandingsTable.StandingsLists[0].DriverStandings[0]; }); ergastAPIservice.getDriverRaces($scope.id).success(function (response) { $scope.races = response.MRData.RaceTable.Races; }); });
여기서 주목해야 할 중요한 점은 $routeParams
서비스를 드라이버 컨트롤러에 주입했다는 것입니다. 이 서비스를 사용하면 $routeParams.id
를 사용하여 URL 매개변수(이 경우 :id
)에 액세스할 수 있습니다.
범위에 데이터가 있으므로 나머지 부분 보기만 필요합니다. partials/driver.html
이라는 파일을 만들고 다음을 추가해 보겠습니다.
<section> <a href="./#/drivers"><- Back to drivers list</a> <nav class="main-nav"> <div class="driver-picture"> <div class="avatar"> <img ng-show="driver" src="img/drivers/{{driver.Driver.driverId}}.png" /> <img ng-show="driver" src="img/flags/{{driver.Driver.nationality}}.png" /><br/> {{driver.Driver.givenName}} {{driver.Driver.familyName}} </div> </div> <div class="driver-status"> Country: {{driver.Driver.nationality}} <br/> Team: {{driver.Constructors[0].name}}<br/> Birth: {{driver.Driver.dateOfBirth}}<br/> <a href="{{driver.Driver.url}}" target="_blank">Biography</a> </div> </nav> <div class="main-content"> <table class="result-table"> <thead> <tr><th colspan="5">Formula 1 2013 Results</th></tr> </thead> <tbody> <tr> <td>Round</td> <td>Grand Prix</td> <td>Team</td> <td>Grid</td> <td>Race</td> </tr> <tr ng-repeat="race in races"> <td>{{race.round}}</td> <td><img src="img/flags/{{race.Circuit.Location.country}}.png" />{{race.raceName}}</td> <td>{{race.Results[0].Constructor.name}}</td> <td>{{race.Results[0].grid}}</td> <td>{{race.Results[0].position}}</td> </tr> </tbody> </table> </div> </section>
이제 ng-show
지시문을 잘 사용하고 있음을 주목하세요. 이 지시문은 제공된 표현식이 true
(즉, false
도 null
도 아님)인 경우에만 HTML 요소를 표시합니다. 이 경우 아바타는 컨트롤러에 의해 범위에 드라이버 개체가 로드된 후에만 표시됩니다.
마무리 손질
CSS를 추가하고 페이지를 렌더링합니다. 다음과 같이 끝나야 합니다.
이제 앱을 실행하고 두 경로가 원하는 대로 작동하는지 확인할 준비가 되었습니다. 사용자의 탐색 기능을 향상시키기 위해 index.html
에 정적 메뉴를 추가할 수도 있습니다. 가능성은 무한합니다.
편집(2014년 5월): 이 튜토리얼에서 빌드한 코드의 다운로드 가능한 버전에 대한 많은 요청을 받았습니다. 따라서 여기에서 릴리스하기로 결정했습니다(CSS가 제거됨). 그러나 이 가이드에는 훨씬 더 유용하고 효과적인 학습 연습이 될 자신의 손으로 동일한 애플리케이션을 구축하는 데 필요한 모든 단일 단계가 포함되어 있기 때문에 다운로드를 권장하지 않습니다 .
결론
튜토리얼의 이 시점에서 우리는 (Formula 1 피더와 같은) 간단한 앱을 작성하는 데 필요한 모든 것을 다루었습니다. 라이브 데모의 나머지 각 페이지(예: 생성자 챔피언십 테이블, 팀 세부 정보, 일정)는 여기에서 검토한 것과 동일한 기본 구조 및 개념을 공유합니다.
마지막으로 Angular는 매우 강력한 프레임워크이며 제공해야 하는 모든 측면에서 표면을 거의 긁지 않았습니다. 이 튜토리얼의 2부에서는 Angular가 피어 프론트 엔드 MVC 프레임워크 중에서 눈에 띄는 이유인 테스트 가능성에 대한 예를 제공합니다. Karma로 단위 테스트를 작성하고 실행하고 Yeomen, Grunt 및 Bower와 지속적으로 통합하는 과정과 이 환상적인 프런트 엔드 프레임워크의 기타 장점을 검토합니다.