How to set two Async Pipe functions in Angular 7? [duplicate] - javascript

This question already has answers here:
*ngIf with multiple async pipe variables
(3 answers)
Closed 3 years ago.
I have 2 functions that checks a specific condition, I would both like both of them to be true.
How would you use *ngIf in this case? Currently if I set one of them it works, but I need both of them.
HTML
<p *ngIf="(isFree$ | async) && (order$ | async)">Free Plan</p>
TS
public order(data) {
const order = data.externalOrderId;
if (order.substring(0, 4) === 'asdad') {
return true;
} else {
return false;
}
}
public isFree$ = this.planType$.pipe(
map((data) => {
return (data.plan === 'Free');
}
));

Create a new dumb component that has (2) Inputs: [isFree], [order]
In your new component use an *ngIf statement to show the <p> if both Inputs are available.

You can use rxjs forkJoin
It will wait until both the observables streams return the response. It returns single observable array of responses to which you can subscribe or use async pipe to resolve
HTML
<p *ngIf="resp$ | async">Free Plan</p>
TS
public isFree$ = this.planType$.pipe(
map((data) => {
return (data.plan === 'Free');
}
));
public isOrder$ = of({resp:[]}) // stream 2
public resp$ = forkJoin(this.isFree$, this.isOrder$)
Fork join example with ngIf and async

Related

Angular push service object data to array

I have created one service to load all the files data:
readonly file= new BehaviorSubject(null);
readonly file$ = this.pnlNumbers.asObservable();
getFile(filename: string) {
this.file.next(null);
this.subscriptions.push(this.http.get(`/file/${filename}).subscribe(data => {
this.file.next(data);
}, error => {
this.file.next(error);
}));
}
This will return an single object with file information ,eg:
{
id:0001,
name: 'test_file.txt',
...
}
I have created ab function to store all the result data that comes from the getFile service:
getAllFiles(): any {
let filesList= [];
this.activeFilesList.forEach(fileName => {
this.fileService.getFile(fileName);
});
this.fileService.file$.subscribe((data) => {
if (data) {
fileList?.push(data);
}
});
return filesList;
}
I don't know why , but "typeOf this.getAllFiles()" will return an Object instead of Array, for that reason I cant access the indices of filesList[], eg on chrome console:
[]
1:{id:0001,name:'test.file.text'}
2:{id:0002,name:'test.file2.text'}
3:{id:0003,name:'test.file3.text'}
the filesList.lenght = 0
I need that this filesList[] be a type of Array instead of an Object.
Few things here:
Firstly, a common JavaScript gotcha is the return type of an array is in fact 'object'
typeof [] // 'object'
typeof ['any array contents'] // 'object'
i.e. typeof is an ineffective heuristic for determining whether the return type of this function is an array. Use Array.isArray instead:
Array.isArray(['any array contents']) // true
Array.isArray({ someKey: 'someValue' }) // false
Secondly, am I safe to assume that this line
readonly file$ = this.pnlNumbers.asObservable();
Should instead be
readonly file$ = this.file.asObservable();
otherwise the rest of the code does not really have much relevance, as this.pnlNumbers is not referenced anywhere else
Thirdly, it appears that you are trying to combine the results of multiple asynchronous streams (in this case http calls) into a single array to be returned from getAllFiles(). As these are asynchronous, they by nature take some time to return their data. While this data is being returned, the rest of your synchronous function code will run, and that means your return statement will be hit before the http calls have returned their data.
In its current state, getAllFiles() is simply returning the value of filesList before any of the http calls have returned their values, i.e. the default value it was assigned, [].
What you will need to do instead is to
Use an RxJs operator to combine those independent http streams into one stream
Subscribe to this combined stream and handle the combination of values as is appropriate for the operator being used
An example implementation using forkJoin is here, but depending on your use case, other joining operators like concat, mergeMap, combineLatest etc may be preferable:
type FileType = { id: number; name: string }
getAllFiles$(): Observable<FileType[]> {
const filesListObservables: Observable<FileType>[] =
this.activeFilesList
.map(
(fileName: string) => this.fileService.getFile(fileName)
);
const filesList$: Observable<FileType[]> = forkJoin(filesListObservables);
return filesList$;
}
getAllFiles(): void {
this.getAllFiles$()
.subscribe(
(allFiles: FileType[]) => {
console.log(allFiles) // [{id:0001,name:'test.file.text'},...]
}
)
}

Why Rxjs combineLatest depends on both subscription and not call when one of them changed?

I have two subscriptions item as you see in the image:
one of them is search input another one is a selection filter.
I want to change the result when one or both of them change.
I use Rxjs combineLatest so when both of them or search input change, everything is ok, but when changing the Partner type at the first, the response does not change.
ngOnInit() {
super.ngOnInit();
this.getAllPartners();
combineLatest([this.searchPartnerFc.valueChanges, this.filterFc.valueChanges])
.pipe(
switchMap(([search, filter]) => {
let partnersBySearch = this.getSearchResult(search);
let partnersByFilter = this.getFilterResult(filter);
this.partners = partnersBySearch.filter(item => partnersByFilter.indexOf(item) > 0);
return this.partners;
}),
)
.subscribe();
}
getFilterResult(filterKey: string) {
if (filterKey == 'All') return this.allPartners;
else return this.allPartners.filter(partner => partner.partnerType == filterKey);
}
getSearchResult(searchString: string) {
return this.allPartners.filter(x => x.partnerName.toLowerCase().includes(searchString));
}
You can achieve your desired result by providing a default emission for each of your source observables using startWith:
combineLatest([
this.searchPartnerFc.valueChanges.pipe(startWith('')),
this.filterFc.valueChanges.pipe(startWith('All'))
]).pipe(
...
);
I think it's not working because of the nature of the combineLatest operator. It will only emit when both of the observables have emitted an initial value first. So when only the first observable emits, and the second doesn't, it will not do anything:
https://www.learnrxjs.io/learn-rxjs/operators/combination/combinelatest
I think you need a merge operator here:
https://www.learnrxjs.io/learn-rxjs/operators/combination/merge
Also please take a look here on this thread:
How to 'wait' for two observables in RxJS

angular autocomplete suggestion list

I have an autocomplete function like this
chooseArtist: OperatorFunction<string, readonly string[]> = (text$: Observable<string>) =>
text$.pipe(
debounceTime(200),
distinctUntilChanged(),
map((term: any) => term.length < 2 ? []
: this.artistlookuplist.filter((v: any) => v.name.toLowerCase().indexOf(term.toLowerCase()) > -1).slice(0, 10))
)
And I have a service that populates the artistlookuplist like this:
getArtists(): void {
this.artistService.getSearchArtist(this.searchstring).subscribe((data: any[]) => {
this.artistlookuplist = data;
});
I would like to combine these two. So that the list of autocomplete suggestion is only fetched when the chooseArtist function is called from the autocomplete field.
Any ideas how?
I understand that you want to combine the two observables. When you need combine two observables, one depending from another you use switchMap rxjs operator
import {of} from 'rxjs
import {debounceTime,distictUntilChahne,switchMap} from 'rxjs/operators'
text$.pipe(
debounceTime(200),
distinctUntilChanged(),
switchMap((term:any)=>{
return term.length<2? of([]):
this.artistService.getSearchArtist(term)
})
)
See that you have a first observable, the text$, and based in the value of "term" you return an observable (if term.length<2 use the of rxjs operator to return an objervable of an empty array, else the observable that return the service.
You can see switchMap as some like map. When we use map, we transform the response (but return an array or a string or...), when we use switchMap we "transform" the response return a new observable.

Unit testing rxjs in Angular [duplicate]

This question already has answers here:
Angular unit test combineLatest
(2 answers)
Closed last year.
Im having a problem writing unit tests for observable in Angular... Im trying to test cases if displayPaymentsLineItem$ will be true or false depending on the values of
mobileAccountBalance$, and selectedMobileDigitalBill$... Can anyone help?
public selectedMobileDigitalBill$: Observable<MobileDigitalBill>;
public mobileAccountBalance$: Observable<MobileAccountBalance>;
private expandedLinesMap: object = {};
private expandedTaxesAndFees: boolean = false;
private devices: MobileDevice[] = [];
private destroy$: Subject<void> = new Subject();
constructor(]
private mobileStatementsTabFacade: MobileStatementsTabFacade,
private billingMobileFacade: BillingMobileFacade,
) {}
ngOnInit() {
this.mobileAccountBalance$ = this.mobileStatementsTabFacade.mobileAccountBalance$;
this.displayPaymentsLineItem$ = combineLatest([
this.mobileAccountBalance$,
this.selectedMobileDigitalBill$,
]).pipe(
map(([mobileAccountBalance, selectedMobileDigitalBill]: [MobileAccountBalance, MobileDigitalBill]) => {
const isPastDue: boolean = mobileAccountBalance?.pastdue > 0;
const hasPayments: boolean = selectedMobileDigitalBill?.payments?.length > 0;
return isPastDue && hasPayments;
})
);
}
});
You can take(1) (take one value, then complete) and subscribe to test if the emitted value is falsy. Observables need to be completed if you test them this way.
describe('The display payment line items observable', () => {
it('should emit truthy', () => {
displayPaymentsLineItem$
.pipe(take(1))
.subscribe(value =>{
expect(value).toBeTruthy();
});
}
}
That being said, displayPaymentsLineItem$ won't emit anything if the two observables inside combineLatest() aren't defined in your test. Since they come from two facades, they may need to be provided before starting your test.
Also, about your code example:
displayPaymentsLineItem$ isn't declared before the constructor.
selectedMobileDigitalBill$ is declared but is never defined before it is referenced inside combineLatest().

Arrays in angular 5 project is undefined but it is not [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 4 years ago.
I have component in my angular 5 project, bill-page. On bill-page I get some info, bill and currency. It is two arrays, bill (array with one object, get it from my firebase) and currency (array with two objects, get it from free api). When I wanna chek this arrays by using
console.log()
it shows me that is two normal arrays with some objects.
export class BillPageComponent implements OnInit {
currency: any = [];
bill: any = [];
userId: string;
isLoaded = false;
copyCurrency: any = [];
constructor(private billService: BillService, private authService: AuthService) {}
ngOnInit() {
this.isLoaded = false;
this.userId = this.authService.returnAuthState();
this.billService.getBillFirebase(this.userId).subscribe(
(resp)=>{
for (let key in resp) {this.bill.push(resp[key])}
}
);
console.log(this.bill);//[{currency: "USD", value: 0}]
this.billService.getCurrencyPB().subscribe((data)=>{
this.currency.push(data[0]);
this.currency.push(data[1]);
});
console.log(this.currency); //[{key1:1, key2:2},{key3:3, key4:4}]
this.isLoaded = true;
}
But when I try get this objects from arrays, or property from objects it becomes undefined, for example
console.log(this.bill[0])// undefined or console.log(this.bill[0].value)//undefined
or if I try copy this arrays it becomes undefined too
this.copyCurrency = this.currency.slice();
console.log(this.copyCurrency) // undefined
Basically what happened was that it executed aysnchronously. console.log (this.bill) executed before it got any value. this.bill gets the value only inside the subscribe function.

Categories