With over 1.1 million developers world wide who use the Angular 1.x framework - it is a framework that undoubtedly became a major web technology used today. It's no surprise that upcoming Angular 2 raises lots of questions about its new features, enhancements and generally the framework itself.
In this article we will focus on a small Angular 2 project where we will take a closer look on Angular 2 development and its key changes. To kick off I would recommend an introduction video presented by AngularConnect
Pascal's Triangle
OK, let's start. First of all - we should start with Pascal's Triangle itself. For those who doesn't know what it is - I would recommend a wikipedia page. The core of Pascal's Triangle is the binomial theorem. Basically, we will construct the triangle by applying the binomial coefficient, according to expression:
The binomial formula itself looks like:
Hence, to determine the particular row of our pascal's triangle we can use the following construction (as stated on wikipedia example):
..and so on. Simple. The result should produce something as follows image:
That's for the pascal's triangle. Let's move on.
Coding options
Before we proceed to actual coding it's worth to mention the coding options we have. You're certainly aware of JavaScript's ES5, ES6, ES2015 or TypeScript. While it's perfectly valid to use ES5 I will stick with TypeScript because of its types and interface features. The Angular 2 itself is actually written in TypeScript.
Angular 1.x - factory, service or provider?
Generally speaking if you need to share your code on a single place (DRY) in multiple controllers in Angular 1 you've had a couple of options such as factory, service or provider. All of them used in specific scenarios.
Angular 2 is much more elegant and all you need to do is to just create a Class. The binomial calculations mentioned above are perfect example of a code that should be placed on a single place - in our example it is in binomial-service.ts. Fair enough. How to use it? Let's compare Angular 1 with Angular 2 by example.
angular.module('app').service('BinomialService', BinomialService); function BinomialService() { //..some internal methods.. this.getPascalTriangle = function(size) { var triangle = []; for (var i=0;i < size;i++) { triangle.push(this.calc(i)) } return triangle; } } import { Injectable } from '@angular/core'; @Injectable() export class BinomialService { //..some internal methods.. getPascalTriangle = (size) => { var triangle = []; for (var i = 0; i < size; i++) { triangle.push(this.calc(i)); } return triangle; } }
Notice two things:
While export keyword is pretty straightforward (the class simply behaves as a module and is not seen outside without setting it as external) the question comes to oddly looking @Injectable line. This is called a decorator and comes from TypeScript. Angular 2 uses a couple of decorators frequently used, such as:
Declares that a class has dependencies that should be injected into the constructor when the dependency injector is creating an instance of this class.
..and that's exactly what is happening. As mentioned - out service needs to actually be injected to the constructor of consuming component or class. As you can see in app.component.ts.
@Component({ selector: 'pt-app', templateUrl: './pascal-triangle.html', directives: [TriangleParams], providers: [BinomialService] }) export class AppComponent implements OnInits, AfterViewInit { constructor(private _binomialService: BinomialService) { } // Methods refreshData(size: number){ this.rows = this._binomialService.getPascalTriangle(size); } }
..couple of things to mention here. The first is the providers: [BinomialService] within the @Component decorator. This tells the Angular to consume the service class. Probably the most important is Dependency Injection part though. Generally speaking dependency injection is very important design pattern and as official Angular docs say: ..we really can't build an Angular application without it. Therefore this line with constructor is very important. Within the class is then accessed by this keyword since it sits on class itself.
Angular 2 basic differences
Before we move on let's just go through some basic differences
<input type="number" ng-model="sizeModel" /> <input type="number" [(ngModel)]="sizeModel" /> <div ng-bind="message" class="centered"></div> <div [innerText]="message" class="centered"></div> This actually is one of the thing I like the most. Instead of having plenty of directives for any particular property you simply use square brackets [] to define any HTML property itself. Dozens of directives from Angular 1 are hence not needed anymore. <span ng-bind="num" class="num-box" ng-mouseenter="hoverNum = num" ng-mouseleave="hoverNum = ''"> </span> <span [innerText]="num" class="num-box" (mouseenter)="hoverNum = num" (mouseleave)="hoverNum = ''"> </span>
Where square brackets [] define any HTML property then classic brackets () define any HTML element events. Very elegant.
<span ng-repeat="num in row" ng-bind="num" class="num-box" ng-mouseenter="hoverNum = num" ng-mouseleave="hoverNum = ''"> </span> <span *ngFor="let num of row" [innerText]="num" class="num-box" (mouseenter)="hoverNum = num" (mouseleave)="hoverNum = ''"> </span>
Here it's very important to notice a star * before ngFor. Any star represents a structural directive. Structural directive means it actually changes or defines the DOM structure itself. There are couple of other structural directives such as:
Unidirectional Data Flow
The data binding in Angular 1 was based on well known digest cycles. Angular 2 uses something called unidirectional data flow. Forget about $apply, repeated digest cycle, $watch.
Facebook's Flux is also based on an idea of unidirectional data flow. Basically it says that you can no longer update the view after it has been composed. This process is much better when it comes to performance since it avoids repeated $digest cycles.
Component life cycle hooks
As stated in official documentation:
"A Component has a lifecycle managed by Angular itself. Angular creates it, renders it, creates and renders its children, checks it when its data-bound properties change, and destroys it before removing it from the DOM. Angular offers component lifecycle hooks that give us visibility into these key moments and the ability to act when they occur."
When you look at the app.component.ts:
@Component({ selector: 'pt-app', templateUrl: './pascal-triangle.html', directives: [TriangleParams], providers: [BinomialService] }) export class AppComponent implements OnInits, AfterViewInit {
@Component itself is a decorator that provides meta data for its class which is actually being exported.
The OnInits and AfterViewInit are our "hook" moments. Angular follows its internal sequence of specific moments which forms its life cycle and hooks allow you to tap into these moments to actually build a project.
Here we have a code of our main component where we also need to access the child component params.component.ts and also the binomial-service.ts:
import { Component, OnChanges, Input, Output, View, EventEmitter, OnInit } from '@angular/core'; import { BinomialService } from './binomial-service'; import { ViewChild, AfterViewInit } from '@angular/core'; import { TriangleParams } from './params.component'; @Component({ selector: 'pt-app', templateUrl: './pascal-triangle.html', directives: [TriangleParams], providers: [BinomialService] }) export class AppComponent implements OnInits, AfterViewInit { constructor(private _binomialService: BinomialService) { } @ViewChild(TriangleParams) triangleParams:TriangleParams; rows: []; // Methods refreshData(size: number){ this.rows = this._binomialService.getPascalTriangle(size); } // Lifecycle hooks ngOnInit(){ this.rows = []; } ngAfterViewInit() { this.refreshData(this.triangleParams.triangleSize); } }
Notice that to access the params component we have to:
Change Detection
While the official documentation still leaves the change detection section empty it is pretty natural to follow property setter as you can see here:
get sizeModel() { return this.triangleSize; } set sizeModel(value) { this.triangleSize = value; if ((this.valid = this.validate()) == false) return; this.sizeChange.emit({ value: this.triangleSize }); }
and a view which looks like this:
<nav class="centered"> Triangle size = <input type="number" [(ngModel)]="sizeModel" /> </nav>
As you can see - every time model changes the property setter is triggered. You can also notice the event emitter being triggered which is going to be described on the following lines.
Communication between components
You've surely came across a need to communicate between controllers in Angular 1. Features such as $broadcast or $emit. These do not exist anymore in Angular 2. We have couple of other options though and they are well described on official documentation.
In our demo we just need to notify a parent component about changes in child component. For which we will EventEmitters. So basically within the child component: import { Component, OnChanges, Input, Output, View, EventEmitter, OnInit } from '@angular/core'; @Component({ selector: 'triangle-params', templateUrl: './triangle-params.html' }) export class TriangleParams { @Input() triangleSize; @Output() sizeChange = new EventEmitter(); //.. set sizeModel(value) { //.. this.sizeChange.emit({ value: this.triangleSize }); } }
..we have to define the target property by which the communication will be performed. That's the @Output() line. Again it is worth reading through the official documentation. To quote:
Input properties usually receive data values. Output properties expose event producers, such as EventEmitter objects.
..which is exactly what we do here. We are exposing an event procedure. So in the parent component's view we can define the (sizeChange) event:
<triangle-params [triangleSize]="6" (sizeChange)="sizeChanged($event);"></triangle-params>
..which triggers our sizeChanged method within the parent component class and passes $event as an argument which we emitted from the child component including the new value.
@Component({ selector: 'pt-app', templateUrl: './pascal-triangle.html', directives: [TriangleParams], providers: [BinomialService] }) export class AppComponent implements OnInits, AfterViewInit { //.. sizeChanged(event) { this.refreshData(event.value); } //.. }
As mentioned above. This process is well described on the official documentation.
Summary
0 Comments
Leave a Reply. |
AboutBlog about my programming experiments, tests and so on. Categories
All
Archives
November 2016
|