A Step-by-Step Guide to Your First AngularJS App

What is AngularJS?


AngularJS is a JavaScript MVC framework developed by Google that lets you
build well structured, easily testable, and maintainable front-end
applications.

And Why Should I Use It?

If you haven’t tried AngularJS yet, you’re missing out. The framework
consists of a tightly integrated toolset that will help you build well
structured, rich client-side applications in a modular fashion—with less code
and more flexibility.

AngularJS extends HTML by providing directives that add functionality to your markup and
allow you to create powerful dynamic templates. You can also create your own
directives, crafting reusable components that fill your needs and abstracting
away all the DOM manipulation logic.

It also implements two-ways data binding, connecting your HTML (views) to
your JavaScript objects (models) seamlessly. In simple terms, this means that
any update on your model will be immediately reflected in your view without the
need for any DOM manipulation or event handling (e.g., with jQuery).

Angular provides services on top
of XHR that dramatically simplify your code and allow you to abstract API calls
into reusable services. With that, you can move your model and business logic to
the front-end and build back-end agnostic web apps.

Finally, I love Angular because of its flexibility regarding server
communication. Like most JavaScript MVC frameworks, it lets you work with any
server-side technology as long as it can serve your app through a RESTful web
API. But Angular also provides services on top of XHR that
dramatically simplify your code and allow you to abstract API calls into
reusable services. As a result, you can move your model and business logic to
the front-end and build back-end agnostic web apps. In this post, we’ll do just
that, one step at a time.

So, Where Do I Begin?

First, let’s decide the nature of the app we want to build. In this guide,
we’d prefer not to spend too much time on the back-end, so we’ll write something
based on data that’s easily attainable on the Internet—like a sports feed
app!

Since I happen to be a huge fan of motor racing and Formula 1, I’ll use an
autosport API service to act as our back-end. Luckily, the guys at Ergast are kind enough to provide a free motorsport API
that will be perfect for us.

For a sneak peak at what we’re going to build, take a look at the live demo. To
prettify the demo and show off some Angular templating, I applied the
awesome Plastic Admin Bootstrap theme; but seeing as this
article isn’t about CSS, I’ll just abstract it away from the examples and leave
it out.

Getting Started

Let’s kickstart our example app with some boilerplate. I recommend
the angular-seed project as it not only provides you with a
great skeleton for bootstrapping, but also sets the ground for unit testing
with Karma andJasmine (we
won’t be doing any testing in this demo, so we’ll just leave that stuff aside
for now).

You app’s skeleton will look like this:

Now we can start coding. As we’re trying to build a sports feed for a racing
championship, let’s begin with the most relevant view: the
championship table.

Given that you already have a drivers list defined within your scope (hang
with me—we’ll get there), and ignoring any CSS (for readability), your HTML
might look like:

<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}}&nbsp;{{driver.Driver.familyName}}
</td>
<td>{{driver.Constructors[0].name}}</td>
<td>{{driver.points}}</td>
</tr>
</tbody>
</table>
</body>

The first thing you’ll notice in this template is the use of expressions
(“{{“ and “}}”) to return variable values. In AngularJS, expressions allow you
to execute some computation in order to return a desired value. Some valid
expressions would be:

  • {{ 1 + 1 }}

  • {{ 946757880 | date }}

  • {{ user.name }}

Effectively, expressions are JavaScript-like snippets. But despite being very
powerful, you shouldn’t use expressions to implement any higher-level logic. For
that, we use directives.

Understanding Basic Directives

The second thing you’ll notice is the presence
of ng-attributes, which you wouldn’t see in typical markup.
Those are directives.

At a high level, directives are markers (such as attributes, tags, and class
names) that tell AngularJS to attach a given behaviour to a DOM element (or
transform it, replace it, etc.). Let’s take a look at the ones we’ve seen
already:

  • The ng-app directive is responsible for
    bootstrapping your app defining its scope. In AngularJS, you can have multiple
    apps within the same page, so this directive defines where each distinct app
    starts and ends.

  • The ng-controller directive defines which controller
    will be in charge of your view. In this case, we denote
    the driversController, which will provide our list of
    drivers (driversList).

  • The ng-repeat directive is one of the most commonly
    used and serves to define your template scope when looping through
    collections. In the example above, it replicates a line in the table for each
    driver in driversList.

Adding Controllers

Of course, there’s no use for our view without a controller. Let’s
add driversController to our controllers.js:

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"}
]
}
];
});

You may have noticed the $scope we’re passing as
parameter to the controller. The $scope variable is
supposed to link your controller and views. In particular, it holds all the data
that will be used within your template. Anything you add to it (like
the driversList in the above example) will be directly
accessible in your views. For now, lets just work with a dummy (static) data
array, which we will replace later with our API service.

Now, add this to app.js:

angular.module(‘F1FeederApp‘, [
‘F1FeederApp.controllers‘
]);

With this line of code, we actually initialize our app and register the
modules on which it depends. We’ll come back to that file (app.js) later on.

Now let’s put everything together in index.html:

<!DOCTYPE html>
<html>
<head>
<title>F-1 Feeder</title>
<script src="lib/angular/angular.js"></script>
<script src="lib/angular/angular-route.js"></script>
<script type="text/javascript" src="js/app.js"></script>
<script type="text/javascript" src="js/controllers.js"></script>
<script type="text/javascript" src="js/services.js"></script>
</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}}&nbsp;{{driver.Driver.familyName}}
</td>
<td>{{driver.Constructors[0].name}}</td>
<td>{{driver.points}}</td>
</tr>
</tbody>
</table>
</body>
</html>

Modulo minor mistakes, you can now boot up your app and check your (static)
list of drivers.

Note: If you need help debugging your app and visualizing your models and
scope within the browser, I recommend taking a look at the awesome Batarang plugin for Chrome.

Editor‘s note: want posts just like this
delivered straight to your inbox? Subscribe below to receive our latest
engineering articles.

We will never share your email with any
3rd party or spam you

Loading Data From the Server


Since we already know how to display our controller’s data in our view, it’s
time to actually fetch live data from a RESTful server.

To facilitate communication with HTTP servers, AngularJS provides
the $http and $resource services.
The former is but a layer on top of XMLHttpRequest or JSONP, while the latter provides a higher level of
abstraction. We’ll use $http.

To abstract our server API calls from the controller, let’s create our own
custom service which will fetch our data and act as a wrapper
around $http. Add this to your 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‘
});
}

return ergastAPI;
});


With the first two lines, we create a new module
(F1FeederApp.services) and register a service within that module
(ergastAPIservice). Notice that we
pass $http as a parameter to that service. This tells
Angular’sdependency
injection
 engine that our new service requires (or depends
on
) the $http service.

In a similar fashion, we need to tell Angular to include our new module into
our app. Let’s register it with app.js, replacing our existing code with:

angular.module(‘F1FeederApp‘, [
‘F1FeederApp.controllers‘,
‘F1FeederApp.services‘
]);

Now, all we need to do is tweak our controller.js a bit,
include ergastAPIservice as a dependency and we’ll be
good to go:

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;
});
});


Now reload your app and check out the result. Notice that we didn’t make any
changes to our template, but we added
nameFilter variable to our scope. Let’s put that
variable to use.

Filters

Great! We have a functional controller. But it only shows a list of drivers.
Let’s add some functionality by implementing a simple text search input which
will filter our list. Add the following line to your index.html, right below
the <body> tag:

<input type="text" ng-model="nameFilter" placeholder="Search..."/>

We are now making use of the ng-model directive; this
directive will bind your text field to
the $scope.nameFilter variable and make sure that its
value is always up-to-date with the input value. Now, let’s visit index.html one
more time and make a small adjustment to the line that contains
the ng-repeat directive.

<tr ng-repeat="driver in driversList | filter: nameFilter">

This line tells ng-repeat that before outputting the
data, the drivers array must be filtered by the value stored
in nameFilter.

At this point, two-way data binding kicks in: every time you input some value
in the search field, Angular immediately ensures that
the $scope.nameFilter that you associated to it is
updated with the new value. Since the binding works both ways, the moment
the nameFilter value is updated, the second directive
associated to it (the ng-repeat) also gets the new value and
the view is updated immediately.

Reload your app and check out your search bar.

Notice that this filter will look for the keyword on all attributes of the
model, including the ones you′re not using. Let’s say you only want to filter
by Driver.givenName and Driver.familyName:
First add to your driversController, right below
the $scope.driversList = []; line:

$scope.searchFilter = function (driver) {
var keyword = new RegExp($scope.nameFilter, ‘i‘);
return !$scope.nameFilter || keyword.test(driver.Driver.givenName) || keyword.test(driver.Driver.familyName);
};

Now, back to index.html, update the line that contains
the ng-repeat directive:

<tr ng-repeat="driver in driversList | filter: searchFilter">

Reload the app one more time and now you have a search by name.

Routes

Our next goal is to create a driver details page which will let us click on
each driver and see his/her career details.

First, let’s include the $routeProvider service (in
app.js) which will help us deal with these varied application
routes
. Then, we’ll add two such routes: one for the championship table,
and another for the driver details. Here’s our new app.’s:

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‘});
}]);

With that change, navigating to “http://domain/#/drivers
will load the driversController and look for the partial
view to render in “partials/drivers.html”. But wait! We don’t have any partial
views yet, right? We’ll need to create those too.

Partial Views

AngularJS will allow you to bind your routes to specific controllers and
views. But first, we need to tell Angular where to render these partial views.
For that, we’ll use the ng-view directive. Modify your
index.html to mirror the following:

<!DOCTYPE html>
<html>
<head>
<title>F-1 Feeder</title>
<script src="lib/angular/angular.js"></script>
<script src="lib/angular/angular-route.js"></script>
<script type="text/javascript" src="js/app.js"></script>
<script type="text/javascript" src="js/controllers.js"></script>
<script type="text/javascript" src="js/services.js"></script>
</head>
<body ng-app="F1FeederApp">
<ng-view></ng-view>
</body>
</html>

Now, whenever you navigate through your app routes, Angular will load the
associated view and render it in place of
the <ng-view> tag. All you need to do is create a
file called partials/drivers.html and put your championship table HTML there.
We’ll also use this chance to link the driver name to our driver details
route:

<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}}&nbsp;{{driver.Driver.familyName}}
</a>
</td>
<td>{{driver.Constructors[0].name}}</td>
<td>{{driver.points}}</td>
</tr>
</tbody>
</table>

Finally, let’s decide what we want to show in the details page. How about a
summary of all the relevant facts about the driver (e.g., birth, nationality)
along with a table containing his/her recent results. Add to 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;
});


This time, we provide the driver’s ID to the service so that we retrieve the
information relevant solely to a specific driver. Now, modify
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;
});
});


The important thing to notice here is that we just injected
the $routeParams service into the driver controller. This
service will allow us to access our URL parameters (for
the :id, in this case)
using $routeParams.id.

Now that we have our data in the scope, we only need the remaining partial
view. Create a file called partials/driver.html and add:

<section id="main">

<nav id="secondary" 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}}<br/>{{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>


Notice that we’re now putting the ng-show directive to
good use. This directive will only show the HTML element if the expression you
provided is true (i.e., neither false,
nor null). In this case, the avatar will only show up once the
driver object has been loaded into the scope by the controller.

Finishing Touches

Add in a bunch of CSS and render your page. You should end up with something
like this:

You’re now ready to fire up your app and make sure both routes are working as
desired. You could also add a static menu to index.html to improve the user’s
navigation capabilities. The possibilities are endless.

Conclusion

At this point in the tutorial, we’ve covered everything you’d need to write a
simple app (like a Formula 1 feeder). Each of the remaining pages in the live
demo (e.g., constructor championship table, team details, calendar) share the
same basic structure and concepts that we’ve reviewed here.

Finally, keep in mind that Angular is a very powerful framework and we’ve
barely scratched the surface in terms of everything it has to offer. In a future
post, we’ll give examples of why Angular stands out among its peer front-end MVC
frameworks: testability. We’ll review the process of writing and running unit
tests withKarma,
achieving continuous integration with YeomenGrunt, and Bower, and other strengths of this fantastic front-end
framework.

A Step-by-Step Guide to Your First AngularJS App,码迷,mamicode.com

时间: 2024-10-18 07:43:36

A Step-by-Step Guide to Your First AngularJS App的相关文章

Step by Step Guide: How to Configure SSL/TLS on ORACLE RAC (with SCAN) (Doc ID 1448841.1)

In this Document   Goal   Solution   References Applies to: Advanced Networking Option - Version 11.2.0.2 to 12.2.0.1 [Release 11.2 to 12.2]Oracle Net Services - Version 12.2.1.2.0 to 12.2.1.2.0 [Release 12.2]Information in this document applies to a

数论之高次同余方程(Baby Step Giant Step + 拓展BSGS)

什么叫高次同余方程?说白了就是解决这样一个问题: A^x=B(mod C),求最小的x值. baby step giant step算法 题目条件:C是素数(事实上,A与C互质就可以.为什么?在BSGS算法中是要求a^m在%c条件下的逆元的,如果a.c不互质根本就没有逆元.) 如果x有解,那么0<=x<C,为什么? 我们可以回忆一下欧拉定理: 对于c是素数的情况,φ(c)=c-1 那么既然我们知道a^0=1,a^φ(c)=1(在%c的条件下).那么0~φ(c)必定是一个循环节(不一定是最小的)

Git Step by Step – (8) Git的merge和rebase

前面一篇文章中提到了"git pull"等价于"git fetch"加上"git merge",然后还提到了pull命令支持rebase模式,这篇文章就介绍一下merge和rebase之间有什么差别. 由于我们主要是想看看merge跟rebase之间的区别,这里就是用本地仓库的分支进行演示了. merge 其实在介绍分支的那篇文章中已经介绍过了一些分支merge的内容,这里就进行一些补充和总结. 下面我们基于本地一个仓库开始介绍,当前仓库的分支情

C# 2012 step by step 学习笔记8 CHAPTER 9 Creating Value types with enumerations and Structures

C# 2012 step by step 学习笔记8 CHAPTER 9 Creating Value types with enumerations and Structures things about 1. Declare an enumeration type. 2. Create and use an enumeration type. 3. Declare a structure type. 4. Create and use a structure type. 5. Explain

Linux Booting Process: A step by step tutorial for understanding Linux boot sequence

One of the most remarkable achievement in the history of mankind is computers. Another amazing fact about this remarkable achievement called computers is that its a collection of different electronic components, and they work together in coordination

C++开发WPF,Step by Step

示例代码 使用C++来开发WPF,主要是如何在MFC(Win32)的窗口中Host WPF的Page.下面我就做个详细的介绍. 一.创建工程, 由于MFC的Wizard会生成很多用不到的代码,所以我准备从一个空的工程开始创建一个MFC的工程. a)         打开VS2005,菜单File->New->Projects-, 左面选择Visual C++->Win32,右面选择Win32 Console Application,给工程起个名字CPlusPlus_WPF, Ok进入下一

数据库设计 Step by Step (1)——扬帆启航

引言:一直在从事数据库开发和设计工作,也看了一些书籍,算是略有心得.很久之前就想针 对关系数据库设计进行整理.总结,但因为种种原因迟迟没有动手,主要还是惰性使然.今天也算是痛下决心开始这项卓绝又令我兴奋的工作.这将是一个系列的文 章,我将以讲座式的口吻展开讨论(个人偷懒,这里的总结直接拿去公司培训新人用). 系列的第一讲我们先来回答下面几个问题 数据库是大楼的根基 大多数程序员都很急切,在了解基本需求之后希望很快的进入到编码阶段(可能只有产出代码才能反映工作量),对于数据库设计思考得比较少. 这

数据库设计 Step by Step (2)——数据库生命周期

引言:数据库设计 Step by Step (1)得到这么多朋友的关注着实出乎了我的意外.这也坚定了我把这一系列的博文写好的决心.近来工作上的事务比较繁重,加之我期望这个系列的文章能尽可能的系统.完整,需要花很多时间整理.思考数据库设计的各种资料,所以文章的更新速度可能会慢一些,也希望大家能够谅解. 系列的第二讲我们将站在高处俯瞰一下数据库的生命周期,了解数据库设计的整体流程 数据库生命周期 大家对软件生命周期较为熟悉,数据库也有其生命周期,如下图所示. 图(1)数据库生命周期 数据库的生命周期

Shell Step by Step (3) —— Stdin &amp;amp; if

4.输入输出 #! /bin/bash # Read users input and then get his name read -p "Please input your first name: " firstName read -p "Please input your last name: " lastName echo -e "Your full name is: $firstName $lastName" read使用方法: read