Angular Notes (for beginners)

Shubham Chitranshi
11 min readOct 24, 2020

Overview

This page documents the learning from my angular course online. Keep a track of some notes

Getting started

Install angular and node on your machine.

ng new test-app

create a new app

ng serve

run the app in dev mode (auto-reload)

ng generate component menu-bar
# alternative shortcut
ng g c menu-bar

create a new component

The way Angular bootstraps

Any SPA is just an index.html file bundled with some javascript. If you open the index.html file you will see the <app-root></app-root> tag, this is the main selector coming from app.component.ts file. When the angular CLI packages the front-end it puts the scripts in the index.html file, that knows what to replace with app-root. this configuration comes from app.module.ts file in the app folder, this is a Angular module (more on this later).

Angular works with annotation tags that are placed on top of the classes to identify what type it is. this annotation takes some configuration in form of a javascript object. An example is the @Component tag that takes in a JS object -

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})

another example is ngModule -

@NgModule({
declarations: [
AppComponent,
ServerComponent,
ServersComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})

By default angular does not scan each package to discover components (like in spring boot). You have to explicitly declare the name of the component in the declarations list in the @ngModule object.

Data-binding

Communication between classes and template (html) is data-binding.

classes to html — String interpolation, property binding

html to classes — event binding

two way binding — both ways

String interpolation

Between the {{ double curly braces }} you can write typescript expression.

Property binding

the html elements have properties like (<button disabled>) these properties are set in the dom element while rendering, with property binding you can bind these properties to a expression

<button [disabled]="isAllowed"></button>

now this html property is bound to the isAllowed variable in typescript. We can write expression within “double quotes“.

Event Binding

Event binding uses parentheses and double quotes to write the expression (normally a method).

All event provided by the dom like onClick translates to (click)

You can use $event it is a reserved keyword that you can pass in the method call to get input events

<input type="text" class="form-control"
(input)="onUpdateServerName($event)"/>

in the component.ts class you can assign a variable with the value coming from this event.

There is an easier way to do this, two way data binding will allow you to bind an action to a variable directly.

<input
type="text"
class="form-control"
[(ngModel)]="serverName"/>

NgModel is a directive, more on directive later.. to import this add the import { FormsModule } from '@angular/forms'; in your module.ts and add FormsModule in imports

Directives

Directives are instructions in the DOM, components are directive, when we place a custom tag (<app-sever></app-server>) we instruct angular to replace that tag with the html and business logic that we have in our component.

Build in directives — ngIf for example is a build in directive, (used with a star because it changes the structure of the element) <p *ngIf="isServerCreated">Server was created with ServerName: {{serverName}}</p>

When we use ngIf, the element is not present in the dom until the condition is true. it is created and injected dynamically.

local reference in the element — <ng-template #noServer><p>no server created</p></ng-template>

and then refer to this marker in ngIf — <p *ngIf=”isServerCreated;else noServer>Whatever here</p>

ngStyle is another build in directive, it is attributed directive so it is not used with *,

<p [ngStyle]=”{backgroundColor: getColor()}”> status is : {{status}} </p>

Model

Models in Angular are simple POJOs which are exported as contracts

export class Recipe {
public name: string;
public description: string;
public imagePath: string;
constructor(name: string, desc: string, imagePath: string) {
this.name = name;
this.description = desc;
this.imagePath = imagePath;
}
}

Typescript provides us an easy way to write the class above

export class Recipe {    constructor(public name: string, public desc: string,
public imagePath: string) {}
}

debug tool — augury as chrome extension gives more insight on the angular app

Passing data in component

By default a property defined in the component is only accessible inside the class. To allow the property to be accessible from the tag we use the decorator — @Input()

@Input() server: { name: string, status: string };
@Input('serverElement') server: { name: string, status: string }; // passing custom alias

The way to use it

<app-server *ngFor="let server of servers" [server]="server"></app-server>

the example above shows passing of model from parent to child. We can also pass an event from child to parent.

The way to do this is to use EventEmitter

@Output() serverCreated = new EventEmitter<{ name: string, status: string }>();

now where you handle the event, call this method with .emit

createServer() {
this.isServerCreated = true;
this.serverCreated.emit({
name: this.serverName,
status: Math.random() > 0.5 ? 'online' : 'offline'
});
}

Now reference the method in the tag like -

<app-add-server (serverCreated)="onAddServer($event)"></app-add-server>

Now write the method onAddServer in your parent component expecting the event to have the object emitted

Using local reference in html

We can add a localReference name to any html element such as input and use it in the template. the use of this property is when you want to pass the actions on the dom via a function call

<input type="text" #serverName/>
<button (click)="onAddServer(serverName)">Add server</button>

and now you have access to the whole element inside the function.

we can also use @ViewChild to get access to the html element

in html

<input #serverName type="text" />

in typescript

@ViewChild('serverName', {static:true}) inputElement: ElementRef;
inputElement.nativeElement.value;

ng-content

it is a directive, if you place this tag <ng-content></ng-content> in the html, whatever is passed between the selector will be applied here

<p> this is it</p>
<ng-content></ng-content>
<app-server>
<p> this is it again</p>
</app-server>

to get access to the ng-content use @ViewContent(‘tag’)

Custom Attribute Directive

Directive are way to give instructions to the display element (DOM usually)

create a directive -

ng g d directives/myDirective

the selector of the directive is [between square bracket] to show that it will be used in the DOM attribute as attribute.

@Directive({
selector: '[appBasicHeighliter]'
})
export class BasicHeighliterDirective implements OnInit {
constructor(private renderer: Renderer2, private elementRef: ElementRef) { }

ngOnInit() {
this.renderer.setStyle(this.elementRef.nativeElement, 'color', 'red');
}
}

Angular is not limited to browser render so if we change the elementRef directly it will give an error. renderer encapsulates this.

if you want to listen to the events on the element the directive sits on use @HostListener

@HostListener('mouseenter') mouseOver(eventData: Event) {
this.renderer.setStyle(this.elementRef.nativeElement, 'color', 'green');
}

another way to change the property is using HostBinding

@HostBinding('style.backgroundColor') backgroundColor:string = 'transparent'

you can take inputs in the directives the same way we saw before

@Input() color:string

in the DOM element you can refer to this property as -

<p directiveName [color]="'red'">
or even better
<p directiveName color="red">

Custom Structural Directive

@Input() set method(value:boolean) {
if(value) {
this.vcRef.createEmbeddedView(this.templateRef);
}else {
this.vcRef.clear();
}
}
constructor(private templateRef:TemplateRef<any>, private vcRef:ViewContainerRef) {}

Service and dependency injection

Service is used as a central repository (stores state and share it across components). Reduce duplication of code.

Service Creation — it is a same typescript class

export class LoggingService {
log(message:string) {
console.log(message);
}
}

even though this is a simple class and you can create an object by your own, you will instead tell angular to inject this via constructor, and also tell angular that this class is a provider

@Compoonent({
selector: 'app-recipe-list',
templateUrl: './recipe-list.component.html',
styleUrls: ['./recipe-list.component.css'],
providers: [LoggingService] # you don't need to add the injection here if the provider is added in the app.module.ts
})
export class RecipeList {
constructor(private log:LoggingService) {}
}

Service injection works in hierarchy, if we inject a service in app-component, the same instance will be available in the sub components, if we explicitly tell the sub component to get a instance of service (by defining in providers) angular will override the one received from parent.

If you want to inject one service in another, Angular will not do that by default because Angular does not handle your service classes (no annotations like @Component) so to explicitly tell angular to inject a service or any component within a class you annotate it will @Injectable

@Injectable()
export class LoggingService {
log(message:string) {
console.log(message);
}
}

you add this to the receiving service, but add it in every service, as Angular recommends it.

So now you can add EventEmitter in the service and add subscribe in the components you want to listen to, thus transferring the state change and events to any components across.

this.service.updatedEvent.subscribe((status:string)=> {// whatever here})

Routes

In app module add the route const

const appRoutes: Routes = [
{ path: 'users', component: UserComponent },
{ path: 'servers', component: ServerComponent },
{path: '', component: HomeComponent}
]

in the imports add the route constant to the RouterModule

RouterModule.forRoot(appRoutes)

Since this is a single page application, all the components are rendered in the app.component.ts, but it still needs to know where to display the component.

in the app.component.ts where you want to display the content based on the route

add

<router-outlet></router-outlet>

this is a angular directive, that will display the page based on the route in the address bar

If we add the links (“/users”) in the href it would still load the page, but the call is made to the server

Giving /page is absolute path, just page is a relative path (path appended to your current path)

 routerLink="/users" vs routerLink="users"
use / in the link, wherever possible.

and the links where you want to go

<a routerLink="/users"></a>
or even better
<a [routerLink]="['/users']"

For the visual indication of the selected tab, we can use angular directive,

routerLinkActive is a directive, that you can supply the class to indicate the selected
<a [routerLink]="/servers" routerLinkActive="active">servers</a>

The default home page with route “/” will always be active for any sub paths. to configure routerLinkActive to choose exact path, [routerLinkActiveOptions]=”{exact:true}”

set route CSS class

routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}"

to navigate programatically

constructor(private router:Router) { }
this.router.navigate(['/servers'])

insider the typescript class, the component does not know the path by default.

To get the current path we import ActivatedRoute

constructor(private route:ActivatedRoute, private router:Router){}this.router.navigate(['server'], {relativeTo: this.route}) #this will not work with relative path.

to handle parameters path — /users/1

in appModule -> routes ->
{ path: 'users/:id', component: UserComponent },

to get parameters dynamically in component.ts inject

when we load the component from within the page, Angular is optimized to not change the DOM or recreate the component, so a link on the same page will not re-create the page, to make those changes, we need to subscribe to the activatedRouter params observable.

constructor(private route:ActivatedRoute) { }
ngOnInit() {
this.id = this.route.snapshot.params['id'];
this.route.snapshot.params.subscribe(
(params:Params) => this.id = params['id']
)
}

Query parameters —

<a 
[routerLink]="['/server', 'edit']"
[queryParams]="{allowEdit:1}">server edit</a>
in typescript
this.router.navigate(['/server', id, 'edit], {queryParams:{allowEdit: "1"}, fragment: "loading"});

For fragments

<a 
[routerLink]="['/server', 'edit']"
[queryParams]="{allowEdit:1}"
[fragment]="'loading'">server edit</a>

Nested routes

child routes are rendered in the component page they look like -

{ path: 'servers', component: ServersComponent, children: [
{path: ':id', component: ServerComponent},
{path: ':id/edit', component: EditServerComponent}
] }

So we add the router-outlet in the rendering component too.

For redirect instead of defining component

{path: 'not-found', component: PageNotFoundComponent},
{path: 'something', redirectTo: '/not-found'}
You can also add wildcard to path ** but make sure it is the last entry
{path: '**', redirectTo: '/not-found'}

Auth Guards —

auth guard is used to protect routes based on some preconditions, it implements canActivate method, its a service.

@Injectable({
providedIn: 'root'
})
export class AuthGuardService implements CanActivate {
constructor(private router:Router) { }
canActivate(route:ActivatedRouteSnapshot, state:RouterStateSnapshot):
Observable<boolean> | Promise<boolean> | boolean {
return true;
// you want to redirect in case of false;
// this.router.navigate(["/"]);
}
}

in the routes you can specify in the paths params, the canActivate

{ path: 'servers', canActivate:[AuthGuardService], component: ServersComponent}

You can also use canActivateChild to protect the children paths.

canActivateChild(route:ActivatedRouteSnapshot, state:RouterStateSnapshot): boolean |
Observable<boolean> | Promise<boolean> {
return this.canActivate(route, state);
}

And then in the paths use canActivateChild, this will allow you to set guards on the child paths instead of the parent one, the parents will have unguarded

{ path: 'servers', canActivateChild:[AuthGuardService], component: ServersComponent}

Deactivate Guard

DeactivateGuard are used to stop users from leaving a component or page. As an example if you are on an edit page and accidentally click back, you would want the ask if you want to save the changes. So the deactivateGuard are like service that would have to access a state in your component, so you write an interface.

export interface CanComponentDeactive {
canDeactive: () => Observable<boolean> | Promise<boolean> | boolean;
}
@Injectable({
providedIn: 'root'
})
export class CanDeactivateGuardService implements CanDeactivate<CanComponentDeactive> {
constructor() { }
canDeactivate(component: CanComponentDeactive,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState?:RouterStateSnapshot):
Observable<boolean> | Promise<boolean> | boolean {
return component.canDeactive();
}
}

and now implement this interface in the component where you apply the logic of what to do if a user navigates back.

canDeactive(): Observable<boolean> | Promise<boolean> | boolean {if (!this.changeSaved) {
confirm("are you sure you want to move");
return true;
} return true;
}

resolve guards —

Based on the route path, if you want to load some data in the component, you can use resolve guard, this will ensure that the data is loaded before the component is loaded. It is a service which implements Resolve

import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
interface Server {
id: number,
name: string,
status: string
}
export class ServerResolver implements Resolve<Server> {constructor(private serverService:ServerService) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Server | Observable<Server> | Promise<Server> {
// you will get the routes here
// so you can use route.params.id to get the id in the route link
return this.serverService.getService(route.params.id);
}
}

and in the routing paths add —

{
path: 'servers/:id', component: ServerComponent,
resolve: { server: ServerResolver }
}

finally in the component you can use

ngOnInit() {
this.route.data.subscribe((data:Data) => this.server = data.server)
}

RSJX and Observable

Observable wrap data streams, the source could be anything from file read to Http calls

they have three types of events — next(), error, complete. And these are captured in this sequence.

this.myObs = Observable.create((observer) => {
const count = 0;
setTimeout(() => {
observer.next(count);
count++;
// some comparison logic here
observer.error("Count bla");
// some comparison logic here
observer.complete();
}, 1000)
});
this.myObs.pipe(... pipes here).subscribe((data)=>
{
console.log(data);
}, error => console.log(error), () => {
console.log("completed");
});
// to unsubcribe
ngOnDestroy() {
this.myObs.unsubscribe();
}

We can pipe the the output from observable to an operator and subscribe to these operators, instead.

obsObject.pipe(map(
data => return transformed(data))).subscribe(data => // transformed data);

this is a rsjx functionality and all the operators can be found here —

https://www.learnrxjs.io/learn-rxjs/operators

Subject instead of EventEmitter

so instead of using EventEmitter we can use subjects, they provide the next method outside, so that can be used across to both emit event and it works same as observable, and subscribe to it.

Now this rsJs also has a provides an alternative where you can have the next function available outside the observerable. its called the subject

eventEmitter = new Subject<valueType>();
eventEmitter.next(value); // in a place you want to emit event
eventEmitter.subscribe(value => do stuffs) // in the place you want to access the change, same as observable.

Angular Forms

Angular provides functionalities to handle form elements,

Angular provides two ways to handle forms —

  • Template driven — angular will read the template defined in html and infer the form elements, providing you the javascript version of the the form to play with
  • Reactive driven — you write the typescript code, and you write the html elements, and combine the two together, this gives more fine grain control over the form elements.

In template driven, we configure in html how angular should action on each form element.

with FormModule imported, angular will automatically detect form tag and try to work with it. You still have to define the which input element is relevant

<input type="text" name="username" ngModel>

use ngModel to tell angular that this input is needed in the form object, use the name tag to define the name of the element. with name this element is added to javascript model

Add ngSubmit referring to the method you want to trigger in the form tag to let angular use the submit behavior of the javascript

<form (ngSubmit)="onSubmit(f)" #f="ngForm">onSubmit(form:NgForm)

you can also have a reference to this form element ngForm from viewchild

@ViewChild('f', {static:false}) form:NgForm;

Validation in forms

Angular can detect form tags to update its ngForm object (template driven),

so you can add required in the tag, some directive tags are made available like email <input type=”text” ngModel email>, now angular adds classes to elements like ng-valid, which we can customize to update the look and feel of these form elements.

input.ng-invalid.ng-touched, select.ng-invalid {
border: 2px solid red;
}

you can get access to an element in the dom by using ref=ngModel

<input type="email" id="email" class="form-control" ngModel name="email" required email #email="ngModel">

References —

--

--