Angular 2 Components: What You Need to Know

David Meza

June 08th, 2016

From the 7th to the 13th of November you can save up to 80% on some of our very best Angular content - along with our hottest React eBooks and video courses. If you're curious about the cutting-edge of modern web development we think you should click here and invest in your skills...

The Angular team introduced quite a few changes in version 2 of the framework, and components are one of the important ones. If you are familiar with Angular 1 applications, components are actually a form of directives that are extended with template-oriented features. In addition, components are optimized for better performance and simpler configuration than a directive as Angular doesn’t support all its features. Also, while a component is technically a directive, it is so distinctive and central to Angular 2 applications that you’ll find that it is often separated as a different ingredient for the architecture of an application.

So, what is a component? In simple words, a component is a building block of an application that controls a part of your screen real estate or your “view”. It does one thing, and it does it well. For example, you may have a component to display a list of active chats in a messaging app (which, in turn, may have child components to display the details of the chat or the actual conversation). Or you may have an input field that uses Angular’s two-way data binding to keep your markup in sync with your JavaScript code. Or, at the most elementary level, you may have a component that substitutes an HTML template with no special functionality just because you wanted to break down something complex into smaller, more manageable parts.

Now, I don’t believe too much in learning something by only reading about it, so let’s get your hands dirty and write your own component to see some sample usage. I will assume that you already have Typescript installed and have done the initial configuration required for any Angular 2 app. If you haven’t, you can check out how to do so by clicking on this link.

You may have already seen a component at its most basic level:

import {Component} from 'angular2/core';

@Component({
  selector: 'my-app',
  template: '<h1>{{ title }}</h1>'
})

export class AppComponent {
  title = 'Hello World!';
}

That’s it! That’s all you really need to have a component. Three things are happening here:

  1. You are importing the Component class from the Angular 2 core package.
  2. You are using a Typescript decorator to attach some metadata to your AppComponent class. If you don’t know what a decorator is, it is simply a function that extends your class with Angular code so that it becomes an Angular component. Otherwise, it would just be a plain class with no relation to the Angular framework. In the options, you defined a selector, which is the tag name used in the HTML code so that Angular can find where to insert your component, and a template, which is applied to the inner contents of the selector tag. You may notice that we also used interpolation to bind the component data and display the value of the public variable in the template.
  3. You are exporting your AppComponent class so that you can import it elsewhere (in this case, you would import it in your main script so that you can bootstrap your application).

That’s a good start, but let’s get into a more complex example that showcases other powerful features of Angular and Typescript/ES2015. In the following example, I've decided to stuff everything into one component. However, if you'd like to stick to best practices and divide the code into different components and services or if you get lost at any point, you can check out the finished/refactored example here. Without any further ado, let’s make a quick page that displays a list of products. Let’s start with the index:

<html>
  <head>
    <title>Products</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">    

    <script src="node_modules/es6-shim/es6-shim.min.js"></script>
    <script src="node_modules/systemjs/dist/system-polyfills.js"></script>

    <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>
    <script src="node_modules/rxjs/bundles/Rx.js"></script>
    <script src="node_modules/angular2/bundles/angular2.dev.js"></script>

    <link rel="stylesheet" href="styles.css">

    <script>
      System.config({
        packages: {        
          app: {
            format: 'register',
            defaultExtension: 'js'
          }
        }
      });
      System.import('app/main')
            .then(null, console.error.bind(console));
    </script>

  </head>

  <body>
    <my-app>Loading...</my-app>
  </body>

</html>

There’s nothing out of the ordinary going on here. You are just importing all of the necessary scripts for your application to work as demonstrated in the quick-start.

The app/main.ts file should already look somewhat similar to this:

import {bootstrap} from ‘angular2/platform/browser’
import {AppComponent} from ‘./app.component’

bootstrap(AppComponent);

Here, we imported the bootstrap function from the Angular 2 package and an AppComponent class from the local directory. Then, we initialized the application.

First, create a product class that defines the constructor and type definition of any products made. Then, create app/product.ts, as follows:

export class Product {
id: number;
price: number;
name: string;
}

Next, you will create an app.component.ts file, which is where the magic happens. I've decided to stuff everything in here for demonstration purposes, but ideally, you would want to extract the products array into its own service, the HTML template into its own file, and the product details into its own component. This is how the component will look:

import {Component} from 'angular2/core';
import {Product} from './product'

@Component({
  selector: 'my-app',
  template: `
  <h1>{{title}}</h1>
  <ul class="products">
    <li *ngFor="#product of products"
      [class.selected]="product === selectedProduct"
      (click)="onSelect(product)">
      <span class="badge">{{product.id}}</span> {{product.name}}
    </li>
  </ul>
  <div *ngIf="selectedProduct">
    <h2>{{selectedProduct.name}} details!</h2>
    <div><label>id: </label>{{selectedProduct.id}}</div>
    <div><label>Price: </label>{{selectedProduct.price | currency: 'USD': true }}</div>
    <div>
      <label>name: </label>
      <input [(ngModel)]="selectedProduct.name" placeholder="name"/>
    </div>
  </div>
  `,
  styleUrls: ['app/app.component.css']
})

export class AppComponent {
  title = 'My Products';
  products = PRODUCTS;
  
  selectedProduct: Product;
      
  onSelect(product: Product) { this.selectedProduct = product; }
}

const PRODUCTS: Product[] = [
  { "id": 1, "price": 45.12, "name": "TV Stand" },
  { "id": 2, "price": 25.12, "name": "BBQ Grill" },
  { "id": 3, "price": 43.12, "name": "Magic Carpet" },
  { "id": 4, "price": 12.12, "name": "Instant liquidifier" },
  { "id": 5, "price": 9.12, "name": "Box of puppies" },
  { "id": 6, "price": 7.34, "name": "Laptop Desk" },
  { "id": 7, "price": 5.34, "name": "Water Heater" },
  { "id": 8, "price": 4.34, "name": "Smart Microwave" },
  { "id": 9, "price": 93.34, "name": "Circus Elephant" },
  { "id": 10, "price": 87.34, "name": "Tinted Window" }
];

The app/app.component.css file will look something similar to this:

.selected {
  background-color: #CFD8DC !important;
  color: white;
}
.products {
  margin: 0 0 2em 0;
  list-style-type: none;
  padding: 0;
  width: 15em;
}
.products li {
  position: relative;
  min-height: 2em;
  cursor: pointer;
  position: relative;
  left: 0;
  background-color: #EEE;
  margin: .5em;
  padding: .3em 0;
  border-radius: 4px;
  font-size: 16px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: #3F51B5;
  display: block;
  width: 100%;

  -webkit-transition: all 0.3s ease;
  -moz-transition:    all 0.3s ease;
  -o-transition:      all 0.3s ease;
  -ms-transition:     all 0.3s ease;
  transition:         all 0.3s ease;
}
.products li.selected:hover {
  background-color: #BBD8DC !important;
  color: white;
}
.products li:hover {
  color: #607D8B;
  background-color: #DDD;
  left: .1em;
  color: #3F51B5;
  text-decoration: none;
  font-size: 1.2em;
  background-color: rgba(0,0,0,0.01);
}
.products .text {
  position: relative;
  top: -3px;
}
.products .badge {
  display: inline-block;
  font-size: small;
  color: white;
  padding: 0.8em 0.7em 0 0.7em;
  background-color: #607D8B;
  line-height: 1em;
  position: relative;
  left: -1px;
  top: 0;
  height: 2em;
  margin-right: .8em;
  border-radius: 4px 0 0 4px;
}

I'll explain what is happening:

  1. We imported from Component so that we can decorate your new component and imported Product so that we can create an array of products and have access to Typescript type infererences.
  2. We decorated our component with a “my-app” selector property, which finds <my-app></my-app> tags and inserts our component there. I decided to define the template in this file instead of using a URL so that I can demonstrate how handy the ES2015 template string syntax is (no more long strings or plus-separated strings). Finally, the styleUrls property uses an absolute file path, and any styles applied will only affect the template in this scope.
  3. The actual component only has a few properties outside of the decorator configuration. It has a title that you can bind to the template, a products array that will iterate in the markup, a selectedProduct variable that is a scope variable that will initialize as undefined and an onSelect method that will be run every time you click on a list item.
  4. Finally, define a constant (const because I've hardcoded it in and it won't change in runtime) PRODUCTS array to mock an object that is usually returned by a service after an external request.

Also worth noting are the following:

  • As you are using Typescript, you can make inferences about what type of data your variables will hold. For example, you may have noticed that I defined the Product type whenever I knew that this the only kind of object I want to allow for this variable or to be passed to a function.
  • Angular 2 has different property prefixes, and if you would like to learn when to use each one, you can check out this Stack Overflow question.

That's it! You now have a bit more complex component that has a particular functionality. As I previously mentioned, this could be refactored, and that would look something similar to this:

import {Component, OnInit} from 'angular2/core';
import {Product} from './product';
import {ProductDetailComponent} from './product-detail.component';
import {ProductService} from './product.service';

@Component({
    selector: 'my-app',
    templateUrl: 'app/app.component.html',
    styleUrls: ['app/app.component.css'],
    directives: [ProductDetailComponent],
    providers: [ProductService]
})

export class AppComponent implements OnInit {
    title = 'Products';
    products: Product[];
    selectedProduct: Product;
    
    constructor(private _productService: ProductService) { }
    
    getProducts() {
        this._productService.getProducts().then(products => this.products = products);
    }
    ngOnInit() {
        this.getProducts();
    }
    onSelect(product: Product) { this.selectedProduct = product; }
}

In this example, you get your product data from a service and separate the product detail template into a child component, which is much more modular. I hope you've enjoyed reading this post.

About this author

David Meza is an AngularJS developer at the City of Raleigh. He is passionate about software engineering and learning new programming languages and frameworks. He is most familiar working with Ruby, Rails, and PostgreSQL in the backend and HTML5, CSS3, JavaScript, and AngularJS in the frontend. He can be found at here.

comments powered by Disqus