/Angular

Primary LanguageTypeScript

Angular

Set up the environment

Versions used in this project:

Angular CLI: 13.3.10
Node: 16.17.0

Angular version Project Folder
Angular CLI: 13.3.10 02AngularFundamentals.list.authors
Angular CLI: 15 03Router

Install node js, and install Angular CLI with the command bellow:

npm install -g @angular/cli

To validate the installations use:

node --version

ng --version

VS Code Facilities

  • Auto Import

Typescript Fundamentals

  • 'export' in the class permit it be imported as a module in other file.

Angular Fundamentals

To create a new project

ng new project_name

To it run in localhost (the default is localhost:4200)

ng serve

To add a new component in the project

ng g c component_name

The 'c' could be replaced by 's' if you want create a service

Adding bootstrap

The command bellow should add a bootstrap folder with the path that you can import to use bootstrap items.

npm install bootstrap@5.3.0-alpha1

And in your css (like styles.css)

@import "~bootstrap/dist/css/bootstrap.css"

To install all project dependencies (in package.json)

npm install

Components curiosities

  • By default all properties of components are only accessible inside these components, not from outside and that generally is a good thing.

Lifecycle:

  • ngOnChanges: Called after a bound input property changes
  • ngOnInit: Called once the component is initialized
  • ngDoCheck: Called during every change detection run
  • ngAfterContentInit: Called after content (ng-content) has been projected into view
  • ngAfterContentChecked: Called every time the projected content has been checked
  • ngAfterViweInit: Called after the component's view (and child views) has been initialized
  • ngAfterViewChecked: Called every time the view (and child views) have been checked
  • ngOnDetroy: Called once the component is about to be destroyed

Encapsulation

Style encapsulation refers to how CSS styles are applied and isolated within a specific component without affecting other components in the application.

By default is applied:

encapsulation: ViewEncapsulation.Emulated

Emulated: It is the default style encapsulation in Angular. With this option, Angular emulates the scope of styles defined in a component by encapsulating them within a unique attribute automatically generated. This ensures that the styles defined in a component do not affect other components, and vice versa. Emulated encapsulation is the most commonly used option as it is the default.

None: With this option, no style encapsulation is applied. This means that the styles defined in a component will be applied globally throughout the application. The use of "None" encapsulation should be avoided in most cases as it can lead to style conflicts and make maintenance difficult.

ShadowDom: This option utilizes the Shadow DOM technology, which is a part of modern browsers, to encapsulate the styles of a component. Shadow DOM allows for real encapsulation of styles, making them inaccessible to other components. However, support for Shadow DOM may vary among browsers and may require additional configurations.

By default, the styles defined in the app.component.css file will be applied to the root component (AppComponent) and all child components within the component hierarchy. This occurs because the default style encapsulation in Angular is emulated encapsulation (ViewEncapsulation.Emulated). However, it's important to note that styles defined in a child component can override styles defined in the parent component.

Binding

Default format is like:

    <button (<click/keyup>)="methodName($event)" [element.attribute]="<boolean/function that inform the property of the attribute>">Text</button>

Example:

    <button (click)="onSave($event)" [style.backgroundColor]="isActive ? 'blue' : 'white'" class="btn btn-primary">Save</button>

One option to receive values in a form is importing FormsModule in app.module.ts, it will permit use

<input [(ngModel)]="email" (keyup.enter)="onKeyUp()" class="mb-3" />

if you have the attribute in class Component and the method that will receive the new value when the action happen, in this case keyup.enter.

In case you're hitting an error, make sure you have FormsModule added to your imports[] in the AppModule.

Pipes

And a exemple of pipes and a custom pipe in course and the custum pipe is common>summary.pipe.ts in the folder 02* .

  • PipeTransform needs the transform() method

ng-content

is used to create configurable components. To indicate which selector will be used when the data is injected:

And when you call the component that have ng-content it should specefi which data will be injected, like in the exemple bellow:

<my-component>
    <div class=".class-name">Here the div that will be injected together with the text</div>
    ...

gn-conteiner

ng-content is used to display children in a template, ng-container is used as a non-rendered container to avoid having to add a span or a div. And to use it, the only difference from ng-content is that call like this:

Text that will be injected

Directives

Attribute Directives Structural Directives
Modify the structure of the DOM Modify the attributes of the DOM elements
Look like a normal HTML Attribute (possibly with databinding or event binding) Look like a normal HTML Attribute but have a leading * (for desugaring)
Only affect/change the element they are added to Affect a whole area in the DOM (elements get added/removed)

ngIf

Works like a common if but for div or other

<div *ngIf="courses.length > 0" >
    <h2>List of Courses</h2>
    <course></course>
</div>
<div *ngIf="courses.length == 0">
    No courses yet
</div>

Other format, using ng-template to do the same thing:

<div *ngIf="courses.length > 0; else noCourses" >
    <h2>List of Courses</h2>
    <course></course>
</div>
<ng-template #noCourses>
    No courses yet
</ng-template>

Other way is using hidden property, "[hidden]="courses.legth == 0" that will hide if it be true. It's better use ngIf if you're working with a large tree with a lot of children because these elements can take substantial memory and computing resouces. Using ngIf you don't put them in the DOM if you're not going to show them to the user. The change detection mechanism in Angular keeps your views in sync with your components, that's running in the backgroud.

ngSwitchCase

<ul class="nav nav-pills">
    <li class="nav-item">
        <a [class.active]="viewMode == 'map'" class="nav-link" (click)="viewMode = 'map'">Map view Mode
        </a>
    </li>
    <li class="nav-item">
        <a [class.active]="viewMode == 'list'" class="nav-link" (click)="viewMode = 'list'">List view Mode</a>
    </li>
</ul>

<div [ngSwitch]="viewMode">
    <div *ngSwitchCase="'map'">Map view Content</div>
    <div *ngSwitchCase="'list'">List view Content</div>
    <div *ngSwitchDefault>Otherwise</div>
</div>

And ngFor could be used with trackBy to improve performace, look here.

Is a easy way to define which class will be true, like in favorite component:

<i 
    [ngClass]="{
        'bi-star': isFavorite,
        'bi-star-fill': !isFavorite
    }">
</i>

ngStyle

ngStyle could help to achive a clean code, like you can transform this:

<button
    [style.backgroudColor]="canSave ? 'blue':'gray'"
    [style.color]="canSave ? 'white':black"
    [style.fontWeight]="canSave ? 'bold':'normal'"    
>Save</button>

on this:

<button
    [ngStyle]="{
        backgroudColor:=canSave ? 'blue':'gray',
        color=canSave ? 'white':black,
        fontWeight=canSave ? 'bold':'normal'
    }"
>Save</button>

For many cases we prefer use the css style, but sometimes in certain situations, we want to add styles explicitly in the html.

Create custom directives

ng g d directive-name

it will create 2 files and modify app.module.ts

Template-driven Forms

Reactive Template-drive
More control over validation logic Simple validation
Good for complex forms Good for simple forms
Unit testable Easier to create
Less code

ngModel

The ngModel directive binds the value of the input element to a property on the component. This means that any changes made to the input element are automatically reflected in the component property, and vice versa.

If the component property is updated programmatically, such as by calling a method or receiving data from an API, the ngModel directive updates the value of the input element to reflect the new value of the property.

Services

What are services?

It's possible injecting LoggingService like this:

@Component(...)
export class AccountComponent {
  // @Input() & @Output() code as shown in the previous lecture
 
  constructor(private loggingService: LoggingService) {}
}

Or inject it like this, by using the inject() function:

import { Component, Input, Output, inject } from '@angular/core'; // <- Add inject import
 
@Component(...)
export class AccountComponent {
  // @Input() & @Output() code as shown in the previous lecture
  private loggingService?: LoggingService; // <- must be added
 
  constructor() {
    this.loggingService = inject(LoggingService);
  }
}

Specific validation errors for forms

How add a validation in the input:

<input 
    required 
    minlength="3" 
    maxlength="10" 
    pattern="[0-9]+">

How show or check the error:

<div 
    class="alert alert-danger" 
    *ngIf="firstName.touched && !firstName.valid">
    <div *ngIf="firstName.errors?.['required']">
        First Name is required.
    </div>
    <div *ngIf="firstName.errors?.['minlength']">
        First name should be minimum {{ firstName.errors?.['minlength'].requiredLength}} characters.
    </div>
    ...
</div>
  • The way to check the error had change from Angular 4 to 5.
  • Cleaner Template is like in the last code, you put one item by line so when other developer check the code, like which are the requisites for this field, will be easy to find and read.

Styling Invalid Input Fields

.form-control.ng-touched.ng-invalid

ngForm

We have created a template reference variable named myForm that references the ngForm directive on the <form> element. We have also bound the ngSubmit event to the onSubmit() method of our component. Check bellow:

Once you have created your form with ngForm, you can use a variety of directives and tools to bind your form elements to model data and validate user input. Some of the key directives and tools available with ngForm include:

  • ngModel: used to bind an input field to a model property
  • ngModelGroup: used to group a set of input fields together in the form data model
  • ngForm: used to nest forms inside other forms
  • ngSubmit: used to trigger a method in your component when the form is submitted
  • form.controls: provides access to the form controls and their validation state

ngModelGroup

Simplifying form validation: By grouping form controls together, you can simplify your form validation logic. For example, if you have a group of required fields, you can use ngModelGroup to validate them all at once. This can help to reduce the amount of validation code you need to write, and can make your code more readable and maintainable.

Conditional validation: ngModelGroup allows you to perform validation on a group of fields based on a specific condition. For example, you can use ngModelGroup to validate a set of fields only if a certain checkbox is checked or a radio button is selected. This can be very useful for complex forms where some fields are only required or valid in certain contexts.

<form>
  <div ngModelGroup="address">
    <div *ngIf="!contact.valid">... error messages ... </div>
    <label>Street Address</label>
    <input type="text" name="street">
    <label>City</label>
    <input type="text" name="city">
    <label>State</label>
    <input type="text" name="state">
    <label>Zip Code</label>
    <input type="text" name="zip">
  </div>
</form>

Reactive Forms

In Angular, FormControl, FormGroup and FormArray are subclasses of AbstractControl. AbstractControl is an abstract base class that provides common functionality for all types of form controls, including FormControl, FormGroup, and FormArray.

AbstractControl defines the following properties and methods that are common to all form controls:

  • value: The current value of the form control.
  • status: The validation status of the form control (e.g. valid, invalid, pending).
  • valid: A boolean indicating whether the form control is currently valid.
  • invalid: A boolean indicating whether the form control is currently invalid.
  • pending: A boolean indicating whether the form control is currently pending validation.
  • errors: An object containing any validation errors for the form control.
  • pristine: A boolean indicating whether the form control has been touched by the user.
  • dirty: A boolean indicating whether the form control has been modified by the user.
  • touched: A boolean indicating whether the form control has been touched by the user.
  • untouched: A boolean indicating whether the form control has not been touched by the user.
  • markAsTouched(): Marks the form control as touched.
  • markAsUntouched(): Marks the form control as untouched.
  • markAsDirty(): Marks the form control as dirty.
  • markAsPristine(): Marks the form control as pristine.
  • setValidators(): Sets the validators for the form control.
  • setAsyncValidators(): Sets the asynchronous validators for the form control.

FormControl extends AbstractControl and adds a few properties and methods that are specific to single form controls, such as:

  • setValue(): Sets the value of the form control.
  • patchValue(): Sets the value of the form control without emitting a value change event.
  • reset(): Resets the form control to its initial state.
  • disabled: A boolean indicating whether the form control is currently disabled.
  • enable(): Enables the form control.
  • disable(): Disables the form control.

FormGroup also extends AbstractControl, but it represents a group of related form controls. It has all the properties and methods of AbstractControl and adds a few methods that are specific to groups of form controls, such as:

  • addControl(): Adds a control to the group.
  • removeControl(): Removes a control from the group.
  • get(): Gets a control from the group using its name.
  • contains(): Checks whether a control exists in the group.

Form Builder

FormBuilder is a convenience service that provides a simplified API for creating and managing Angular reactive forms. It helps to reduce the amount of boilerplate code needed to create form controls and provides a consistent way to handle form inputs. FormBuilder is a higher-level abstraction over the FormControl and FormGroup classes.

Asynchronous Operations

  • Calling the server (AJAX)
  • Timer functions
static shouldBeUnique(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
    return new Promise(async resolve => {
        console.log("Validating uniqueness...");
        setTimeout(() => {
            ...
        }, 2000);
    });
}

To add a message when a async process is running you can add something like this:

Checking for ...

I stop in the Reactive Forms assignment

Consuming HTTP Services

To use the HTTP client, you first need to import the HttpClientModule from '@angular/common/http' in your application's root module. Then, you can inject the HttpClient service in your component or service and use its methods to make HTTP requests.

The HTTP client also supports interceptors, which allow you to intercept and modify HTTP requests and responses. Interceptors can be used to add headers, transform request or response data, and handle errors.

To handle errors, you can use the catchError operator from the 'rxjs/operators' library, which allows you to catch errors and return a custom error response.

In addition, Angular provides a HttpParams class for creating URL query parameters and a HttpHeaders class for creating HTTP headers. These classes can be used to configure HTTP requests with query parameters, headers, and other options.

Separation of Concerns

Component Layer: The component layer is responsible for displaying data to the user and handling user interactions. By separating the display logic from the HTTP request logic, you can make your code easier to understand and maintain. The component layer can call the service layer to retrieve data and then update the view accordingly.

Service Layer: The service layer is responsible for handling HTTP requests and responses. By encapsulating HTTP requests in a service, you can separate concerns and make your code more modular. The service layer can also handle error handling, headers, and other HTTP configurations.

Model Layer: The model layer is responsible for defining the data models used in the application. By separating data models from the HTTP request logic, you can make your code more reusable and easier to test. The model layer can define classes or interfaces to represent the data returned by HTTP requests.

Our classes should have a single responsibility. By separating concerns into different layers, you can create a more modular and maintainable codebase. The service layer can handle HTTP requests and responses, the component layer can display data to the user, and the model layer can define the data models used in the application.

And it will solve all the details about working with this backend is encapsulated in one place and we can reuse this in multiple places. One benefit is that if in the future some detail changes, for example the URL, we have to update only one place in our code. The second benefit is that when we want to unit test our input, we can create a fake implementation of this service that doesn't make HTTP calls to the server.

ng g s service_name

Handling Errors

Unexpected Expected
Server is offline "Not Found" errors (404)
Network is down "Bad request" errors (400)
Unhandled exceptions

How to handle unexpected errors: subscribe method accept a second optional parameter that it's possible set what should happen in case of unexpected error. Passing an object to the subscribe method instead of two functions that is deprecated. The object contains two properties: next and error, which are functions that handle the response and error cases, respectively. The error function now takes an err parameter and returns an _ObservableInput _(which can be void).

How to handle expected errors: inside the error it's possible set something specif for some error, like when you try delete something maybe you can receive something that don't exist (404).

Lifecycle Hooks

  • OnInit
  • OnChanges
  • DoCheck
  • AfterContentInit
  • ...

Observables vs Promisses

  • Observables allow reactive programming and provide a bunch of useful operators. In Observable are more lazy because don't do anything until you subscribe to them;
  • Promises are more eagers is deprecated;

Understanding Observables

Install RxJS

In order to follow along smoothly with the course examples, make sure you install RxJS v6 by running

npm install --save rxjs@6

In addition, also install the rxjs-compat package:

npm install --save rxjs-compat

Official Docs: https://rxjs-dev.firebaseapp.com/

RxJS Series: https://academind.com/learn/javascript/understanding-rxjs/

Updating to RxJS 6: https://academind.com/learn/javascript/rxjs-6-what-changed/

Routing and Navigation

Depth details in this readme

  • Configure routes
  • RouterLinker

And if you want pass a paramether with the route:

<a [routerLink]="['followers', follower,id]">

routerLinkActive

HTTP

Depth details in this readme

Other notes

  • What is the difference between DOM(Document Object Model) and HTML

DOM is a model of objects that represent a structure of a docment, it's essentially a tree of objects in memory. And HTML is a markup language that we use to represent DOM in text.

  • To make a component more reuseble add a bunch of input and output properties. Input to receive the state and the output to raise events from there custom components. This combination make up the component API (application programming interface).

  • Why we use Leading Asterisk (*) before the "ng"?

When Angular sees this leading asterisk with structural directives, it's going to rewrite that block using ng-template.

  • The #name="ng*" syntax creates a template reference variable that references all ng* directives on the input element. This can be useful if you need to access multiple directives on the same element, or if you want to create a more general reference to the element that can be used in multiple contexts. This includes directives such as ngModel, ngForm, ngIf, ngFor, and so on.

References:

Doubts:

  • How work the throw in Application-specific Errors with Service in Angular 13+? In Angular 4 is like: Service file
deletePost(id: any){
return this.http.delete(this.url + '/' + id)
    .catch((error: Response) => {
        if(error.status === 404)
            return Observable.throw(new NotFoundError());
        return Observable.throw(new AppError(error));
    });
}

and in the component file

deletePost(post) {
    this.service.deletePost(post.id)
    .subscribe(
        response => {
            let index = this.posts.indexOf(post);
        },
        (error: AppError) => {
            if (error instanceof NotFoundError)
                alert('This post has already been deleted.');
            else {
                alert('An unexpected error occurred.');
                console.log(error);
            }
        }
    )
}

How use a global Error handling in Angular13+.