angular

Angular's Fundemental Walkthrough

Informational

April 29, 2022

  • Javascript
  • Angular

Why Angular?

I recently interviewed with a company that uses Angular in their tech stack. I've know many enterprise companies use Angular as their main front-end framework, but I've grown accustom to making React my primary choice. I figured this would be a good chance to take a better look at Angular and see why it's one of the biggest names in the game. I decided to use this follow-along dev video and take notes to farther explain what is actually happening.

Starting off, Angular is far more opinionated than React is. Even though React is just a libary compared to being viewed as a framework, the learning curve for Angular is definitely much steeper. But as I started to use Angular more and more, I was starting to realize how useful Angular can be. Even though Angular is much more complex than React, it provides better safety nets and structure for development. My thinking is once you get accustom to Angular's syntax, it's actually a super useful tool to have knowledge on.

Foundation of Angular

Typescript

A fundemental trait to Angular is that it's foundation is built on Typescript. For those who might not know, Javascript is a loosely-typed language, meaning you don't have to specify addition information about variables and functions. Although it might be easy to use, there are a lot of headaches that can come from it. You can combat that with using Typescript. Typescript is a strong-typed superset of Javascript that provides an additional layer to it's syntax: the type-system. While using Typescript, you have to specify types when delcaring variables, parameters and other things. Angular using Typescript as a fundemental just heightens Angular's "safety net."

NgModules

If you follow Angular's introduction to it's concepts, it states that the building blocks of the framework is Angular components built into Angular's NgModules. The NgModule collects code from components into a set, and the application as a whole is built by NgModules.

Like regular Javascript, modules can be imported into an NgModule (as well as diffrent view & services components) to aid in creating an application. Every application has to have at least one root module (which is typically called AppModule) that allows Angular to bootstrap (or launch) the application. When using the CLI, the entry point is usually held in a file called main.ts. Here is what that looks like out of the box.

import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";

import { AppModule } from "./app/app.module";
import { environment } from "./environments/environment";

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .catch((err) => console.error(err));

Here we can see ....

  1. AppModule is being imported from app/app.module.ts where the NgModule lives
  2. platformBrowserDynamic() is using it's bootstrapModule() method to launch the Module

Now let's take a look into (a part of) the app/app.module.ts file.

// Imports containing NgModules/support modules from Angular & custom components

@NgModule({
  declarations: [
    AppComponent,
    HeaderComponent,
    ButtonComponent,
    TasksComponent,
    TaskItemComponent,
    AddTaskComponent,
    AboutComponent,
    FooterComponent,
  ],
  imports: [BrowserModule, FontAwesomeModule, HttpClientModule, FormsModule],
  exports: [AppComponent],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

The @NgModule() decorator is a function that takes a single metadata object, whose properties describe the module. The primary properties shown are declarations, imports, exports, providers and bootstrap.

  • declarations - The components, directives, and pipes that belong to this NgModule.
  • imports - Other modules whose exported classes are needed by component templates declared in this NgModule.
  • exports - The subset of declarations that should be visible and usable in the component templates of other NgModules. (exports is only declared here for presentional purposes. A root NgModule has no reason to export anything because other modules don't need to import the root NgModule.)
  • providers - Creators of services that this NgModule contributes to the global collection of services; they become accessible in all parts of the application. (You can also specify providers at the component level.)
  • bootstrap - The main application view, called the root component, which hosts all other application views. Only the root NgModule should set the bootstrap property.

I'll explain more about what components, directives and services actually are in the next section, but it's good to know what is expected when that time comes.

Angular's Framework Structure

Angular's core consists of 4 main sections...

  • Components - Specifying a components selector, template & stylesheet
  • Templates - HTML template that declares how that component renders
  • Directives - Classes that add additional behavior to elements in your application
  • Dependency Injections (Services) - Allows classes to requests dependencies (which can be a service or object) from external sources rather than creating them

I'll explain the basic fundemental to each section first, then I'll later explain how they call come together to create an application and show the data flow all together.

Components

Components are one of the main building blocks to Angular. Each component comes with:

  • HTML Template - Declares what to be rendered on a page (required)
  • CSS selector - Instructs Angular to instantiate the component wherever it finds it's corresponding tag in the template HTML (required)
  • TypeScript class - Defines component behavior and exports for usage ()
  • CSS file for styling - (Optional)

To create a component, you have to declare a @Component({}) decorator and pass into it's object the metadata regarding the respected corresponding data. Then the @Component() decorator will identify the class directly below it as it's own. For example:

@Component({
  selector: "app-container", // CSS selector that specifies to use <app-container> to instantiate component
  templateUrl: "./component-container.component.html", // Template URL to locate what template to use to render component logic
  styleUrls: ["./component-container.component.css"], // Optional: CSS file that modifies the look of the component
})

// Class that holds component logic and exports for usage
export class ContainerComponent {
  // additional logic
}

In the class that gets exported, you can add the logic that will operate your component. For example, you can declare properties that might hold the component's state and methods that modify the component's state.

Angular's core also comes with a set of lifecycle methods that can be imported. These methods help you dictate what should be happening at different phases of a components life. For example:

  • ngOnInit() - Desribes what should happen when the component is being mounted (or rendered)
  • ngOnDestroy() - Desribes what should happen when the component is being unmounted (or taken away from the render)

Templates

In Angular, a template is just a chunk of code that dictates how a component will render it's data. Like mentioned before, when you create a class, you must specify what CSS selector the component will use. That allows you to call that component elsewhere. The template code should be found in the component-name'-component.html file and takes in data passed from the component's class.

Angular's templates use a design called "string interpolation" that lets you incorporate dynamic string values into the template. In order to do so, you must wrap whatever value is being passed from the class in double curly braces. ( {{value}} ). I'll explain in a basic example below.

// src/app/app.component.ts
const blowOwner = "Cisco";

// src/app/app.component.html
<p>The owner of this blog is named {{ blogOwner }}</p>;

// "The owner of this blog is named Cisco" will be rendered

You can also use template expressions to evaluate a value of an expression. Angular will then convert the value to a string.

// "The sum of 1 + 1 is 2"
<p>The sum of 1 + 1 is {{1 + 1}}.</p>

When using templates, you can also incorporate template statements. Template statements are methods or properties that you can use in your HTML to respond to user events. Template statements will then call a specific event to occur.

<button type="button" (click)="sayHello()">Say Hello!</button>

The syntax for this is (event)="statement" meaning whenever the event (in this case, a click) is fired off, the template will know to use a statement defined in the component's class to execute.

There's different type of bindings you can use inside of your template to help interactions within the application.

  • Property Binding - Property binding moves a value in one direction, from a component's property into a target element property using brackets ([]).
<img alt="item" [src]="itemImageUrl" />

<!--Sets the target property "src" to the property of "itemImageUrl" passed from the component-->
  • Event Binding - Listen for and respond to user actions such as clicks, touches, keypresses, etc by wrapping the target event name in parentheses (())
<button (click)="onSave()">Save</button>

<!-- Wrapping the target event name (in this case, "click) in parentheses let's Angular know to call the components
"onSave()" method when triggered-->
  • Two-way Binding - Gives your application a way to listen for events and update values simultaneously between parent and child components. Syntax is both parentheses and brackets. (([]))
<app-sizer [(size)]="fontSizePx"></app-sizer>

<!-- -->
Note: Two-way data binding is a bit more complex. Two decorators called @Input & @Output are needed and I haven't discussed them yet so I'll run back to this point.

Angular implements a design using template variables. Template variables help you use data from one part of your template in another part. Templates are best used when fine tuning a specific behavior of a view or when working with forms. In order to use a template variable, you have to use the hash character (#) followed by a variable name inside of a template element.

<input #phone placeholder="phone number" />

In the example above, we declare a #phone variable in an <input element. If we wanted to retrieve the value of the element in a later part of the template, we can use the following syntax.

<button type="button" (click)="callPhone(phone.value)">Call</button>

<!-- phone refers to the input element stated elsewhere in our template. We pass its `value` to an event handler -->

Directives

Directives are classes that add additional behavior to elements in your Angular applications. Use Angular's built-in directives to manage forms, lists, styles, and what users see.

There are 3 different types of Angular directives

  • Components - Used with a template. This type of directive is the most common directive type.
  • Attribute directives - Change the appearance or behavior of an element, component, or another directive.
  • Structural directives - Change the DOM layout by adding and removing DOM elements.

Attribute directives listen to and modify the behavior of other HTML elements, attributes, properties, and components. The most common attribute directives are as follows:

  • NgClass - Adds and removes a set of CSS classes depending on whether a key is true or false.
<!-- toggle the "special" class on/off with a property -->

<div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>
  • NgStyle - Adds and removes a set of HTML styles.
export class Component {
  currentStyles: Record<string, string> = {};
  setCurrentStyles() {
    // CSS styles: set per current state of component properties

    this.currentStyles = {
      "font-style": this.canSave ? "italic" : "normal",
      "font-weight": !this.isUnchanged ? "bold" : "normal",
      "font-size": this.isSpecial ? "24px" : "12px",
    };
  }
}
<div [ngStyle]="currentStyles">
  This div is initially italic, normal weight, and extra large (24px).
</div>

For this use case, Angular applies the styles upon initialization and in case of changes. To do this, the full example calls setCurrentStyles() initially with ngOnInit() and when the dependent properties change through a button click.

  • NgModel - Adds two-way data binding to an HTML form element.
<label for="example-ngModel">[(ngModel)]:</label>
<input [(ngModel)]="currentItem.name" id="example-ngModel" />

Use the NgModel directive to display a data property and update that property when the user makes changes. (Two-way Binding)

Structural directives are responsible for HTML layout. They shape or reshape the DOM's structure, typically by adding, removing, and manipulating the host elements to which they are attached. The most common built-in structural directives:

  • NgIf - Conditionally creates or disposes of subviews from the template.
<!-- If "isActive" property is true, <app-item-detail> will render-->

<app-item-detail *ngIf="isActive" [item]="item"></app-item-detail>
  • NgFor - Repeat a node for each item in a list.
<!--Will loop through the array property of "items" and render the item's name-->

<div *ngFor="let item of items">{{item.name}}</div>
  • NgSwitch - A set of directives that switch among alternative views. ngSwitch works like regular javascript in the sense that it also utilizes ngSwitchCase for any of the switch options & ngSwitchDefault for any default option
<div [ngSwitch]="currentItem.feature">
  <app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item>
  <app-device-item
    *ngSwitchCase="'slim'"
    [item]="currentItem"
  ></app-device-item>
  <app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item>
  <app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item>
  <!-- . . . -->
  <app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item>
</div>

It's important to note that you can also create your own directives.

Dependency Injection

Dependencies are services or objects that a class needs to perform its function. Dependency injection, or DI, is a design pattern in which a class requests dependencies from external sources rather than creating them. Services can be used when you need data from external APIs or when you need to to do some type of UI change, and These services can be injected into whatever component calls for them

This is the result of a CLI-generated service:

import { Injectable } from "@angular/core";

@Injectable({
  providedIn: "root",
})
export class CustomService {
  constructor() {}

  getData() {
    const DATA = fetch("some api call");
    return DATA;
  }
}

The @Injectable() decorator specifies that Angular can use this class in the DI system. The metadata providedIn: 'root' means that the CustomService is visible throughout the whole application. The CustomService service has a method called getData that is in charge of retrieving data and providing it to the application.

To inject a dependency in a component's constructor(), supply a constructor argument with the dependency type. The following example specifies the CustomService in the AppComponent constructor. The type of CustomService is CustomService.

import { Injectable } from '@angular/core';
import { CustomService } from '../services/custom.service';


export class AppComponent {

  constructor(private customService: CustomService) {  }

  getAPIData() {
    const apiData = this.customService.getData());
    return apiData;
  }
}

In the example above, we

  1. Import the CustomService type
  2. We inject the CustomService into the constructor and give it a private instance named customService
  3. We create a method called getAPIData() that calls the getData method from the CustomService service.

Observables

Observables are often used in services to keep of functionality inside a component. Angular makes use of observables as an interface to handle a variety of common asynchronous operations. For example:

  • You can define custom events that send observable output data from a child to a parent component
  • The HTTP module uses observables to handle AJAX requests and responses
  • The Router and Forms modules use observables to listen for and respond to user-input events

RxJS is a very common libary used in Angular to help with observables. The essential concepts in RxJS which solve async event management are:

  • Observable: represents the idea of an invokable collection of future values or events.
  • Observer: is a collection of callbacks that knows how to listen to values delivered by the Observable.
  • Subscription: represents the execution of an Observable, is primarily useful for cancelling the execution.
  • Operators: are pure functions that enable a functional programming style of dealing with collections with operations like map, filter, concat, reduce, etc.
  • Subject: is equivalent to an EventEmitter, and the only way of multicasting a value or event to multiple Observers.

To learn more about Observables and RxJS, you can visit this link

Passing Data In Between Angular Components

When designing components, you have to think about how they're going to be used and what they will be performing. More times than not, data will be passed around from the parent to child component, and vise versa. Angular helps this processes by providing the @Input to dictate how data should flow.

@Input() Decorator - Recieve Data From The Parent

Consider the following hierachry:

<parent-component>
  <child-component></child-component>
</parent-component>

The <parent-component> should serve data to it's child component <child-component>. @Input lets the child component recieve data from it's parent component.

// parent.component.ts

export class ParentComponent {
  currentName = "Cisco";
}

// child.component.ts

export class ChildComponent {
  // Component's property "name" inputs data from it's parent
  @Input() name = "";
}
<!-- parent.component.html-->
<child-component [name]="currentName"></child-component>

<!-- child.component.html-->
<p>Blog owner's name: {{name}}</p>

Today's There's seems to be a lot going on but I'll explain the walk through. We have two different components. The ParentComponent & the ChildComponent.

  1. The ParentComponent's class contains a property called customerName which is asigned to 'Cisco'
  2. The ChildComponent class has a @Input property named name. This tell the component to expect to recieve this value.
  3. In the parent.component.html file, we instantiate the ChildComponent & use property binding to bind the name property in the child to the currentName property of the parent.
  4. Last but not least, we use string interpolation to render the value of name that the @Input recieved from the currentName property in the parent component.

@Output Decorator - Send Data To The Parent

A child component can signal an event to notify it's parent of a change. In order to do this, Angular let's you implement the @Output() decorator. In order to submit an event, Angluar offers the EventEmitter method to send an event from a child to a parent. Combining @Output and EventEmitter pushes data back up the hierarchy.

Consider this snippet taken from the Angular site. item-output.component is the child to a parent component component.

// src/app/item-output/item-output.component.ts

export class ItemOutputComponent {
  @Output() newItemEvent = new EventEmitter<string>();

  addNewItem(value: string) {
    this.newItemEvent.emit(value);
  }

}
  • @Output() - The decorator function marking the property as a way for data to go from the child to the parent.
  • newItemEvent - The name of the @Output() decorator.
  • EventEmitter<string> - The @Output()'s type. This tells Typescript what to expect (in this case, a string)
  • new EventEmitter<string>() - Tells Angular to create a new event emitter and that the data it emits is of type string
  • addNewItem(string) - Class method that takes in a user value and calls the classes' @Output decorator's method newItemEvent and emits the value

Below is the child component's template.

<!-- src/app/item-output/item-output.component.html -->

<label for="item-input">Add an item:</label>
<input type="text" id="item-input" #newItem />
<button type="button" (click)="addNewItem(newItem.value)">
  Add to parent's list
</button>

The child's template has two controls. The first is an HTML <input> with a template reference variable #newItem, where the user types in an item name. The value property of the #newItem variable stores what the user types into the <input>. The second element is a <button> with a click event binding. The (click) event is bound to the addNewItem() method in the child component class. The addNewItem() method takes as its argument the value of the #newItem.value property.

Now let's move onto the parent component and template.

// src/app/app.component.ts

export class AppComponent {
  items = ["item1", "item2", "item3", "item4"];

  addItem(newItem: string) {
    this.items.push(newItem);
  }
}

The addItem() method takes an argument in the form of a string and then adds that string to the items array.

<!-- src/app/app.cpmponent.html-->

<app-item-output (newItemEvent)="addItem($event)"></app-item-output>

<!-- Render Items in 'items' property -->

The event binding ((newItemEvent)='addItem($event)) connects the event in the child newItemEvent, to the method in the parent, addItem(). The $event contains the data that the user types into the <input> in the child template UI.

Two-way Data binding

As mentioned previously, Angular can implement two-way data binding (mentioned under the "Template" in the "Component" section) to give components a way of listening to events and updating values between parent and children component. Two way binding combines the @Input decorator and the @Output decorator under a child function. In the parent template, you must combine the property binding and event binding syntax ( [(property)] ) in order to use two-way-binding.

Below is an example from the Angular site.

// src/app/sizer.component.ts

export class SizerComponent {

  @Input()  size!: number | string;
  @Output() sizeChange = new EventEmitter<number>();

  dec() { this.resize(-1); }
  inc() { this.resize(+1); }

  resize(delta: number) {
    this.size = Math.min(40, Math.max(8, + this.size + delta));
    this.sizeChange.emit(this.size);
  }
}

SizerComponent is the child component to AppComponent. In SizerComponent...

  • We declare an @Input decorater property named size which we expected to get from the parent component. We expect the type to be either a number or string
  • We declare an @Output decorater proptery named sizeChange which will send data back to the parent component. We instantiate a new EventEmitter which will emit() ( or send ) a value of number
  • We declare 2 methods called dec() & inc() which call resize and respectively decrease and increase the value of size
<!-- src/app/sizer.component.html -->

<div>
  <button type="button" (click)="dec()" title="smaller">-</button>
  <button type="button" (click)="inc()" title="bigger">+</button>
  <span [style.font-size.px]="size">FontSize: {{size}}px</span>
</div>

sizer.component.html is the template to the child component SizerComponent. Here we just event bind two buttons to their respected method and use string interpolation to render the value of size.

<!-- src/app/app.component.html -->

<app-sizer [(size)]="fontSizePx"></app-sizer>
<div [style.font-size.px]="fontSizePx">Resizable Text</div>

In the parent component AppComponent's template, we instantiate <app-sizer> and use two-way binding to handle AppComponent's fontSizePx property.

<app-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></app-sizer>

Above is the just the expanded version of app.component.html. The $event variable contains the data of the SizerComponent.sizeChange event. Angular assigns the $event value to the AppComponent.fontSizePx when the user clicks the buttons.

Angular's CLI

Angular's CLI is one of my favorite features about Angular. Angular's CLI is a tool that you use to initialize, develop, scaffold, and maintain Angular applications directly from a command shell.

A few of the main commands are as followed:

  • ng new <project-name> - creates a new Angular project folder and generates a new application skeleton.
  • ng generate <schematic> <name>- Generates and/or modifies files based on a schematic.
  • ng add <libary-name> - Adds support for an external library to your project.
  • ng serve - Builds and serves your app, rebuilding on file changes.
  • ng test - Runs unit tests in a project.

One of the reasons why I love the CLI so much is because of the ng generate command. Let's say I wanted to create a new component. If I run ng generate component components/NewComponent, the CLI will automatically generate a component folder called NewComponent under the /components folder including an HTML template, CSS file, test unit and module. It will also automatically import it into my declarations parameter in the AppModule file so I don't forget.

The same thing can happen with different schematic such as service, web-worker, or class. For a list of different schematics, you can take a look at Angular's documentation here.

And That's All!

Angular is defintely a complex framework, but it makes sense once you spend more time with it. It has a specific structure for a reason, and after some time you can understand why some enterprises choose Angular over a less opinionated option like React. Although there seems to be 20 things happening at once in an Angular application, it's pretty easy to track what's happening once you understand the fundementals of the framework. I'll defintely be using this blog as a reference for future Angular projects!