View on GitHub

Povio Engineering Guidelines

These are guidelines for the technologies we are using.

Angular guidelines


Angular is a framework completely rewritten from the ground up, replacing the AngularJS framework (Angular 1.x).

More than just a framework, Angular should now be considered as a whole platform. It comes with a complete set of tools, like its own CLI, debug utilities and performance tools.

Angular has been around for some time now, but I still get the feeling that it’s not getting the love it deserved, probably because of other players in the field like React or VueJS. While the simplicity behind these frameworks can definitely be attractive, they lack in my opinion what is essential when making big, enterprise-grade apps: a solid frame to lead both experienced developers and beginners in the same direction and a rational convergence of tools, patterns and documentation. Yes, the Angular learning curve may seem a little steep, but it’s definitely worth it.

— maintainers of ngx rocket

Angular also has great detailed docs available at Angular docs.

Getting started

Newcomer

One of the big disadvantages of Angular is its steep learning curve. A newcomer can be overwhelmed by the number of new concepts. So, if you are not familiar with Angular yet, check out this progressive tutorial which will guide step by step in making your first Angular application.

AngularJS developer

If you come from AngularJS, you already may be familiar with a lot of concepts of the new Angular. Here is a quick reference guide to make the transition easier AngularJS quick reference.

Cheatsheet

Here is a great cheatsheet you can keep around, till you know Angular by heart.

Setting up an application

The best way for getting started with Angular app is to use the Angular CLI tool:

Style guides

Angular has comprehensive style guides written at style guides. Do check them out.

File structure

The best way is to stick to the Angular file structure style guide.

Creating new files inside a project

For starters, a good way of adding new components/modules/directives/… is using Angular CLI generate tool. If you are not a big fan of CLI tools you can install Angular Console.

You can find full documentation on Ng generate docs.

Here is an example of creating a new component using Angular CLI:

Run ng g component header inside root folder, to create a header component.

NOTE: Every component/directive/service should be added to the corresponding Module. Using Angular CLI, this is added automatically, to the nearest module.

You can also find Angular CLI plugins for different IDEs:

Forms

There are two ways of creating/binding to form(choose one and stick to it):

Linting

Typescript linting is enabled by default when creating an app with Angular CLI.

For style linting run:

For html linting run:

Routing

Since one of the biggest problems of Angular is its bundle size, it is advised to keep well-structured routing with Lazy loaded modules. Also, the best way is keeping each module routes, inside separate routing module.

Example:

Lazy loading DevicesModule in core routing file app-routing.module.ts :

const routes: Routes = [
  {
    path: 'devices',
    loadChildren: () => import('./devices/devices.module').then(m => m.DevicesModule)
  },
  ...

Devices module file devices/devices.module.ts:

@NgModule({
  imports: [
    CommonModule,
    DevicesRoutingModule
  ],
  declarations: []
})
export class DevicesModule { }

Devices module routing file devices/devices-routing.module.ts:

const routes: Routes = [
  {
    path: '',
    component: DevicesPageComponent
  }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class DevicesRoutingModule { }

Styling

So there are four ways to set the component view encapsulation, see more details at View encapsulation docs.

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  encapsulation: ViewEncapsulation.Emulated // <-- component view encapsulation
})

The Emulated view encapsulation is the default one. If you are not used to shadow DOM, you are going to run into some troubles. Sure you can set the view encapsulation to None, but is not advised. Now imagine inside of app-root component you have some external component ngx-datepicker. You can’t set the styles inside of app.component.scss for ngx-datepicker, because of view encapsulation. You can use ::ng-deep css selector to cheat, but since it is going to be soon deprecated, it is not advised, you can read more at Ng deep selector.

The best way to get through this problem is:

Managing data

There are a couple of ways to send/receive data in an Angular application.

Lifecycles hooks

You can find all the details at lifecycle hooks guide. Let’s just mention the three, that we find most important:

Change detection

In Angular, change detection is done for each component separately. By default ChangeDetectionStrategy.Default change detection is enabled. I would encourage you to use ChangeDetectionStrategy.OnPush on all components, since it can dramatically speed up your application. A lot less change detection cycles are made. You need to explicitly set it like this:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush // Default is ChangeDetectionStrategy.Default
})

Now if you are using Mutables and Observables everything should in general work the same. Where things fail is with primitives(i.e. string, number) since they are Immutable. Basically, in Angular zones take care of the change detection. In Angular, there is a specific zone called ngZone. To update the Immutable object run the following:

constructor(private ngZone: NgZone,
            private cdr: ChangeDetectorRef) {

  this.ngZone.run(() => {
    // Make changes to immutable objects here
    this.cdr.markForCheck();
  });

  // You can also force change detection, but above is preferred.
  this.cdr.detectChanges();
}

Using ChangeDetectionStrategy.OnPush may be more time consuming, but increases the speed of the application. Also, it improves the developer’s understanding of the internal working of the Angular change detection system.

Reactive programming(Observables)

Angular uses a reactive system by design. You are not forced to use the reactive pattern, but they make up the core of the Angular. It’s highly advised to learn and use the reactive programming pattern.

If used correctly, observables are one of the best things in Angular. Convention for observables is to use $ at the end of the variable name. Here is the rxjs documentation.

There is some good news, observables can be turned into promises and vice versa.

// Create an observable 
const observable$ = new Observable((observer: Subscriber<any>) => {
    observer.next([]);
    observer.complete();
});

// Convert observable to promise
const promise = observable$.toPromise().then(() => {});

// Convert promise back to observable
const fromPromise$ = from(promise);
fromPromise$.subscribe();

NOTE: This is not a good practice, but it can make the transition from promises to observables easier. But promises can emit only one value!

Where they shine? Microservices architecture. In the next example you can see 3 subsequent calls to API, directly inside .html file, here we use async pipe so that the observable get resolved:

<ng-container *ngIf="device$ | async as device">
    <ng-container *ngIf="deviceToOwner(device) | async as deviceToOwner">
        <ng-container *ngIf="owner(deviceToOwner) | async as owner">
            
        </ng-container>
    </ng-container>
</ng-container>

Observables are lazy, which means they will not execute till subscribed to. A good practice is to subscribe to observables with an async pipe inside HTML file since the component automatically cancels the observable in case of component destroy itself. Otherwise, we need to do this ourselves.

How we cancel the observable inside *.component.ts file? This is necessary every time, we subscribe inside the code. Because if in the meantime, the component destroys itself and observable is still loading, this no longer points to the component class.

export class AppComponent implements OnDestroy {
  private readonly unsubscribe$ = new Subject();

  constructor() {
    const observable$ = new Observable((observer: Subscriber<any>) => {
      observer.next(null);
      observer.complete();
    });
    observable$
      .pipe(takeUntil(this.unsubscribe$))
      .subscribe();
  }

  ngOnDestroy(): void {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }
}

TIP: Great way to not write all the code above is to use a library like untilDestroyed, and can be used like this:

observable$
  .pipe(untilDestroyed(this))
  .subscribe();

Some combination/creation/operators we find useful (these just some that we used before, there are a lot more):

Learning material

— maintainers of ngx rocket

Testing

Angular has a great test guide written. When creating an app through Angular CLI, both unit testing and e2e testing are already configured. It’s advised to create files with Angular CLI so unit test files are automatically created(*.spec.ts files). You can find e2e tests inside the e2e folder. *.po.ts files represent helper classes, for example, login.po.ts can contain some helper methods for login, so they are defined only once. *.e2e-spec.ts files contain e2e tests.

AOT vs JIT

AoT can be helpful not only for achieving more efficient bundling by performing tree-shaking but also for improving the runtime performance of our applications. The alternative of AoT is Just-in-Time compilation (JiT) which is performed runtime. With AoT we can reduce the number of computations required for the rendering of our application by performing the compilation as part of our build process.

The only case we can think of using the JiT compiler is when creating dynamic components. Otherwise, always use AoT compiler.

Imports

Since the bundle size of Angular is already huge, we must do everything to keep it, as small as possible. Do, this, when you are already almost finished with developing the application, and the application goes to production. We can analyze the bundle size with the following steps:

Now a local server starts up running on http://localhost:8888, navigate to the page. Now you can examine if there are any unnecessary modules being imported.

One bad example is the moment library. When importing it, all the moment-locale files are being loaded as well, increasing bundle size.

A second bad example is when importing from rxjs/operators.

Bad:

import {takeUntil} from 'rxjs/operators';

Since rxjs/operators point to the index file, that imports all of the rxjs operators.

Good:

import {takeUntil} from 'rxjs/internal/operators/takeUntil';

The above code only imports takeUntil operator and not 50 other operators, that are not needed. Be careful what you import!

Third bad example, import only the Modules you need. For example, if using some chart library, and using only PieChart, make sure that only PieChartModule is imported and not the whole library module, for example ChartsModule.

Deploying

Make sure you first consult Angular deploy docs.

The basic deployment process looks like this:

Since Angular is a SPA, it does not need an actual web server. You could copy files to a static file server, saving you some money. Also make sure that the cache is set on the server, since the files can get quite big, and if the application is frequently used, so the files don’t have to download every time.

Service worker

Follow the service worker getting started guide. It takes only half an hour in order to set up a working service worker. It can be configured to prefetch lazy modules, on page load, creating a really fast user experience.

browserlist

To configure which polyfills. are being included, open browserlist file at the root of the project. For more check out format and rule options.

This handles JS unsupported features in browsers, and also manage auto prefixing for CSS for configured browsers.

Resources

Proxy

The proxy can be used to bypass CORS browser restriction, so you don’t need to run a web server locally. Also, it can help in some other cases. Follow the using proxy guide.

Mocking

For mocking services, here is how we do it:

Create a common interface DeviceServiceInterface that implements all functions:

export interface DeviceServiceInterface {
  getDevices(): Observable<DeviceInterface[]>;
}

Now create two services. The http service:

@Injectable()
export class DeviceService implements DeviceServiceInterface {

  constructor(private httpClient: HttpClient) { }

  getDevices(): Observable<DeviceInterface[]> {
    return this.httpClient.get('<ENDPOINT_URL>') as Observable<DeviceInterface[]>;
  }
}

And the mock service(responses should be in .json files, here are inline, for simplicity):

@Injectable()
export class DeviceMockService implements DeviceServiceInterface {

  constructor() {
  }

  getDevices(): Observable<DeviceInterface[]> {
    return of([{id: 1, name: 'device-1'}, {id: 2, name: 'device-2'}] as DeviceInterface[])
      .pipe(delay(1000)); // To simulate delay from http response
  }
}

In the corresponding module provide this:

{
...
providers: [
  {
    provide: DeviceService,
    useClass: environment.production ? DeviceService : DeviceMockService
  }
],
...
}

Now you can use DeviceService, locally mocked and on production, the actual http server.

Server side rendering - SEO

The Angular Universal is a technology that renders Angular application on the server.

Here are three main reasons for using Angular Universal:

Nx

Nx is a set of extensible dev tools for monorepos, which helps you develop like Google, Facebook, and Microsoft. Nx does not replace Angular CLI but makes it more powerful.

Why would you use Nx?

Resources

Upgrade Angular

An Angular update website has a great guide for upgrading. Upgrading to a newer version of Angular is pretty simple with ng update /*angular packages here*/, since the command does most of the migration steps all already for us. We just need to update the code, if there are any breaking changes.

Analytics

You may want to consider Angulartics2. It has many supported providers, like Google Analytics, Mixpanel, Facebook Pixel, Application Insights, …

Documentation

Compodoc automatically generates documentation for Angular projects.

Resources