Everything you need to know when starting your Angular adventure

Damian Wróblewski - November 2022

Everything you need to know when starting your Angular adventure

Table of contents

  1. Historical background: Angular 2 vs AngularJS
  2. Angular CLI
  3. Object Oriented Programinng
  4. TypeScript
  5. Project structure
  6. Change detection
  7. String interpolation
  8. ng-content
  9. @ViewChild
  10. Directives
  11. Pipes - transforming data in the template
  12. Constructor
  13. Modules
  14. Lifecycle methods
  15. Services and Dependency Injection
  16. Inputs and Outputs
  17. Forms
  18. Everything is a stream
  19. State management - service, ngrx or localStorage
  20. Routing
  21. Styles - view encapsulation
  22. Conclusions
  23. Helpful resources

Angular is not dead no matter what people say on Twitter. It's still widely used especially in case of large, enterprise applications. Google origin framework provides wide set of all necessary tools needed to build complex applications so you don't have to dig among many external libraries to handle such basic parts of your application as routing or HTTP requests.

I am working currently in the commercial project using Angular for a few months now, and with this post I'd like to cover all important things, that good to know when starting with the most frameworkish framework on the stage. You can treat this post as a list of most important things you should know about Angular.

The purpose of this post is to gather the concepts you should know at the beginning to have a broad picture of the Angular environment, without going into implementation details.

Historical background: Angular 2 vs AngularJS

Angular also known as Angular 2 may be confused with AngularJS, which is an older, incompatible version of the framework first released in 2010.

Angular was released in 2016 and differs primarily in that it is written in TypeScript, rather than based on javaScript AngularJS. To be clear, in this post we will cover topics related to Angular, not AngularJS.

Angular CLI

Angular provides an easy-to-use command line interface through which with a short command we can quickly create new project boilerplate or generate individual application elements like components, services, modules etc.

Object Oriented Programinng

Let's talk about the fundamental. Angular is based on OOP paradigm and it's crucial to get deep understanding how objects and related topics are working under the hood in javascript.

All of the Angular composites are based on JS classes so it's strongly recommended to be familiar with the topics like how inharitance is working, what are prototypes, constructor etc.

TypeScript

Angular is based on TypeScript, and it is somewhat thanks to it that it has been popularized on a wider scale so typescript basics are must have to work with Angular. You should know at least:

  • types and interfaces,
  • type inference,
  • private and public properties,
  • type definitions,

Project structure

There is no general recipe for creating file structure which fits for all projects. If you don't have the experience and knowledge to build a structure well suited to the specifics of your project Angular Style Guide is a good start point. You should keep the structure as flat as possible, this makes possible to locate the files quickly.

Component directory

Angular gives you the ability to use the Single File Component concept and put the template and styles in a single file:

1@Component({
2 selector: 'app-root',
3 template: `
4 <h1>Heading</h1>
5 <p>Bolded paragraph</p>
6 `,
7 styles: ['p { font-weight: bold; }']
8})
9export class AppComponent {
10}

However, a good pratice is to arrange the class, template and styles of the component in separate files and place them in e common directory of the component:

1@Component({
2 selector: 'app-root',
3 templateUrl: './app.component.html',
4 styleUrls: ['./app.component.scss']
5})

Change detection

Through the change detection mechanism, Angular determines components whose state has changed to then update their view.

Angular provides two strategies for change detection:

  • Default
  • OnPush

Default strategy

In simple terms, the default strategy is that Angular checks each component going down the DOM tree and compares the values of expressions used in the template. If the previous value is different from the current one, the component's isChanged flag is set to true and such component will be rerendered. The values are compared via the === operator.

For this strategy, there are a lot of events that trigger change detection like receiving data from API calls, user clicks etc.

The disadvantage of the default strategy is mainly that updating the parent component data causes its children to be re-rendered, no matter whether the child component is associated with the changed data.

OnPush strategy

In most cases, the default strategy is sufficient, but for more complex components, it can lead to performance problems. To improve performance, Angular provides an OnPush strategy. Of course, nothing for free. The price of better performance is increased complexity.

In this case change detection is triggered much less frequently, mainly when the values passed as @Input have changed so it's a good way to prevent child components from unnecessary re-rendering.

To enable OnPush strategy:

1@Component({
2 selector: "child-component",
3 template: '...',
4 changeDetection: ChangeDetectionStrategy.OnPush
5})
6export class ChildComponent {
7}

However, we have to remember that object passed as @Input cannot be mutable, so with this strategy when we update an object passed to a child as @Input, we have to create a new, changed object with a different reference.

String interpolation

Angular provides string interpolation which you can use to display the value of variable in the template.

In the following example we're displaying the string value of userName variable in the template:

<h3>Name: {{ userName }}</h3>

ng-content

Using the ng-content element, we can display content placed inside the component tags:

Parent component class:

1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-slot-component',
5 template: `
6 <h2>Heading</h2>
7 <ng-content></ng-content>
8 <p>Some other stuff</p>
9 `
10})
11export class SlotComponent {}

Parent component template:

1<app-slot-component>
2 <p>Projected content</p>
3</app-slot-component>

Result:

1<h2>Heading</h2>
2<p>Projected content</p>
3<p>Some other stuff</p>

A similar mechanism in React is known as children props, and in Vue as slots.

@ViewChild

Using the @ViewChild decorator, we gain control over child components or directives from the parent component. By assigning a child component or directive to a variable, we have access to its public methods and properties:

1import {
2 Component,
3 ViewChild,
4 AfterViewInit
5} from '@angular/core';
6import { ChildComponent } from './child.component';
7
8@Component({
9 selector: 'app-root',
10 templateUrl: './app.component.html',
11 styleUrls: ['./app.component.css'],
12})
13export class AppComponent implements AfterViewInit {
14 @ViewChild(ChildComponent) child!: ChildComponent;
15 ngAfterViewInit() {
16 this.child.someMethodFromChild();
17 }
18}

With @ViewChild we can also assign a reference to an HTML element gaining the ability to change its properties from the parent component class:

<div #ref1></div>
1@ViewChild('ref1') child: ElementRef;
2
3// Now we can, for example, change component's class:
4this.child.nativeElement.className = 'text-bold';

It is worth mentioning that there is also a @ViewChildren decorator which allows you to collect more than one child components into a list.

Directives

Directives are used to change the appearance and behavior of DOM tree elements. There are two types of directives in Angular - attribute and structural.

Attribute directives

These directives are used to manipulate the attributes of DOM elements. Angular provides some built-in attributes directives:

  • NgClass - adds and removes a set of CSS classes To apply the directive, add it as HTML attribute to the template. In the following example, we add a dynamic CSS class 'red' that depends on the isRed boolean property:
<div [ngClass]="isRed ? 'red' : ''">Text content</div>
  • NgStyle - adds and removes a set of HTML styles
<div [ngStyle]="{'color': 'red'}">Text content</div>
  • NgModel - adds two-way data binding to an HTML form element In the following example, we bind input.value with userName property:
<input [(ngModel)]="userName" id="name">

We can also create custom directives to manipulate DOM elements in a customized way. If you want to learn more about this I refer you to the Angular documentation.

Structural directives

These directives are used to manipulate the structure of DOM tree by adding and removing elements. Let's take a look into built-in structural directives:

  • NgIf - used for conditional rendering of DOM elements
  • NgFor - used for rendering a list of DOM elements
  • NgSwitch - used to switch among alternative views depends on some condition

As with attribute directives, we can also create our custom structural directives. If you want to learn more about this I refer you to the Angular documentation.

Pipes - transforming data in the template

Pipes are simple functions that transform data in templates. They are used to share transformation logic throughout the application. As with directives, Angular provides some built-in pipes as well as the ability to create your own.

Example of usage:

<p>{{ stringValue | uppercase }}</p>

Constructor

As mentioned before, Angular component is just a javaScript class so it's naturally has a contrstructor method which is called when our component instance is created by Angular using the New keyword.

Typically, the constructor is used to provide the component with services and other dependencies by binding dependency instances to class properties:

1constructor(private translate: TranslateService,
2 private store: Store<State>,
3 private router: Router,
4 private fb: FormBuilder) {
5 }

The constructor can also be treated as the first of the component lifecycle methods about which you will learn more below. Constructor is run first, before any other lifecycle method.

Modules

One of Angular's most important features that allows you to scale your application is modularity system. What are modules? Modules allow you to group components, directives, pipes, services and other components of an Angular application for encapsulation and performance improving purposes.

For better organization of the code, it is recommended to create so-called feature modules dedicated to particular application functionality and import them into the main application module called root module.

Shared parts of the application we can put in a separate module called shared module and import it wherever we need it.

You can learn more about the modules from the official documentation.

Lifecycle methods

Like other frameworks, Angular provides a component lifecycle methods in which you can place the code you want it to run at the appropriate point in the component life cycle.

Lifecycle method in chronological order:

ngOnChanges(SimpleChanges) - runs when input bindigs have changed. The method receives a SimpleChanges object which you can use to compare previous and current values

ngOnInit() - runs after Angular initialized every data-bound properties. Here you can put your additional initialization logic

ngDoCheck() - not runs on an event like OnChanges, which is called when a change in input properties occurs. Inside DoCheck you can add your custom change detection logic, however you should keep code lightweight - DoCheck runs very often so it could be a source of performance issues in case of heavy logic included in it

Lifecycle methods associated with @ViewChild:

ngAfterViewInit() - runs after child components and directives are initialized and available

ngAfterViewChecked() - runs after the change detector of a child component or directive has been run for checks

Lifecycle methods associated with <ng-content>:

ngAfterContentInit() - runs after projected content inside <ng-content> tags is initialized and available

ngAfterContentChecked() - runs after the change detector of projected content has been run for checks

ngOnDestroy() - runs before destroying an instance of the class. This is where you should put your cleanup logic like unsubscribing from the observable streams

Services and Dependency Injection

No matter which framework you are using, separation of logic and presentation is a good practice. When writing functional oriented code in React you can move logic outside the components by using hooks, in Vue you can use mixins. When it comes to Angular we have a concept of injectable services which use dependency injection design pattern for sharing business logic between components.

It's worth mention that there is a couple of ways in which you can provide and inject your dependency.

Providing dependency:

  • inside the component - dependency (i.e. service) is available to all instances of the component and other components and directives used in the template and, what is important, a new instance of the service is created with each new instance of the component:
1@Component({
2 selector: 'your-component',
3 template: '...',
4 providers: [YourService]
5})
6class YourComponent {}
  • inside the module using the providers field of the @NgModule decorator - service is available to all components, directives, and pipes declared in the NgModule and the same instance of a service is available to all components in that NgModule:
1@NgModule({
2 declarations: [YourComponent]
3 providers: [YourService]
4})
5class YourModule {}
  • At the root level of application by adding the providedIn: 'root' field to the @Injectable decorator - service is available in every classes in the application and the same instance is created for every class that ask for it. Additionaly in this case angular uses tree-shaking process to remove the service from classes that not using it:
1@Injectable({
2 providedIn: 'root'
3})
4class YourService {}

Inputs and Outputs

The primary means of communication between parent and child components in Angular are @Inputs and @Outputs. With an @Input, we can pass data from the parent to the child, and with an @Output, vice versa.

Input

To provide @Input we need to simply decorate property in the component class:

1import { Component, Input } from '@angular/core';
2export class ComponentWithInput {
3 @Input() name = '';
4}

Then we can bind the Input with userName property from parent component:

<component-with-input [name]="userName"></component-with-input>

Output

To pass data from the child to the parent, we need to create an emitting event:

1import { Output, EventEmitter } from '@angular/core';
2export class ComponentWithOutput {
3 @Output() outputEvent = new EventEmitter<string>();
4}
5
6emitValue(value: string) {
7 this.outputEvent.emit(value);
8 }

And then handle the event in the parent template by binding it to the handling method:

<component-with-output (outputEvent)="handleEvent($event)"></component-with-out>

Forms

Angular also for forms provides a different solution depending on your needs. In the case of forms, we can choose from:

  • Template Driven Forms - based on the ngModel directive and suggested for less complex forms
  • Reactive Forms - allow building forms in a more programmatic way. Here we use dedicated class instances for groups of forms and controls, allowing full control over the form and controls. As the name suggests, Reactive Forms also enjoy the benefits of reactive streams. We can subscribe to changes in the form and react accordingly to changes in input values. Reactive Forms is a rather broad topic so we won't go into implementation details here to keep this post as compact as possible.

Everything is a stream

An integral part of Angular are reactive streams and the RxJS library that supports them. For consistency, even an event emitting single value is treated as a stream in Angular so even for simple communication with API, you need knowledge of the concept of reactive streams. Among other reasons, this is why Angular is considered a framework with a higher entry level than React or Vue. RxJS is a vast topic worth a separate post.

Useful resources for learning rxjs:

State management - service, ngrx or localStorage

There are several ways to hold application state in Angular. The easiest way is to create a service that will keep the state of all or part of our application. Below I will describe perhaps the two most popular solutions.

State in the service

In the following example, we have created a sample service to store the state. Here we use BehaviorSubject, which allows us to pass an initial value and then manually control the emitting of data by the created Observable. In addition, we created setter and getter methods to retrieve and set the state of the theme:

1@Injectable()
2export class AppState {
3
4 private theme = new BehaviorSubject<Theme>(initialTheme);
5 private theme$ = this.theme.asObservable().pipe(shareReplay(1));
6
7 /**
8 * Returns the state of the Theme.
9 */
10 getTheme(): Observable<Theme> {
11 return this.theme$;
12 }
13
14 /**
15 * Sets the provided theme in the state.
16 */
17 setTheme(theme: Theme): void {
18 this.theme.next(theme);
19 }
20}

NgRx

For medium to large sized applications, Angular provides a state management tool which is the NgRx library. NgRx is a state management library that combines Redux and reactive streams.

As one of Redux's developers Dan Abramov said, you don't need Redux if you don't know you need it. The same applies to Angular and NgRx. If the state of the application is so complex that working with it is painful, consider implementing NgRx.

NgRx is such a vast topic that at least several blog posts could be devoted to it. You can learn more about this library here.

Routing

As you might guess, Angular also provides tools to implement proper application routing. In order to configure routing, we need to create a dedicated module, which we then import into the main module of the application.

Routing module

Below is an example of a routing module, where we defined two paths and assigned components to them, which Angular will load when the user enters the associated path:

1import { NgModule } from '@angular/core';
2import { Routes, RouterModule } from '@angular/router';
3
4const routes: Routes = [
5 { path: '', component: HomePageComponent },
6 { path: 'posts', component: PostsComponent },
7];
8
9@NgModule({
10 imports: [RouterModule.forRoot(routes)],
11 exports: [RouterModule]
12})
13export class AppRoutingModule { }

ActivatedRoute

In order to get information about the current path, Angular provides an injectable service called ActivatedRoute.

AcivatedRoute provides access to information about a route related to the component such as the path or query params.

Lazy loaded modules

To improve application performance, Angular allows paths to be loaded lazily - on demand. This means that the module code will be downloaded by the user only when enters the appropriate path.

For configuration, instead of assigning individual components to paths, we import modules using loadChildren:

1const routes: Routes = [
2 {
3 path: 'posts',
4 loadChildren: () => import('./posts.module').then(m => m.PostsModule)
5 }
6];

We then assign the individual components inside a dedicated routing module:

1import { NgModule } from '@angular/core';
2import { Routes, RouterModule } from '@angular/router';
3
4import { PostsComponent } from './customers.component';
5
6const routes: Routes = [
7 {
8 path: '',
9 component: PostsComponent
10 }
11];
12
13@NgModule({
14 imports: [RouterModule.forChild(routes)],
15 exports: [RouterModule]
16})
17export class PostsRoutingModule { }

Styles - view encapsulation

The last topic I wanted to cover in this post is view encapsulation. In order to separate the component styles from the rest of the application, Angular provides a mechanism called view encapsulation. It makes sure that the styles specified in one component do not come into conflict with the styles of other components.

Angular provides three view encapsulation options: Emulated(default), ShadowDom and None. You can change the encapsulation mode by adding the appropriate option in the component decorator:

1@Component({
2 selector: 'app-component',
3 template: `...`,
4 styleUrls: ['./app.component.css'],
5 encapsulation: ViewEncapsulation.None,
6})
7export class AppComponent { }

Below we will focus on the default encapsulation mode - Emulated.

View encapsulation - Emulated

To take advantage of the encapsulation, all we need to do is add the CSS file via the styleUrls property in the component decorator:

1@Component({
2 selector: 'app-root',
3 styleUrls:['./app.component.css'],
4 template: `...`})
5export class AppComponent {}

From now on, the styles added in our CSS file will only refer to the context of this component.

How does Angular do it? Let's preview the application's DOM tree in the browser: angular-view

We can see that some mysterious attributes have been added to HTML elements like _nghost-osm-c44 or _ngcontent-osm-c43. It is through these attributes that Angular assigns styles to the corresponding components and only to them.

So when we add styles like this:

1.bolded {
2 font-weight: bold;
3}

In fact, Angular will generate a CSS file with additional attributes assigned to the selectors:

1.bolded[_ngcontent-osm-c43] {
2 font-weight: bold;
3}

:ng-host

What if we want to add styles to the host element of our component? Then we can use the :ng-host pseudo-class:

1:host {
2 font-weight: bold;
3}

::ng-deep

Sometimes we may want to style nested child components from the parent component level. Then we can use the ::ng-deep pseudo-class, which, so to speak, removes encapsulation attributes from selectors. Therefore, we must be careful, because when we use only ::ng-deep, our styles will become global. In that case, we can use a combination of two pseudoclasses :ng-host and ::ng-deep:

1:host ::ng-deep h1 {
2 font-weight: bold;
3}

Now the styles will be applied to all h1 elements inside our component and also in the nested child components.

Conclusions

As you can see, the Angular environment is very broad, and the topics that you need to learn in order to efficiently use this framework are really a lot. This, among other reasons, is why Angular is considered one of the more difficult frontend frameworks to learn at the beginning. But let's not forget that Angular is a complete framework that provides all the necessary tools to build scalable applications. I hope that with this post I have helped you learn all the basics of the Angular universe.

Helpful resources

If you found inaccuracies in this post or know other useful tips or tricks for building flexible tables, please leave a comment below 😉


Join the discussion