Getting Past Hello World in Angular 2
This article is part of a web development series from Microsoft. Thank you for supporting the partners who make SitePoint possible.
So you’ve been through the basic Angular 2 application and now you want a bit more. If you’ve been reading about Angular 2 you’ve undoubtedly seen what might look like really odd syntax in templates. You may have heard something about an overhaul to dependency injection. Or maybe some of the features of ES7 (or ES2016, the version of JavaScript planned to come out next year) such as Decorators and Observables.
This post will give you a quick introduction to these concepts and how they apply to your Angular 2 applications. We won’t dive deep into these topics yet, but look for later posts that cover each area in more detail.
So, let’s begin at the beginning–setting up an environment.
Environment Setup
Angular2 source is written in TypeScript, a superset of JavaScript that allows for type definitions to be applied to your JavaScript code (you can find more information about TypeScript at www.typescriptlang.org).
In order to use TypeScript, we need to install the compiler which runs on top of Node.js. I’m using Node 0.12.6 and NPM 2.9.1. TypeScript should be installed automatically by NPM, but you can also install TypeScript globally using:
npm install –g typescript
# Test using
tsc -v
We are also using Gulp to build the project. If you don’t have Gulp installed globally, use this command:
npm install -g gulp-cli
Once you have Node installed, use these instructions to set up and start your simple application:
git clone https://github.com/DevelopIntelligenceBoulder/second-angular-app
cd second-angular-app
npm install
gulp go
In a browser, navigate to http://localhost:8080 (or the deployed version at http://secondangular2.azurewebsites.net/). You should see an extremely basic application like this:
This is a simple application that takes comma-separated ticker symbols as input and retrieves data including price and the stock name, displaying them in a simple list. This lacks styling, but we can use it to demonstrate a few ways to think about Angular 2 applications.
Components
Angular 2 applications are built using components. But what is a component? How do we set up a component in Angular 2?
At a high level, a component is a set of functionalities grouped together including a view, styles (if applicable) and a controller class that manages the functionality of the component. If you’re familiar with Angular 1, a component is basically a directive with a template and a controller. In fact, in Angular 2 a component is just a special type of directive encapsulating a reusable UI building block (minimally a controller class and a view).
We will look at the pieces that make up a component by examining the StockSearch component. In our simple application, the StockSearch component displays the input box and includes a child component called StockList that renders the data returned from the API.
Decorators
Decorators are a new feature in ES7 and you will see them identified in source code by a leading “@” symbol. Decorators are used to provide Angular with metadata about a class (and sometimes methods or properties) to they can be wired into the Angular framework. Decorators always come before the class or property they are decorating, so you will see a codeblock like this:
This block uses the component decorator to tell Angular that MyClass
is an Angular component.
Note: For an in-depth discussion of decorators and annotations (a special type of decorator used in Angular 2) see Pascal Precht‘s post on the difference between annotations and decorators.
Examining a Component
Let’s get into some code. Below is the StockSearch component, responsible for taking user input, calling a service to find stock data, and passing the data to the child StockList component. We will use this sample to talk about key parts of building an Angular 2 application:
import {Component, View} from 'angular2/core'
import {StockList} from './stockList'
import {StocksService} from '../services/stocks'
@Component({
selector: 'StockSearch',
providers: [StocksService]
})
@View({
template: `
<section>
<h3>Stock Price & Name Lookup:</h3>
<form (submit)="doSearch()">
<input [(ngModel)]="searchText"/>
</form>
<StockList [stocks]="stocks"></StockList>
</section>
`,
directives: [StockList]
})
export class StockSearch {
searchText: string;
stocks: Object[];
constructor(public stockService:StocksService) {}
doSearch() {
this.stockService.snapshot(this.searchText).subscribe(
(data) => {this.stocks= data},
(err) => {console.log('error!', err)}
);
}
}
(Screenshots in this post are from Visual Studio Code in Mac.)
While I’m relatively new to TypeScript, I’ve been enjoying it while writing Angular2 applications. TypeScript’s ability to present type information in your IDE or text editor is extremely helpful, especially when working with something new like Angular2.
I’ve been pleasantly surprised with how well Visual Studio Code integrates with TypeScript. In fact, it’s written in TypeScript and can parse it by default (no plugins required). When coding in Angular2 I found myself referring to the documentation frequently until I found the “Peek Definition” feature. This feature allows you to highlight any variable and use a keyboard shortcut (Opt-F12 on Mac) to look at the source of that variable. Since Angular2’s documentation exists in the source, I find I rarely need to go to the online documentation. Using Peek Definition on the Component decorator looks like this:
import statements
The import statements at the top of the file bring in dependencies used by this component. When you create a component, you will almost always use import {Component, View} from 'angular2/core
at the top. This line brings in the component and view decorators.
Angular 2 also requires that you explicitly tell the framework about any children components (or directives) you want to use. So the next line (import {StockList} from './stockList')
pulls in the StockList component.
Similarly, we identify any services we will need. In this case we want to be able to request data using the StockService, so the last import pulls it in.
selector and template
The selector
and template
properties passed to the component and view decorators are the only two configuration options required to build an Angular 2 component. Selector tells Angular how to find this component in HTML and it understands CSS selector syntax.
Unlike regular HTML, Angular 2 templates are case sensitive. So where in Angular 1 we used hyphens to make camelCase into kebab-case, we don’t need to do this in Angular 2. This means we can now use uppercase letters to start our component selectors, which distinguishes them from standard HTML. So:
selector: 'StockSearch' // matches <StockSearch></StockSearch>
selector: '.stockSearch' // matches <div class="stockSearch">
selector: '[stockSearch]' // matches <div stockSearch>
When the Angular compiler comes across HTML that matches one of these selectors, it creates an instance of the component class and renders the contents of the template property. You can use either template
to create an inline template, or templateUrl
to pass a URL, which contains the HTML template.
Dependency Injection (“providers” and “directives”)
If you’re familiar with Angular 1 you know it uses dependency injection to pass things like services, factories and values into other services, factories and controllers. Angular 2 also has dependency injection, but this version is a bit more robust.
In the case of the component above, the class (controller) is called StockSearch. StockSearch needs to use StockService to make a call to get stock data. Consider the following abbreviated code snippet:
import{StocksService} from '../services/stocks'
@Component({
selector: 'StockSearch',
providers: [StocksService]
})
export class StockSearch {
constructor(public stockService:StocksService) {}
}
As with other classes, the import
statement makes the StocksService
class available. We then pass it into the providers
property passed to the Component
decorator, which alerts Angular that we want to use dependency injection to create an instance of the StocksService
when it’s passed to the StockSearch constructor.
The constructor looks pretty bare-bones, but there’s actually quite a bit happening in this single line.
public keyword
The public keyword tells TypeScript to put the stockService
(camelCase, as opposed to the class name which is PascalCase) variable onto the instance of the StockSearch component. StockSearch’s methods will reference it as this.stockService
.
Type declaration
After public stockService
we have :StocksService
, which tells the compiler that the variable stockService
will be an instance of the StockService
class. Because we used the component decorator, type declarations on the constructor cause Angular’s dependency injection module to create an instance of the StocksService class and pass it to the StockSearch constructor. If you’re familiar with Angular 1, an analogous line of code would look like this:
One key difference between the Angular 1 and Angular 2 dependency injection systems is that in Angular 1 there’s just one large global namespace for all dependencies in your app. If you register a controller by calling app.controller('MyController', …)
in two different places in your code, the second one loaded will overwrite the first. (Note that this overwrite issue doesn’t apply to directives in Angular 1. Registering a directive by calling app.directive('myDirective', …)
in multiple places will add to the behavior of the earlier definition, not overwrite it.)
In Angular 2 the confusion is resolved by explicitly defining which directives are used in each view. So directives: [StockList]
tells Angular to look for the StockList component inside our view. Without that property defined, Angular wouldn’t do anything with the “<StockList>
” HTML tag.
Properties and Events
Looking at the template
passed to the View decorator, we see a new syntax where some attributes are surrounded by parentheses, some by brackets, and some by both.
Parentheses
Surrounding an attribute with parentheses tells Angular to listen for an event by the attribute name. So <form (submit) = "doSearch()">
tells Angular to listen for the submit
event on the form
component, and when a submit occurs the doSearch
method of the current component should be run (in this case, StockSearch
is the current component).
Square Brackets
Surrounding an attribute with square brackets tells Angular to parse the attribute value as an expression and assign the result of the expression to a property on the target component. So <StockList [stocks] = "stocks"></StockList>
will look for a stocks variable on the current component (StockSearch) and pass its value to the StockList component in a property also named “stocks.” In Angular 1 this required configuration using the Directive Definition Object, but looking just at the HTML there was no indication of how the attribute would be used.
<!-- Angular 2 we know [stocks] causes "stocks" to
parsed and passed to StockList component -->
<StockList[stocks] = "stocks"></StockList>
<!-- Angular 1 doesn't use brakcets. Looking just at
the HTML we don't know how the directive is using
the stocks attribute -->
<stock-list stocks = "stocks"></stock-list>
Square Brackets and Parentheses
This is a special syntax available to the ngModel
directive. It tells Angular to create a two-way binding. So <input [(ngModel)] = "searchText">
wires up an input element where text entered into the input gets applied to the searchText
property of the StockSearch component. Similarly, changes to this.searchText
inside the StockSearch instance cause an update to the value of the input.
<!-- Angular 2 requires both event and attribute
bindings to be explicitly clear we want 2-way binding -->
<input [(ngModel)] = "searchText">
<!-- Angular 1 looks simpler without the [(...)]. -->
<input ng-model = "searchText">
Services
A more in-depth explanation of services will be the subject of a future post, but our application defines a StockService that’s used to make an HTTP call to retrieve data. So let’s take a look at the service and walk through the pieces:
//a simple service
import {Injectable} from 'angular2/core';
import {Http, URLSearchParams} from 'angular2/http';
@Injectable()
export class StocksService {
// TS shortcut "public" to put http on this
constructor(public http:Http) {}
snapshot(symbols:string): any {
let params = new URLSearchParams();
params.set('symbols', symbols);
return this.http.get("/api/snapshot", {search: params})
.map(res => res.json()) // convert to JSON
.map(x => x.filter(y => y.name)); // Remove stocks w/no name
}
}
@Injectable() decorator
In Angular 2 we use the Injectable decorator to let Angular know a class should be registered with dependency injection. Without this, the providers property we used in the StockSearch component wouldn’t have worked, and dependency injection wouldn’t have created an instance of the service to pass into the controller. Therefore, if you want a class to be registered with dependency injection, use the Injectable decorator.
HTTP service
We’ll deep-dive into Angular 2’s HTTP library in the future, but the high-level summary is that it lets you call all your basic HTTP methods. Similar to the way we made StockService available to the StockSearch constructor, we are adding “http” on the “this” object using public http:Http
.
If you take a look at the “snapshot” method, you’ll see we call /api/snapshot
, then pass a configuration object with the search params. This is pretty straightforward, but at the bottom pay attention to .map(...)
. In Angular 1 (as in most modern HTTP libraries), an HTTP call returns a “promise.” But in Angular 2 we get an Observable.
An observable is like a promise but it can be resolved multiple times. There’s a lot to know about observables and we’ll cover them in upcoming posts, but for more information on observables, set aside some time to go through this post and exercises in order to get up to speed.
Tree of Components
We’ve looked at one component and a service it accesses. Angular 2 applications are built by constructing a tree of components, starting with a root application component. The application component should contain very little logic, as the primary purpose is to lay out the top-level pieces of your application.
//our root app component
import {Component, View} from 'angular2/core'
import {StockSearch} from './components/stockSearch';
@Component({
selector: 'App'
})
@View({
template: '
<header>
<h2>Second Angular 2 App</h2>
</header>
<StockSearch></StockSearch>',
directives: [StockSearch]
})
export class AppComponent {}
Now that we are familiar with the syntax of wiring up a component, we can see that the template outputs the header, followed by the StockSearch component. StockSearch is in the directive list, so Angular renders the component when it comes across the <StockSearch>
tag.
That’s about all there is to an application component; it is simply the root of our application. There can be only a single root in any Angular 2 application, and the final piece of the puzzle is telling Angular to look for it.
Bootstrapping our Application
In an Angular 2 application, we need to tell Angular when to start up. This is done using the bootstrap
method, passing in our AppComponent
along with other module dependencies. For those familiar with Angular 1, this is similar to the main angular.module(name, [dependencies...])
construct.
//our root app component
import {bootstrap} from 'angular2/platform/browser';
import {HTTP_PROVIDERS} from 'angular2/http';
import 'rxjs/Rx';
import {AppComponent} from './app';
bootstrap(AppComponent, [HTTP_PROVIDERS])
.catch(err =>console.error(err));
Notice the second argument is [HTTP_PROVIDERS]
. This is a special variable that references the classes defining Angular 2’s HTTP functionality, and it’s needed to let Angular know we can use the HTTP classes in our application. The Angular 1 equivalent looks something like this:
// JavaScript code to set up the app
angular.module('App', ['ngResource']);
<!-- Corresponding HTML tag pointing to "App" module -->
<div ng-app="App"></div>
Also notice we have import 'rxjs/Rx'
at the top. This pulls in the RxJs library so we get access to methods on Observables. Without adding this line we wouldn’t be able to run .map()
on the return from http.get()
method since the returned Observable wouldn’t have the map method available.
Once the application is bootstrapped, Angular looks for our root component in our markup. Looking back at the AppComponent
, the selector is set to app, so Angular will be looking for an element <app>
and will render the AppComponent
there:
<!DOCTYPE html>
<html>
<head>
<title>Basic Angular 2 Application Demonstration</title>
</head>
<body>
<App>
loading...
</App>
<script src="/lib/angular2-polyfills.js"></script>
<script src="/lib/system.js"></script>
<script src="/lib/Rx.js"></script>
<script src="/lib/angular2.dev.js"></script>
<script src="/lib/http.dev.js"></script>
<script>
System.config({
//packages defines our app package
packages: {'app': {defaultExtension: 'js'}}
});
System.import('app/bootstrap')
.catch(console.error.bind(console));
</script>
</body>
</html>
Summary
When considering Angular 2 applications, think about components and their children. At any point in the application you are examining some component, and that component knows what its children are. It’s a top-down approach where at each level you define compartmentalized functionality that eventually leads to a complete application.
More Hands-on with Web Development
This article is part of the web development series from Microsoft and DevelopIntelligence on practical JavaScript learning, open source projects, and interoperability best practices including Microsoft Edge browser and the new EdgeHTML rendering engine.
DevelopIntelligence offers instructor-led JavaScript Training, AngularJS Training and other Web Development Training for technical teams and organizations.
We encourage you to test across browsers and devices including Microsoft Edge – the default browser for Windows 10 – with free tools on dev.microsoftedge.com:
- Scan your site for out-of-date libraries, layout issues, and accessibility
- Download free virtual machines for Mac, Linux, and Windows
- Check Web Platform status across browsers including the Microsoft Edge roadmap
- Remotely test for Microsoft Edge on your own device
More in-depth learning from our engineers and evangelists:
- Interoperability best practices (series):
- Coding Lab on GitHub: Cross-browser testing and best practices
- Woah, I can test Edge & IE on a Mac & Linux! (from Rey Bango)
- Advancing JavaScript without Breaking the Web (from Christian Heilmann)
- Unleash 3D rendering with WebGL (from David Catuhe)
- Hosted web apps and web platform innovations (from Kiril Seksenov)
Our community open source projects:
- vorlon.JS (cross-device remote JavaScript testing)
- manifoldJS (deploy cross-platform hosted web apps)
- babylonJS (3D graphics made easy)
More free tools and back-end web dev stuff:
- Visual Studio Code (lightweight code-editor for Mac, Linux, or Windows)
- Visual Studio Dev Essentials (free, subscription-based training and cloud benefits)
- Code with node.JS with trial on Azure Cloud