Are you moving from writing AngularJS to Angular apps? There's a lot to learn, but check out this quick guide on mistakes to avoid first!
Upgrading any project can be a big task, and with the vast changes between AngularJS and Angular it can almost seem like a whole new framework to learn! Fortunately Angular is a serious improvement over AngularJS, and if there's one thing to get excited about, it's no more fighting with UI router. ;) A few months back I was re-reading John Papa's AngularJS styleguide and it was awesome to reflect back on those patterns and optimizations that actually are no longer needed as they're now solved by Angular. There is a bit of a learning curve and some "gotchyas" that you may stumble on, so here's a quick guide on mistakes to avoid when moving from AngularJS to Angular development.
1. Not Knowing How to Google for Angular
Googles branding strategy has been to distinguish Angular version 2 and everything above as "Angular" and Angular 1 as "AngularJS". Unfortunately this is not helpful when googling and trying to differentiate results. Searching for "Angular 2 xyz" will normally get you to results you need faster. The demo code tools have changed too - we've traded in Codepen for Stackblitz, and it's pretty awesome.
2. Not Installing & Using the Angular CLI
I remember the conversation very vividly with my future CTO at the time about the Angular CLI and webpack while I was interviewing for a lead front-end dev role. I was coming from AngularJS work, and the CTO mentioned they still hadn't gotten unit tests set up and the front-end needed a LOT of work. I was the one to fight to convert from Grunt to Webpack and set up unit tests on my previous AngularJS projects, and upon mentioning this I could not believe him when he said the Angular CLI "just works". "Just works" did not match my experience with anything Webpack, I was used to hours of debugging, agonizing with loaders, and a general fight of configuration tweaks when any changes needed to be made.
Ya'll, my CTO wasn't lying about the Angular CLI - it is brilliant and DOES just work. When I accepted the job and took over the front-end on my first Angular project it had been set up by backend devs, no shade, and there wasn't even a css compiler being used. I lasted maybe a day doing UI changes before crying and needing to switch to less. I rolled up my sleeves, loaded up the documentation, prepared for a fight, then ran:
ng config defaults.styleExt=less
I switched the file extensions from .css to .less, updated their references in their components, and everything just worked. No compiler errors, no "missing an appropriate loader" messages, just a seamless development experience. I was shook. Angular CLI ftw.
Need to make a new component?
ng g component my-sweet-component
Need to make a new service to request some data from the server?
ng g service get-all-the-datas
Need a tabs UI element from a 3rd party library?
ng add ngx-bootstrap --component tabs
Life is good with the Angular CLI, so take the time to get familiar with it - you'll save a lot of keystrokes and have fewer compiler errors than spinning up new components & modules by hand. And when you get really comfortable, check out the power of schematics!
3. Not Taking the Time to Learn TypeScript Fundamentals
I have a bad habit of wanting to jump into projects and immediately start fixing bugs, and this was no different the first time I moved from doing AngularJS work to an Angular project with a mess of a front-end. In retrospect, taking a bit of time to walk through TypeScript fundamentals vs. my usual "learn-as-I-go" strategy would have been much more efficient and I would have spent less time debugging TypeScript errors thrown by the compiler early on.
I know using TypeScript can seem restrictive and demanding, but it doesn't take as much time as you may think to become familiar with and start using it, if you've been writing ES6 you're already half-way there. Before you know it, you'll love how quickly you're able to mentally parse and use the annotated code. You'll also be able to quickly take advantage of built-in Angular classes and interfaces.
The documentation at https://www.typescriptlang.org/ is great, or to get some quick hands-on learning experience you can take the Bitovi TypeScript training course in about 3 hours! (Not going to lie, I wish I had this training available when I first switched to writing Angular)
4. Getting Trolled by Syntax Changes
It's funny - I think the number one thing that trolled me when moving from AngularJS to Angular was the changing of the template looping syntax. The source of a vast majority of my in-browser compiler errors were due to typing "let season in season" before I gave up and started using Angular VS Code snippets. I am much happier now. = ) I'm a big advocate of using code snippets, especially for those situations where we know what we want our code to do, but are have a fat-finger day, are trying to write code before that first cup of coffee has been consumed, or are moving to a slightly different syntax with the same intent.
Iterating in AngularJS
<ul class="seasons">
<li class="season" ng-repeat="season in seasons">
Season {{season.seasonNumber}}
</li>
</ul>
Iterating in Angular
<ul class="seasons">
<li class="season" *ngFor="let season of seasons">
Season {{season.seasonNumber}}
</li>
</ul>
5. Not Learning the Basics of RxJS
RxJS can seem overwhelming with the paradigm shift of thinking in reactive programming, but it's ok to start with baby steps. Some basic concepts to learn are Observables, Subscriptions, and Operators.
Observables are lazy collections of multiple values over time.
Subscriptions are how we "subscribe" to observables to get their values.
Operators are methods to use on Observables to manipulate them. It's important to know Operators do not CHANGE Observables, but return new Observables based on the current Observable.
To demonstrate Observables and Subscriptions, let's look at creating services and fetching data in AngularJS and working with promises vs working with Observables in Angular.
Making an HTTP request
In building AngularJS applications you probably got pretty familiar with creating services, using $http to make HTTP requests, and returning promises:
'use strict';
SeasonService.$inject = []
class Seasons {
constructor($http, $ENV, SeasonService) {
this.$http = $http;
this.env = $ENV;
}
getSeasons() {
return this.$http({
method: 'GET',
url: this.env.APIurl + '/seasons'
}).then((results) => {
return results.data
})
}
getSeason(season_id) {
return this.$http({
method: 'GET',
url: this.env.APIurl + '/seasons/' + season_id
}).then((results) => {
return results.data
})
}
}
Seasons.$inject = ['$http', '$ENV', 'SeasonService'];
export default angular.module('seasons', [])
.service('Seasons', Seasons)
.name;
function seasonsView() {
return {
template: require('./partials/seasonsview.html'),
controllerAs: 'sv',
controller: seasonsViewCtrl
}
}
function seasonsViewCtrl($scope, Seasons) {
let sv = this;
sv.showSeasons = function() {
Seasons.getSeasons().then((response) => {
sv.seasons = response;
}).catch(() => {
sv.errorMessage = 'failed to fetch seasons. stop trying to make fetch happen';
})
}
}
seasonsViewCtrl.$inject = ['$scope', 'Seasons']
In Angular, the HttpClient API will return an Observable of the data collected. We can then subscribe to it in our component to get the latest value.
seasons.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class SeasonService {
public apiUrl: string = 'http://www.nokeynoshade.party/api';
constructor(private http: HttpClient) { }
getSeasons() {
return this.http.get(this.apiUrl + '/seasons');
}
getSeason(seasonId: number) {
return this.http.get(this.apiUrl + '/seasons/' + seasonId);
}
}
seasons.component.ts
import { Component, OnInit } from '@angular/core';
import { SeasonService } from './season.service';
@Component({
selector: 'dr-seasons',
templateUrl: './seasons.component.html',
styleUrls: ['./seasons.component.less']
})
export class SeasonsComponent implements OnInit {
public seasons: any = [];
constructor(
private seasonService: SeasonService
) { }
ngOnInit() {
this.seasonService.getSeasons().subscribe((seasons) => {
this.seasons = seasons;
})
}
}
Reactive programming is a big mindset shift, but to explore its power, let's look at a common scenario - we have some data we want to fetch, and we have some sort of loading UI component we'd like to display to our user to let them know we're working on getting that data.
Revisiting the code above, we might do something like this:
seasons.component.ts
import { Component, OnInit } from '@angular/core';
import { SeasonService } from './season.service';
@Component({
selector: 'dr-seasons',
templateUrl: './seasons.component.html',
styleUrls: ['./seasons.component.less']
})
export class SeasonsComponent implements OnInit {
public seasons: any = [];
//boolean value to indicate loading status
public seasonsLoading: boolean = false;
constructor(
private seasonService: SeasonService
) { }
ngOnInit() {
//setting value to true before seasons HTTP request is made
this.seasonsLoading = true;
this.seasonService.getSeasons().subscribe((seasons) => {
this.seasons = seasons;
//setting value to false once we have the data
this.seasonsLoading = false;
})
}
}
We may have template code that looks like this:
<h2>Seasons</h2>
<ng-container *ngIf="seasons.length">
<div class="row" *ngFor="let chunk of seasons | chunks: 4">
<mat-card *ngFor="let season of chunk" class="season-card">
<mat-card-header>
<mat-card-title>Season {{season.id}}</mat-card-title>
</mat-card-header>
<img routerLink="/seasons/{{season.id}}" mat-card-image src="{{season.image_url}}" alt="Season {{season.id}} promo photo">
</mat-card>
</div>
</ng-container>
<ng-container *ngIf="seasonsLoading"><mat-spinner></mat-spinner></ng-container>
A REACTIVE approach would look something like this:
import { Component, OnInit } from '@angular/core';
import { SeasonService } from './season.service';
import { map, startWith } from 'rxjs/operators';
import { Observable } from 'rxjs';
function sortBySeasonNumber(a,b) {
const seasonA = parseInt(a.seasonNumber, 10);
const seasonB = parseInt(b.seasonNumber, 10);
if (seasonA < seasonB)
return -1;
if (seasonA > seasonB)
return 1;
return 0;
}
@Component({
selector: 'dr-seasons',
templateUrl: './seasons.component.html',
styleUrls: ['./seasons.component.less']
})
export class SeasonsComponent implements OnInit {
public seasons$: Observable<any>;
constructor(
private seasonService: SeasonService
) { }
ngOnInit() {
this.seasons$ = this.seasonService.getSeasons().pipe(
map((response) => {
return {
value: response.sort(sortBySeasonNumber),
isLoading: false
}
}),
startWith({isLoading: true, value: []})
);
}
}
In this code we now have one Observable that we're using Operators on. We use the map
operator to get the response from our getSeasons
method and map it to our value
key (which I'm also calling a sort function on because the API doesn't return seasons in chrono order), and set our isLoading
key to false. We use the startWith
operator to set initial value to an empty array and isLoading
to true.
In our HTML markup we'll use Angular's async pipe to subscribe to our seasons$
Observable.
<h2>Seasons</h2>
<ng-container *ngIf="(seasons$ | async).value.length; else loading">
<div class="row" *ngFor="let chunk of (seasons$ | async).value | chunks: 4">
<mat-card *ngFor="let season of chunk" class="season-card">
<mat-card-header>
<mat-card-title>Season {{season.seasonNumber}}</mat-card-title>
</mat-card-header>
<img routerLink="/seasons/{{season.id}}" mat-card-image src="{{season.image_url}}" alt="Season {{season.id}} promo photo">
</mat-card>
</div>
</ng-container>
<ng-template #loading>
<mat-spinner></mat-spinner>
</ng-template>
And not to sound like a broken record, but at Bitovi we've built an RxJS training course as well to help you get up to speed with using RxJS in an Angular context.
6. Not Realizing the Nuances of Dependency Injection with Services
In AngularJS you've dealt with services and factories. Factories allowed you to create a new instance of a service depending on your needs. In Angular there are just services, and the WAY you provide them matters.
For instance, the following creates a single, shared instance of the service at the root level of the application - it will be available as a dependency for all components. When Angular looks up the dependency, it checks for existing instances of the dependency first, then creates one if the dependency doesn't exist yet.
@Injectable({
providedIn: 'root',
})
export class MyService {
}
If you register a provider for a single module, it will only be available for use in that module.
@NgModule({
providers: [
MyService
],
...
})
export class MyChildModule { }
If you need a new instance of your service you can register it in a specific components providers.
@Component({
selector: 'service-list',
templateUrl: './service-list.component.html',
providers: [ MyService ]
})
7. Not Understanding Change Detection
It was easy to tank performance in AngularJS by sprinkling $scope.$watch
everywhere - and if that was a crutch your team was guilty of using, moving to Angular is a great time to make sure everyone understands how change detection works.
Honestly in Angular if you're working with Observables it's not often you get into situations where the UI hasn't updated to reflect changes to the data you're working with - I can't think of hardly any instances in Angular development when I've had the need to call something like $scope.$watch
, and it's a nice feeling! When you start building Angular applications, I'd encourage you to explore using the ReactiveForms API - vs the typical [ngModel]
approach you're used to, it's a much better experience when interacting with form elements and showing changes in the UI.
Needing a way to tell Angular to listen for changes/make updates will be even less of an issue as your team moves towards truly reactive programming. The key thing for now is to remember to unsubscribe from your Observables to prevent memory leaks.
Angular provides a few Lifecycle hooks to help you manage, some of the more common ones I've included below:
@Component({
selector: 'my-component',
template: `<p>Hello world</p>`
})
class MyComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
ngOnChanges(changes: SimpleChange) {
//Called before ngOnInit() and whenever one or more data-bound input properties change.
}
ngOnInit() {
//Called once, after the first ngOnChanges().
}
ngDoCheck() {
//Called during every change detection run, immediately after ngOnChanges() and ngOnInit().
}
ngOnDestroy() {
//Called just before Angular destroys the directive/component.
}
}
To go more in depth into understanding the underlying mechanics behind change detection in Angular I really enjoyed these articles explaining Zones and how Angular uses them.
Summary
There is a LOT to learn in the Angular ecosystem, and exciting changes coming down the pipeline with Ivy, so there's no better time to switch from AngularJS to Angular than now, and I'm happy to help you through it! Find out more at bitovi.com.