Category Archives: Angular Fundamentals

Angular – basics

Courses I learnt from:

Creating and communicating between components

Every created component must be registered in it’s module’s declarations.

In Html template you can use:

  • string interpolation (one-way) {{ }} to display data in component;
  • property binding [property]=”value” to assign values;
  • event binding (click)=”handleClicked($event)” to handle events;

Passing parameters from parent component to child.

In child use @Input decorator: @Input() myProperty: any. In parent bind using: <child-component [myProperty]=”myObjectToPass”>.

Event handling

<button (click)=”handleClick($event)” />

Passing data form child to parent

In child create an event and raise it in any moment.

@Output() myEvent = new EventEmitter();

this.myEvent.emit(‘any info here’);

In parent handle that event.

<child-component (myEvent)=”handleChildEvent($event)” />

Template variables – another way to access child component properties and methods.

In parent define the variable: <child-component #theChild />

And use it <span> {{ theChild.Name }} </span>

Exploring the Angular Template Syntax

*ngFor – use repeat something in loop:

<div *ngFor="let item in items"><child-component [theItem]="item" />

*ngIf – use to handle conditions, i.e. do not print something when there is no data, i.e. <div *ngIf=”user?.name” />. It full removes items from the HTML structure, not just hide them.

[hidden] – use to hide element in structure, but not fully remove from DOM, i.e. <div [hidden]=”!user?.name” />

*ngSwitch – typical switch, i.e. <div [ngSwitch]=”user?.location” ><span *ngSwitchCase=”city”>

*ngClass and *ngStyle – use to bind CSS.

<div [class.green]="user?.location === 'village' /> <!-- applies css class 'green' if condition is met. -->

<div [ngClass] = "{green: user?.location === 'village', bold: user?.important === 'village'} /> <!--applies green and bold classes when conditions are met -->

<div [ngClass] = "getCssClasses()" /> and in ts:

getCssClasses() {if (anyCondition) return 'green bold' else ''}

Creating reusable services

Create service

Create injectable service: @Injectable() export class MyService { getItems(){}; }

Add service in module as a provider to make it injectable for angular.

Use service in ctor: constructor(private theService: MyService) {}

Wrap 3rd party component into a service

Install a node module, i.e. npm install toastr –save. Import styles and scripts in angular.json from this node_modules module path. Create new service, declare global variable toaster and wrap all it’s methods in service. Register new service as a provider and use your service.

Routing and Navigation

Use *-routing.module.ts to define routes, redirections, quards.

ActivatedRoute – inject it in ctor to use i.e. current route param: this.route.snapshot.params['id'] for route like /my-route/:id

[routerLink] – use to navigate from html, i.e. <div [routerLink]="['/items', item.id]" routerLinkActive="active-link-style" [routerLinkActiveOptions]="{options: exact=true}">

[Router] – service to navigate from code, i.e. this.router.navigate(['/items'])

RouteGuard – disallows user to enter or leave a page.

Create service EventRouteActivator implements CanActivate. canActivate method must be implemented according to your logic. Just use router and navigate to any error page when page is not allowed. Return true when user can access page. Add service to the providers. In routing add canActivate: [EventRouteActivator]. The same way you can use canDeactivate. Instead services you can use functions in routing as well.

Resolver – use to load data earlier or do some prechecks before loading component. Imagine you have component which displays some items. Typically you could call some API in onInit to get this data. But you can use resolver to make it even before initalization of the component. In routing define resolver, it’s just a service to be implemented which returns data as observable. It will execute resolver service and put result (items) in route parameter. In onInit you can just read already returned data. In consequence component will not appear until data cames.

I.e. in routing file:

//in routing
{path: "my-item/:itemId", resolver: {item: MyItemResolver}}

//in resolver injectable service, only one method to be implemented:
resolve(route: ActivatedRouteSnapshot) {
return this.itemsService.getEvent(route.params['itemId']);
}

//and then in component ts get resolved data: 
ngOnInit() {
this.item = this.route.snapshot.data['item'];
//or
this.route.data.forEach((i) => this.item = data['item'];

Collecting and validating data with forms

Forms building blocks:

  • FormControl – corresponds to single input, provides changes tracking, validation
  • FormGroup – collection of FormControls. Each FormControl is a property in group (name is the key). Group just aggregates controls and allows for example easier validation check.
  • FormArray – similar to FormGroup, but the array

FormBuilder helps to build these blocks.

let address= new FormGroup({
    street : new FormControl(""),
    city : new FormControl(""),
    pinCode : new FormControl("")
}); 

let addressValue = address.value;
let streetValue = address.get("street").value;
let streetValue2 = address.street.value;
let addressIsValid = address.valid;

There are two types of forms in angular:

  • template-based – for simple scenarios, fully created in html
  • model-based (reactive) -logic in component rather than in html complex scenarios, to avoid logic in html and make unit testing possible

Template-based form

Here we configure everything for the form in HTML template. We use ngForm directive. It creates for us top-level FormGroup instance, instances of FormControls for each input with ngModel directive, FormGroup instances for each NgModelGroup directive.

ngModel – bind data into form control to model:

  • one-way binding (form to model) <input (ngModel)="itemName" name="itemName" id="itemName" type="text" />
  • two-way binding <input [(ngModel)]="itemName" name="itemName" id="itemName" type="text" />

ngSubmit – for submitting data in form: <form #myForm="ngForm" (ngSubmit)="sendIt(myForm.values)"> <!–#myForm syntax create template variable, you can use it in other place in you template i.e. myForm.form.valid or in ts code by @ViewChild(‘myForm’, null) theForm: NgForm.

Validation (template-based) – form and it’s fields have a few usefull properties: valid, pristine, dirty (if somebody typed something into the field), touched (when somebody enter and left the field), invalid.

<form #itemsForm="ngForm" (ngSubmit)="saveIt(itemsForm.value)">
<em *ngIf="myForm.controls.itemName.invalid">Required</em>
<input [(ngModel)]="itemName" name="itemName" id="itemName" type="text" pattern="[A-Z]" required />

<button type="submit" [disabled]="myForm.invalid">Send</button>

Model-based form

Here we configure everything for the form in code (new component ts file) and do some configuration in html template. In component ts add FormGroup and FormControls in it. Then in html bind it, more or less like this:

myForm: FormGroup 
let myItem = new FormControl(this.itemName, [Validators.Required, Validators.pattern('[A-Z]')])
this.MyForm= new FormGroup( {itemName: myItem})
<form [formGroup]="myFormGroup"><input formControlName="itemName">

You can set or patch values of a FormControl, FormGroup or FormArray. Using set you must update all values of a group, patch can update only some of them.

//set values
    let address= {
      city: "My City",
      street: "My Street",
      pincode: "00001",
    };
    this.reactiveForm.get("address").setValue(address); 

//patch value
    let address= {
      street: "New street",
     };
     this.reactiveForm.get("address").patchValue(address);

Custom validator – just a function which return null if control is valid or an error object when it is invalid, i.e. private restrictedWords(control: FormControl): {[key: string]: any} { if() return null else {'restrictedWords': 'any error message'}}.

You can subscribe to validation status changes:

//subscribe to the event which provides validation changes
this.myForm.statusChanges.subscribe(newStatus => {
    console.log(newStatus)
})

//you can disable emitting this event while setting value:
this.myForm.get("name").setValue("", { emitEvent: false });

//you can emit status for control, but not bubble it to the parent form: 
this.myForm.get("firstname").setValue("", { onlySelf: true });

You can subscribe to value changes:

this.myForm.get("firstname").valueChanges.subscribe(x => {
   console.log('firstname value changed')
   console.log(x)
})

Reusing components with Content Projection

Imagine popup dialog component with different contents inside. We would like to reuse dialog and pass different contents.

ng-content – use to replace content in some generic component. You can use selectors when you want to have multiple contents, i.e. <ng-content selector=”[ct-title]”> and then in specific content you need to have something like: <span [ct-title]>…

<!-- parent container with span content
<div class="my-container" [title]="item.name">
<my-content-component>
<span>{{item.description}}</span>
<span>any specific content here</span>
</my-content-component>

<!-- reusable my-content-component -->
<div class="importantContent">
<span>{{title}}</span>
<ng-content></ng-content> <!-- here the specific content will appear automatically -->

Displaying Data with Pipes

Use pipes to format data,i.e. {{title | uppercase }}, {{startDate | date:’short’}}, {{price | currency:’USD’ }}

Custom pipe:

@Pipe({name: 'duration'})
export class DurationPipe implements PipeTransform {
transform(value: number): string {
... any logic to return string
}
}
// add it to module declarations
// use: {{myNumber | duration}}

Filtering and sorting

Do not use pipes for that, to avoid performance issues.

Filter – generally you should have in your component field: filteBy: string, items: Items[], visibleItems: Items[]. Create filterItems() method:

filterItems(string filter) {
if (filter == 'all') {
this.visibleItems = this.items.slice(0); // just copy the array
} else {
this.visibleItems = this.items.filter(i => {return i.name.toLocaleLowercase() === filter})
}
}

Sorting – similar way like filtering

//filter on your own in the component:
this.visibleItems = this.sortBy === 'name' ? this.visibleItems.sort(sortByNameAsc) : this.visibleItems.sort(sortByDateAsc)

//
function sortByNameAsc(Item i1, Item i2) {
 if (i1.name > i2.name) return 1;
...
}

ChangeDetectionRef – component to detect changes in model and refresh the view.

//two strategies
//by default it refreshes the view when data has been modified
//onPush refreshes only when property has been assigned with new reference (new object), but mutation is not refreshing
@Component({
  templateUrl: './customers.component.html',
  styleUrls: ['./customers.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})

//when using OnPush, you can force refresh on demand
public constructor(private cdr: ChangeDetectorRef){}
public Refresh: void() {this.cdr.detectChanges();}

//there is also option to add specific input to be checked when next change detection will happen
this.cdr.markForCheck();
//two strategies
//by default it refreshes the view when data has been modified
//onPush refreshes only when property has been assigned with new reference (new object), but mutation is not refreshing
@Component({
  templateUrl: './customers.component.html',
  styleUrls: ['./customers.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})

//when using OnPush, you can force refresh on demand
public constructor(private cdr: ChangeDetectorRef){}
public Refresh: void() {this.cdr.detectChanges();}

//there is option to detach the view from change detection
this.cdr.detach()

//there is also option to add to be checked when next change detection will happen, often used in subscriptions
this.cdr.markForCheck();

Angular’s Dependency Injection

If you need to inject any class, you need to register it in providers and then pass to ctor.

InjectionToken – key for dependency injection system. You can use it to register in dependency injection an existing object (useValue).

//
//in service
export let TOASTR_TOKEN = new InjectionToken<Toastr>('toastr') //create interface Toastr for this purpose

//in app module
//create toastr:
declare let toastr: Toastr
and add it to provider: 
{provide: TOASTR_TOKEN, useValue: toastr}

//then inject TOASTR_TOKEN where you need it in component
import {TOASTR_TOKEN, Toastr} from ....
constructor(@Inject(TOASTR_TOKEN) private toastr: Toastr)

UseClass – specific way to register providers. Decide which implementation should be passed when somebody injects a service, i.e. in providers do {provide Logger, useClass: FileLogger}

UseExisting

UseFactory

Directives and Advanced Components

Use directives to have some logic which you don’t like to bind with component where you use it.

<button my-test="any message">Do</button><!--directive usage-->

@Directive({
selector: '[my-test]'
})
export class MyTestDirective implements OnInit {
private el: HTMLElement;
@Input('my-test') myTest: string; //receive directive parameter ('any message')
constructor(ref: ElementRef) {
this.el = ref.nativeElement; //gets element to which directive is bound
}
ngOnInit() {
//any logic
console.log(myTest);

You can use ViewChild to reference HTML element in componentCode:

<!--in html template-->
<div #myDiv>something</div>

//then in ts
@ViewChild('myDiv') el: ElementRef;
//there is also @ViewChildren and @ContentChild, @ContentChildren (for ng-content scenario)

Add simple setting on component:

<!-- in html template -->
<div logOnClick="true">anything</div>

//then use it in ts
@Input() logOnClick: string;
...
if (logOnClick) {
console.log('hello');

More Components and Custom Validators

To validate multiple related fields you need to write your own directive. Imagine you have form with phycisal location (state, city, street) or online location (url) – and one of these must be filled.

@Directive({
selector: '[validateLocation]',
providers: [{provide: NG_VALIDATORS, useExisting: LocationValidator, multi: true}] //register our validator in validators registry
})
export class LocationValidator implements Validator {
validate(formGroup: FormGroup): {[key: string]: any} {
//find appropriate controls in template
let addressControl =  formGroup.controls['address'];
let countryControl =  formGroup.controls['country'];
let cityControl =  formGroup.controls['city'];
let onlineUrlControl = (<FormGroup>formGroup.root).controls['onlineUrl'];

//check their values
if((onlineUrlControl && onlineUrlControl.value)) || ...) {
return null; //validation succeeded
}
return {validateLocation: false} //validation fails

<!-- then use it in html template-->
<div ngModelGroup="location" #locationGroup="ngModelGroup" validateLocation>

Communicating with the Server, HTTP, Observables, Rx

Promise – represents single value in the future.

Observable – represents zero or more values now or in the future.

RxJS Pipe operator

We use observable to work with data streams. It is concept from RxJS module. Usually we use Pipe operator to handle observable data.

There is nice tutorial about all of them: https://www.tektutorialshub.com/angular-tutorial/#observable-in-angular

Here are some remarks about a few of them.

switchMap

Projects each value of source observable to another result observable. When it subscribes to new result observable it unsubscribes from source one. It can be used to read ActivatedRoute.paramMap parameter only once and do not listen to it’s next changes. Similarly it can be used to handle ValueChanges event.

ngOnInit() {
 
    this.activatedRoute.paramMap
      .pipe(
        switchMap((params: Params) => {
          return this.service.getProduct(params.get('id'))
        }
        ))
      .subscribe((product: Product) => this.product = product);
}

mergeMap – from source observable items creates result observables. Does not cancel or unsubscribe anything, just combines all results without any ordering. Can be used to merge multiple HTTP calls. Imagine you have first call which returns observable of main categories and for each category you want to ask for all subcategories and merge them into one observable. You can use forkJoin to merge multiple inner observables

concatMap – mergeMap but maintains the order of result observables.

exhaustMap – from each source observable item creates new observable. But! While receiving items from new observable ignores the values from source observable. When new observable has completed, it again considers next value from source observable. Use to ignore duplicated events, i.e. user clicks button twice, but the second time will be ignored if the handling for the first one did not finish yet.

scan – for each source observable item does some accumulative function and it’s result is an input for handling the next item. For example for each item can log sum of all items which came so far.

reduce – like scan, but returns only the last result, when observable completes.

RxJS Subject

Subject is observer and observable in one. It can publish values and subscribe to them.

BehaviorSubject – when publishing values stores the last one. When next subscriber subscribes it immediately gets the last published value.

ReplaySubject – like BehaviorSubject but new subscriber gets all published item, not only the last one.

AsyncSubject – as BehaviorSubject, but publishes last value only when it’s completed observable.