Building a component based app with Angular 2
Developers have been trying to find a solution to architecture on complex web applications. The most recent answer to that is components, divide an interface in smaller and autonomous blocks to conquer maintainability and scalability.
In this case I will go through my thoughts and feelings on developing a web application using Angular 2, the upcoming version of this popular framework.
This writing belongs to a serie of articles about using components with Vue, React, Polymer and Angular 2.
Introduction to Angular 2
After years of being the framework behind almost every web application out there, Angular is getting a full rewrite focusing on the last development trends.
Even if you have a bunch of experience with Angular 1.x, mastering this new version will require several hours of reading and even learning a new language. A lot of strong decisions were taken during the development of this new version.
Writing components
If you have used some of the popular rendering libraries today and ES2015 classes, this Angular 2 component file will look familiar to you.
import { Component } from '@angular/core';
@Component({
selector: 'sample-component',
styleUrls: ['./sample-component.less'],
templateUrl: './sample-component.html'
})
export class SampleComponent {
firstProp: string;
secondProp = true;
someFn() {}
}
Inside the class declaration we define which properties will be used, indicating their type (:)
or assigning a value (=)
to it.
Don't freak out if this syntax looks a bit confusing, the reason is you're not looking at a .js file, but a .ts one actually.
Angular 2 documentation encourages you to use TypeScript and not JavaScript in your project, and when I say encourages you I mean forces you since its whole guide lacks of JavaScript documentation and only contains TypeScript examples.
First strong decision: TypeScript
Instead of being a class we can extend from, Component
is a decorator used to modify the component's behavior and creation. Decorators are part of the ES2016 specification and they are already available in TypeScript.
Inside of that decorator we indicate the files where the styles and template for our component will be, or they can also be placed in line.
@Component({
selector: 'sample-component',
styles: [ '.link { font-weight: 700; }' ],
template: '<a href="//github.com/jeremenichelli">GitHub</a>'
})
Decorators and types are not part of ECMAScript specs and browsers can't interpret them, so before running your code it will need to be compiled to JavaScript.
Transclution
Components in Angular will use web component technologies behind the scenes to encapsulate views and styles, which I think is a great decision by the team since it will improve performance as these features get supported natively on browsers.
Second strong decision: Web components behind the scenes
That said, when views are appended inside a shadowRoot
the content inside tags doesn't get rendered unless you specify so.
<sample-component>This content won't get displayed!</sample-component>
In Angular 2, content projection can be achieved by placing an <ng-content>
tag. Similar to how the <content>
tag works in vanilla web components.
In our sample component this would make content between the tags visible.
@Component({
selector: 'sample-component',
styles: [ '.link { font-weight: 700; }' ],
template: `
<a href="//github.com/jeremenichelli">
<ng-content></ng-content>
</a>
`
})
If you wanna go deeper into transclusion I recommend this article by Todd Motto.
Directives
Present in previous versions of Angular, directives are hints that will modify an element's behavior when Angular compiles a view. Notation has also changed for this version.
<ul *ngIf="avengers.length > 0">
<li *ngFor="let avenger of avengers">
<h2>{{ avenger.name }}</h2>
<p>{{ avenger.identity }}</p>
</li>
</ul>
*ngIf
will not render the element when its condition is false, useful to improve view times and prevent undefined errors when data is not available.
*ngFor
allows you to render an element more than once dependending on a collection of data present in the component, making your template smarter.
These are just a couple of a bunch of directives available out of the box.
Events
Not much has changed around binding events, in this version they need to be surrounded by ()
and expecting an expression or a function call.
<button (click)="onSave()">Save</button>
Events act like directives under the hood, and Angular provides some special ones like ngSubmit
to handle form submissions.
Properties
Along with the component hype, one way data flow became popular as a way to improve the coherence of a code, since communication is always expected to go from a parent to a child component.
This communication is achieved through properties or props, a feature already mentioned before in this article and also present in other libraries.
THe difference in Angular 2 is that properties are encapsulated and can't be modified externally, unless they are decorated.
// child component
import { Component, Input } from '@angular/core';
@Component({
selector: 'github-link',
template: `
<a href="//github.com/{{ user }}">
{{ user }} on GitHub
</a>
`
})
export class GitHubLink {
@Input() user: string;
}
If we don't place the @Input
decorator before the property, not only the parent component won't be able to pass a value to it, but it will also generate an error at compile time.
// parent component
import { Component } from '@angular/core';
@Component({
selector: 'app',
template: '<github-link [user]="users[1]"><github-link>'
})
export class App {
users = ['jeremenichelli', 'iamdustan'];
}
To bind data with a property we enclose it with square brackets []
in the component's template, and the same can be done to handle standard attributes.
Two way data binding
Not all libraries explicitly allow children to parent communication, the performance cost is big and most of the time is not the best solution.
Though not recommended, sometimes two way data flows are needed.
For form elements, Angular 2 provides an ngModel
directive available.
@Component({
selector: 'search-box',
template: `
<form action="/?">
<input type="text" [(ngModel)]="searchValue" name="searchValue" />
<button type="submit">Search</button>
</form>
`
})
export class SearchBox {
searchValue = '';
}
In two way bindings [()]
notation is used. The component's property will be modified by the changes in the input element.
For children to parent communication outside a form, documentation suggests various communication methods, with the parent listens to child events pattern being the cleanest option in my opinion.
For that we will need Angular's built-in event emitter and the Output
decorator.
// child component
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'search-box',
template: `
<form action="/?" (ngSubmit)="onSubmit()">
<input type="text" [(ngModel)]="searchValue" name="searchValue"/>
<button type="submit">Search</button>
</form>
`
})
export class SearchBox {
searchValue = '';
@Output() onSearchStarted = new EventEmitter<string>();
onSubmit() {
this.onSearchStarted.emit(this.searchValue);
}
}
After the custom event is created we can bind to a method present in the parent component as we do with natural web events.
// parent component
import { Component } from '@angular/core';
@Component({
selector: 'search-view',
template: `
<div class="view">
<search-box (onSearchStarted)="onSearchStarted($event)"> </search-box>
</div>
`
})
export class SearchView {
onSearchStarted(value: string) {
// event triggered... do something!
}
}
Notice the parameter's type is specified with <>
on the emitter and :
on the receiver function in TypeScript.
Styles
Same as views, styles are encapsulated using Shadow DOM when available, falling back to an attribute system like CSS modules or Vue tools do when not supported.
As you might have noticed styles can be passed in line or you can make reference to an external url that is later handled by the tool in charge of bundling or serving your files.
I had to make the distinction between bundling and serving since documentation indicates that, though you could safely use bundlers like webpack, the core team will recommend and push for SystemJS to drive your application build.
We can call that the third strong decision, right?
Third strong decision: SystemJS
But more on this later in the Ecosystem section.
Routing
Along with its official release, Angular 2 will bring a new [routing module][router]. It provides the basics for configuring paths, setting components and handling events.
It's not the goal of this article to go through the setup since it is pretty similar to existing router plugins and it still can change on its release candidate cycle, happening now.
Architecture
Angular is definitely a framework, not a library. It tells you exactly what to do and how to do it, there are not glitches or second approaches to explore.
That's the big difference between adding a library or a framework to a project.
On the first, you're supposed to be in control of your code structure and approaches while on the second you're letting it govern all those decisions for you.
You have to adopt ways which can bring both benefits and drawbacks. The first moments of developing with Angular 2 were painful. I had to understand TypeScript, new binding syntax, services and modules declarations, and more.
Even on your simplest Angular 2 application, your project folder will contain one or more files for each component, something that actually sounds good in terms of code organization.
But then, along with the app component file, I need an app module file specifying each service in a providers array, each component in a declarations array, and all the modules in an imports array even when some of them need to be imported in the component that is going to use them too.
Does that mean that in a really big application I'm going to have a declarations array with dozens of component names? Why do I need to import my services twice? What are the big benefits of this when almost everyone is already used to ES2015 module notation?
Those are some of the questions that came to my mind while developing a simple and small application that contained three times more files when comparing it to other frameworks or libraries.
The question will always be if the benefits can overcome the drawbacks, something that will depend on each project, company or developer.
Ecosystem
To simplify development Angular team is releasing an [angular cli][angular-cli] to kick off your app quickly. I tried to use it but couldn't really change configurations without breaking it.
Beware that Angular 2 is still in a release candidate state so documentation and tooling is not ready yet.
Instead I've used the [official angular seed][angular-seed] which looked cleaner and allowed me to do some modifications like moving to LESS.
This seed has webpack in charge of bundling your project while documentation points to SystemJS to load your application as I already mentioned.
I feel that because of the amount of initial configuration required, the decision of TypeScript being the language to go and the dissociation from common module patterns, Angular 2 will need that cli and seeds a lot to help developers without an extensive knowledge of tooling and last trends to adopt it.
Wrap-up
Angular has a really big community waiting for this release but there's a difference between today and 2010, the year the first Angular version came out. Today there is competition and [competition is doing really good][state-js-survey].
I hope the drawbacks I pointed out in the last part of this article become tiny in comparison to Angular's rendering times and performance, because if that happens I will be more than pleased to dig into TypeScript and all the configuration it requires to build an application with it.
All these thoughts came from building a [simple web app][angular-app] available on GitHub.
[router]: [//angular.io/docs/ts/latest/guide/router.html][angular-cli]: //github.com/angular/angular-cli
[angular-seed]: //github.com/angular/angular2-seed
[state-js-survey]: //medium.com/@sachagreif/the-state-of-javascript-front-end-frameworks-1a2d8a61510
[angular-app]: //github.com/jeremenichelli/movies/tree/master/results/angular
Do you want me to write for your publication? Click here to contact me via email.