-
Observables are a fundamental concept in Angular that provides support for passing messages between publishers and subscribers in an application.
-
They offer significant benefits over other techniques for event handling, asynchronous programming, and handling multiple values.
-
Observables are declarative, meaning that we define a function for publishing values, but it is not executed until a consumer subscribes to it.
-
Subscribed consumers receive notifications until the function completes or until they unsubscribe.
-
To use observables in Angular, follow these steps:
- Import the Observable type from 'rxjs':
import { Observable } from 'rxjs';
- Subscribe to the observable in your application to listen to the data it emits:
getCurrencies() { return this.http.get(backendUrl.quoteService.getCurrencies) as Observable<string[]> }
this.tradeService.getCurrencies().subscribe((response) => { this.currencies = response; })
Async pipe:
- The async pipe is a powerful feature in Angular for working with observables.
- It simplifies the process of binding observable data directly to the template.
@Component({
selector: 'async-observable-pipe',
template: '<div><code>observable|async</code>: Time: {{ time | async }}</div>'
})
export class AsyncObservablePipeComponent {
time = new Observable<string>((observer: Observer<string>) => {
setInterval(() => observer.next(new Date().toString()), 1000);
});
}
- Observables differ from Promises in several ways:
- Observables allow for emitting multiple values over time, while Promises handle a single response.
- Promises are eager and always return the first value, while Observables are lazy and execute only when someone subscribes.
- Observables are cancelable, allowing you to stop them when needed.
const observable = new Observable((observer) => {
let i=0;
setInterval(() => {
observer.next(i++);
},1000);
}
observable.subscribe(value => console.log(value));
// logs:
// 0
// 1
// 2
// and so on, every second
- Promises are eager, Observables are lazy : function passed to Observable constructor gets called only when someone actually subscribes to an Observable - not wasting resources
- Promises are not cancellable, Observables are cancelable, so we can stop whatever happens inside it
const subscription = observable.subscribe(value => console.log(value));
subscription.unsubscribe();
- Pull and push are two ways to describe how data consumers communicate with data producers.
- Pull is when the consumer decides when to get data from the producer.
- the time it takes the data to be delivered to the data consumer is unknown even for the data producer
- e.g.:
- Data producers: JavaScript Functions
- Data consumers: code that calls the previous functions
- Push, supported by Observables, is when the producer delivers data to registered callbacks.
- the common way to push in JS are promises
- we can consider the Promise as producer, but this time it delivers a value to registered callbacks
- Observables are the new way to push data in JS
-
Observables go through a lifecycle:
- Created by calling new Observable().
- Subscribed to by an observer.
- Executed by calling next() to send data.
- Disposed of by calling unsubscribe() when no longer needed.
Creating observable:
- just call new Observable() and pass one argument (which is the observer here)
Subscribing to observables:
- if you don't subscribe to observables, nothing will happen, because they are a lazy collection of data or multiple values over time
Executing observables:
- the execution of the observables is the code inside it
- there are 3 functions used to send data to observables:
- next: it sends any value such as Arrays, objects or Numbers to it's subscribers
- complete: it doesn't send any value
- error: it sends a JS exception or error
- the next calls are the most common because they deliver the data to subscribers
Disposing observables:
- because the time it takes to execute can be infinite amount of time, we need a way to stop it when we want to
- we will need to unsubscribe from the observable to do cleanup and release resources as it will be a waste of memory and computing power
- Angular forms are essential for creating user interfaces, blocking invalid data, guiding users, and avoiding unnecessary requests.
- There are two ways to create Angular forms: Template-driven and Reactive (model-driven), both using FormGroup and FormControl building blocks.
- Reactive forms are recommended for complex applications, as they provide a more robust, scalable, reusable, and testable solution.
-
In reactive forms, the form model provides the value and status of form elements.
-
Each form element in the view is directly linked to a FormControl instance in the model.
-
Updates from the view to the model and vice versa are synchronous and independent of UI rendering.
-
Validator functions are added directly to form control models in the component class.
-
e.g.:
const formGroup = new FormGroup({ 'phones': new FormArray([ new FormGroup({ 'number': new FormControl('', [ Validators.required ]), 'type': new FormControl('Primary') }), new FormGroup({ 'number': new FormControl(''), 'type': new FormControl('Secondary') }) ]) });
<form [formGroup]="formGroup" > <h1>User Phones</h1> <div class="phones" *ngFor="let phoneGroup of formGroup.get('phones')['controls']; let i = index" formArrayName="phones"> <ng-container [formGroupName]="i" > <p>Phone Type: {{ phoneGroup.get('type').value }}</p> <input type="tel" placeholder="Phone number" formControlName="number" /> </ng-container> </div> </form>
Note
We can also create our own Custom Validators.
-
Template-driven forms are easier to use but may not scale well for complex requirements.
-
The template manages the form model internally.
-
Validation is added through attributes in the template, and validation is performed as the value of a form control changes.
-
e.g.:
<input id="name" name="name" class="form-control" required minlength="4" [(ngModel="book.name")]> <div *ngIf="name.invalid && (name.dirty || name.touched)" class="alert alert-danger"> <div *ngIf="name.errors.required">Name is required.</div> <div *ngIf="name.errors.minlength">Name must be at least 4 characters long.</div> </div>
Note
We can also create our own Custom Validators.
Key | Reactive | Template-driven |
---|---|---|
Setup (form model) | More explicit, created in component class | Less explicit, created by directives |
Data model | Structured | Unstructured |
Predictability | Synchronous | Asynchronous |
Form validation | Functions | Directives |
Mutability | Immutable | Mutable |
Scalability | Low-level API access | Abstraction on top of APIs |