Reputation: 1951
I've seen tutorials showing umpteen different ways to implement observables in Angular. Many of them seem overly complicated for my purposes. Others are for previous versions and no longer work.
Let's say I have a service with a single property named numChickens
, and I want to allow components to subscribe to that property. Do.i.really((need)=> to.chain((a)=>{million.garbledyGook().statements.to('gether)}) to make that work?
Here's the code for the service in question:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ChickenService {
public chickens: number;
constructor() { }
}
...and here is the code for a component which will use the observable:
import { Component, OnInit } from '@angular/core';
import { ChickenService } from '../chicken.service';
@Component({
selector: 'app-chickendisplay',
templateUrl: './chickendisplay.component.html',
styleUrls: ['./chickendisplay.component.scss']
})
export class ChickenDisplayComponent implements OnInit {
constructor(public cs: ChickenService) {
}
ngOnInit() {
}
}
In Angular 6, what is the simplest, most straightforward, most readable way to expose the chickens
property in ChickenService such that a component class has access to the value of that property as an observable stream? Or so that a component template can display the value using an async pipe?
I can't stress that enough - please, no answers that include an 8,192-character wall of closures and then say "see, it's simple".
I ask this question not just for myself, but for others like me who are trying to wrap their heads around observables and struggling with all of the dense and outdated tutorials on the subject. If you can reduce this solution to a simple form, future generations will thank you.
Upvotes: 5
Views: 6076
Reputation:
You might consider using a decorator to turn a property into an observable. The details of doing this are not particularly fun, but once you've done it, you'll be able to write something like:
export class ChickenService {
@Observablize()
public chickens: number;
public chickens$: Observable<number>;
}
OK, so now take a deep breath. Here's approximately what the implementation of the decorator would look like (untested).
function Observablize() {
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
const observableKey = propertyKey + "$";
const privateKey = propertyKey + "_";
Object.defineProperties(target.prototype, {
[observableKey]: { value: new Subject<any>() },
[propertyKey]: {
get() { return this[privateKey]; },
set(v) { this[privateKey] = v; this[observableKey].next(v); }
}
});
};
}
Upvotes: 2
Reputation: 18271
The simplest way would be to create a private Subject
, and use that to create your public Observable
.
In the code below, I've created a get
and set
for your chickens
variable. This means that each time you update it using (for example) service.chickens = 10
, it will automatically trigger a new event on the Observable
stream with the new value.
import { Injectable } from '@angular/core';
import { Subject, Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ChickenService {
private _chickens: number; // Make this private so we can expose it via a get/set
private chickenChange$ = new Subject<number>(); // This will be used to create our Observable
public chickens$ = this.chickenChange$.asObservable(); // This is our Observable
constructor() { }
set chickens(val: number) {
this._chickens = val; // Set the new value
this.chickenChange$.next(val); // Trigger the subject, which triggers the Observable
}
get chickens() {
return this._chickens;
}
}
Upvotes: 6
Reputation: 1509
If you really want to expose it as a stream, all you have to be doing is add a single line:
import { Injectable } from '@angular/core';
import { of } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ChickenService {
public chickens: number;
public chickens$ = of(chickens);
constructor() { }
}
Note, doing so will still not allow you to update the value, so there not much added value into doing this.
You could use a Subject to make it possible to update the value and reflect those changes in the UI by subscribing the stream:
import { Injectable } from '@angular/core';
import { of } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ChickenService {
public chickens: number;
public chickens$ = chickensSub$.asObservable();
private chickensSub$ = new BehaviorSubject();
constructor() { }
updateChickens(value: number) {
this.chichensSub$.next(value);
}
}
Note: I try to avoid explicitly using Subjects when not necessary.
Upvotes: 4