Angular2: how to initially trigger Control.valueChanges - javascript

constructor(private _service: LocatorService) {
this.counties = this.countyTerm.valueChanges
.debounceTime(300)
.distinctUntilChanged()
.switchMap((term: string) => _service.getCounties(term));
}
counties: Observable<County[]>;
countyTerm = new Control();
As expected, this.counties is only populated once a value is entered into the countyTerm bound control.
How can I trigger valueChanges when this component is instantiated so that the set of counties is loaded initially?
I tried the following, but it had no effect (implements OnInit was added to the class):
ngOnInit() {
this.countyTerm.updateValue('', { emitEvent: true });
}

Just start your stream out with a fixed value. Something like this:
this.counties = Rx.Observable.of('')
.concat(this.countyTerm.valueChanges.debounceTime(300))
.distinctUntilChanged()
.switchMap((term: string) => _service.getCounties(term));

Use startWith RxJS operator to emit something before stream.
this.counties = this.countyTerm.valueChanges
.startwith('')
.debounceTime(300)
.distinctUntilChanged()
.switchMap((term: string) => _service.getCounties(term));
http://reactivex.io/documentation/operators/startwith.html

With RxJS 6 / 7 new syntax:
this.counties$ = this.countyTerm.valueChanges.pipe(
startWith(''),
debounceTime(300),
distinctUntilChanged(),
switchMap((term: string) => _service.getCounties(term))
);

Related

Listen for value changes in an object property, slice and subscribe to the changed value

I use the Async local-storage with angular to store data in the indexedDB.
The library has a method to listen to / watch for changes.
This method returns an Observable with the complete object.
What I want is to slice the changed property value, object, or the array of the object and subscribe to it.
My example object would be:
{
fetched: boolean,
loaded: boolean,
item: null, // object
list: [], // object arry
}
Now I need to watch for the changes in each property as an Observable.
fetched$.subscribe((fetched: boolean)) => {}
loaded$.subscribe((loaded: boolean)) => {}
item$.subscribe((item: any)) => {}
list$.subscribe((list: any[])) => {}
Here is the code I used to slice the changed value so far.
// slice an individual value
function sliceValue<T>(
repo: BaseRepository<T>,
key: string
): Observable<any> {
if (!repo.key || !repo.store.has(repo.key)) {
return of (null);
}
return repo.store.watch(repo.key).pipe(
distinctUntilChanged((prev, curr) => {
if (!prev || !curr) return false;
return prev[KEY.D][key] === curr[KEY.D][key];
}),
switchMap((ctx: any) => repo.store.get(repo.key)),
map((ctx: any) => ctx[KEY.D][key]));
}
// State class
export class TestState extends BaseRepository<TestModel> {
public fetched$: Observable<boolean> = sliceValue(this, 'fetched');
public loaded$: Observable<boolean> = sliceValue(this, 'loaded');
public item$: Observable<any> = sliceObject(this, 'item');
public list$: Observable<any[]> = sliceList(this, 'list');
constructor(
public readonly store: StorageMap,
) {
super(store);
}
}
But I do not know the efficient way to do it.
If there is any other best way to do it, I would like to know and try it out.
Thank you very much.

How to invoke function that return an Observable within a recursive function?

I have the following function that traverse the tree-like object and it working fine so far.
const traverse = async (menuInputs: MenuInput[], parent: Menu = null) => {
for (const input of menuInputs) {
const entity = toEntity(input, parent);
const parentMenu = await this.menuService.create(entity).toPromise();
if (isEmpty(input.children)) {
continue;
}
await traverse(input.children, parentMenu);
}
};
My question is how can i invoke method this.menuService.create(entity) that is actually return an Observable<Menu> without convert it to Promise, are there any RxJS way of doing this ?
I've got something like this for recursive observable calls using the HttpService. You can probably work with something similar.
#Injectable()
export class CharacterReaderApiService implements CharacterReader {
private characters: SwapiCharacter[] = [];
constructor(private readonly http: HttpService) {}
async getCharacters(): Promise<Character[]> {
console.log('Querying the API for character data...');
return this.callUrl('https://swapi.dev/api/people')
.pipe(
map((data) => {
return data.map(this.mapPerson);
}),
tap(async (data) =>
promises.writeFile(join(process.cwd(), characterFile), JSON.stringify(data)),
),
)
.toPromise();
}
private callUrl(url: string): Observable<SwapiCharacter[]> {
return this.http.get<SwapiResponse>(url).pipe(
map((resp) => resp.data),
mergeMap((data) => {
this.characters.push(...data.results);
return iif(() => data.next !== null, this.callUrl(data.next), of(this.characters));
}),
);
}
private mapPerson(data: SwapiCharacter): Character {
return {
name: data.name,
hairColor: data.hair_color,
eyeColor: data.eye_color,
gender: data.gender,
height: data.height,
};
}
}
The important thing is keeping a running array of the values so that they can be referenced later on.
By using mergeMap with iif we're able to recursively call observables and work with the result as necessary. If you don't like the idea of a class variable for it, you could make the running array a part of the callUrl (or similar) method's parameters and pass it on as needed.
I'd strive to change toEntity() and menuService.create() to receive the list of children rather than the parent so I could use forkJoin:
const buildMenu = (input: MenuInput) =>
forkJoin(
input.children.map(childInput => buildMenu(childInput, menu))
).pipe(
flatMap(childMenus => this.menuService.create(toEntity(input, childMenus)))
);

How is it possible to stop a debounced Rxjs Observable?

I created an observable, which will fire 3 seconds after the last change is made, and calls the publishChange of the service. It works, but I would like to create a doImmediateChange function, which calls publishChange immediately and stops the debounced observable. How is that possible?
My component:
class MyComponent {
private updateSubject = new Subject<string>();
ngOnInit() {
this.updateSubject.pipe(
debounceTime(3000),
distinctUntilChanged()
).subscribe(val => {
this.srv.publishChange(val);
});
}
doChange(val: string) {
this.updateSubject.next(val);
}
doImmediateChange(val: string) {
// Stop the current updateSubject if debounce is in progress and call publish immediately
// ??
this.srv.publishChange(val);
}
}
You can emulate debounceTime using switchMap and delay. Then cancel the inner Observable with takeUntil to prevent a waiting value from being emitted.
private updateSubject = new Subject<string>();
private interrupt = new Subject();
ngOnInit() {
this.updateSubject.pipe(
switchMap(val => of(val).pipe(
delay(3000),
takeUntil(this.interrupt)
))
).subscribe(val => publish(val));
}
doChange(val: string) {
this.updateSubject.next(val);
}
doImmediateChange(val: string) {
this.interrupt.next();
publish(val);
}
https://stackblitz.com/edit/rxjs-ya93fb
Use the race operator:
The first observable to complete becomes the only observable subscribed to, so this recursive function will complete after one emission take(1), then resubscribe () => this.raceRecursive().
private timed$ = new Subject<string>();
private event$ = new Subject<string>();
ngOnInit() {
this.raceRecursive()
}
raceRecursive() {
race(
this.timed$.pipe(debounceTime(1000)),
this.event$
)
.pipe(take(1)) // force it to complete
.subscribe(
val => console.log(val), // srv call here
err => console.error(err),
() => this.raceRecursive() // reset it once complete
)
}
doChange(val: string) {
this.timed$.next(val)
}
doImmediateChange(val: string) {
this.event$.next(val)
}
You can achieve this behavior using debounce and race:
with the code you provided
private destroy$ = new Subject<void>();
private immediate$ = new Subject<void>();
private updateSubject$ = new Subject<string>();
constructor(private srv: PubSubService) {}
ngOnInit() {
this.updateSubject$.pipe(
takeUntil(this.destroy$),
debounce(() => race(timer(3000), this.immediate$))
).subscribe(val => {
this.srv.publishChange(val);
});
}
doChange(val: string, immediate?: boolean) {
this.updateSubject$.next(val);
if (immediate) this.immediate$.next();
}
// don't forget to unsubscribe
ngOnDestroy() {
this.destroy$.next();
}
emitting an immediate change will replace the previous normal change (that is debounced for 3s) without the delay (thanks to our race observable).
here's a working example
You could supply a value specific debounce time with every value and use debounce with timer to change the debounce time for values dynamically.
private updateSubject = new Subject<{ value: any, debounceTime: number}>();
ngOnInit() {
updateSubject.pipe(
debounce(({ debounceTime }) => timer(debounceTime)),
pluck('value')
).subscribe(val => publish(val));
}
doChange(value: string) {
updateSubject.next({ value, debounceTime: 3000 });
}
doImmediateChange(value: string) {
updateSubject.next({ value, debounceTime: 0 });
}
This doesn't directly stop the debounced Observable but let's you "overwrite" a waiting value with a new one being emitted with zero delay.
https://stackblitz.com/edit/rxjs-j15zyq
(user733421 didn't seem to want to add a complete solution so I expanded the approach)
The value for debounceTime is only evaluated once, on observable creation time.
To be able to dynamically update debounceTime, use debounce together with timer, like this :
debounce(()=>timer(this.debounceTime)),

Rxjs from() operator doesn't send data

I have a simple app on Angular/rxjs/Ngrx which requests list of default films from the api.
component.ts
export class MoviesComponent implements OnInit {
private movies$: Observable<{}> =
this.store.select(fromRoot.getMoviesState);
private films = [];
constructor(public store: Store<fromRoot.State>) {}
ngOnInit() {
this.store.dispatch(new MoviesApi.RequestMovies());
this.movies$.subscribe(film => this.films.push(film));
console.log(this.films)
}
effects.ts
#Effect()
requestMovies$: Observable<MoviesApi.MoviesApiAction> = this.actions$
.pipe(
ofType(MoviesApi.REQUEST_MOVIES),
switchMap(actions => this.MoviesApiServ.getDefaultMoviesList()
.pipe(
mergeMap(movies => of(new MoviesApi.RecieveMovies(movies))),
catchError(err => {
console.log('err', err);
return of(new MoviesApi.RequestFailed(err));
})
)
)
);
service.ts
export class MoviesApiService {
private moviesList = [];
constructor(private http: HttpClient) { }
public getDefaultMoviesList(): Observable<{}> {
DEFAULT_MOVIES.map(movie => this.getMovieByTitle(movie).subscribe(item => this.moviesList.push(item)));
return from(this.moviesList);
}
public getMovieByTitle(movieTitle: string): Observable<{}> {
return this.http.get<{}>(this.buildRequestUrl(movieTitle))
.pipe(retry(3),
catchError(this.handleError)
);
}
}
DEFAULT_MOVIES is just array with titles.
So my getDefaultMoviesList method is not sending data. But if I replace this.moviesList to hardcoced array of values it works as expected.
What I'm doing wrong?
UPD
I wanted to loop over the default list of films, then call for each film getMovieByTitle and collect them in array and send as Observable. Is there any better solution?
1) You should probably move this line to the service contructor, otherwise you will push a second array of default movies every time you getDefaultMoviesList:
DEFAULT_MOVIES.map(movie => this.getMovieByTitle(movie).subscribe(item => this.moviesList.push(item)));
2) Actually you should probably merge the output of each http.get:
public getDefaultMoviesList(): Observable<{}> {
return merge(DEFAULT_MOVIES.map(movie => this.http.get<{}>(this.buildRequestUrl(movieTitle))
.pipe(retry(3),
catchError(this.handleError)
)))
}
3) You should actually only do that once and store it in BehaviorSubject not to make new HTTP request on each getDefaultMoviesList
private movies$: BehaviorSubject<any> = new BehaviorSubject<any>();
public getMovies$() {
return this.movies$.mergeMap(movies => {
if (movies) return of(movies);
return merge(DEFAULT_MOVIES.map(movie => this.http.get<{}>(this.buildRequestUrl(movieTitle))
.pipe(retry(3),
catchError(this.handleError)
)))
})
}
4) Your implementation shouldn't work at all since:
public getDefaultMoviesList(): Observable<{}> {
DEFAULT_MOVIES.map(movie => this.getMovieByTitle(movie).subscribe(item =>
this.moviesList.push(item))); // This line will happen after http get completes
return from(this.moviesList); // This line will happen BEFORE the line above
}
So you will always return an Observable of empty array.
5) You shouldn't use map if you don't want to map your array to another one. You should use forEach instead.
map is used like this:
const mappedArray = toMapArray.map(element => someFunction(element));
You can try creating the observable using of operator.
Ex: of(this.moviesList);
One intersting fact to note is that Observable.of([]) will be an empty array when you subscribe to it. Where as when you subscribe to Observable.from([]) you wont get any value.
Observable.of, is a static method on Observable. It creates an Observable for you, that emits value(s) that you specify as argument(s) immediately one after the other, and then emits a complete notification.

Angular6 polling not returning data

I have an Angular application where I am trying to check an external data service for changes every few seconds, and update the view.
I've tried to implement Polling from rxjs but I'm not able to access the object, conversely it seems the polling function isn't working but assume this is because the returned object is inaccessible.
app.component.ts
export class AppComponent {
polledItems: Observable<Item>;
items : Item[] = [];
title = 'site';
landing = true;
tap = false;
url:string;
Math: any;
getScreen(randomCode) {
const items$: Observable<any> = this.dataService.get_screen(randomCode)
.pipe(tap( () => {
this.landing = false;
this.Math = Math
}
));
const polledItems$ = timer(0, 1000)
.pipe(( () => items$))
console.log(this.polledItems);
console.log(items$);
}
excerpt from app.component.html
<h3 class="beer-name text-white">{{(polledItems$ | async).item_name}}</h3>
excerpt from data.service.ts
get_screen(randomCode) {
return this.httpClient.get(this.apiUrl + '/tap/' + randomCode)
}
assuming that you want an array of items you could go with something like this.
// dont subscribe here but use the
// observable directly or with async pipe
private readonly items$: Observable<Item[]> = this.dataService.get_screen(randomCode)
// do your side effects in rxjs tap()
// better move this to your polledItems$
// observable after the switchMap
.pipe(
tap( () => { return {this.landing = false; this.Math = Math}; })
);
// get new items periodicly
public readonly polledItems$ = timer(0, 1000)
.pipe(
concatMap( () => items$),
tap( items => console.log(items))
)
the template:
// pipe your observable through async and THEN access the member
<ng-container *ngFor="let polledItem of (polledItems$ | async)>
<h3 class="item-name text-white">{{polledItem.item_name}}</h3>
</ng-container>
take a look at: https://blog.strongbrew.io/rxjs-polling/
if you are not awaiting an array but a single than you dont need the ngFor but access your item_name like:
<h3 class="item-name text-white">{{(polledItems$ | async).item_name}}</h3>

Categories