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 ....
AppModule
is being imported fromapp/app.module.ts
where theNgModule
livesplatformBrowserDynamic()
is using it'sbootstrapModule()
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 utilizesngSwitchCase
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
- Import the
CustomService
type - We inject the
CustomService
into the constructor and give it a private instance namedcustomService
- We create a method called
getAPIData()
that calls thegetData
method from theCustomService
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
.
- The
ParentComponent
's class contains a property calledcustomerName
which is asigned to 'Cisco' - The
ChildComponent
class has a@Input
property namedname
. This tell the component to expect to recieve this value. - In the
parent.component.html
file, we instantiate theChildComponent
& use property binding to bind thename
property in the child to thecurrentName
property of the parent. - Last but not least, we use string interpolation to render the value of
name
that the@Input
recieved from thecurrentName
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 stringaddNewItem(string)
- Class method that takes in a user value and calls the classes'@Output
decorator's methodnewItemEvent
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 namedsize
which we expected to get from the parent component. We expect the type to be either anumber
orstring
- We declare an
@Output
decorater proptery namedsizeChange
which will send data back to the parent component. We instantiate a newEventEmitter
which willemit()
( or send ) a value ofnumber
- We declare 2 methods called
dec()
&inc()
which callresize
and respectively decrease and increase the value ofsize
<!-- 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!