How to test concatenate streams in rxjs - testing

The given class has a method which returns a cached stream but that stream can be triggered by another private hot stream which makes the cached stream emits a new value.
The class
export class SomeClass {
private cache: Observable<number>;
private trigger$ = new Subject();
private multiply = 1;
constructor(private num: number) {}
getNumber(): Observable<number> {
return (
this.cache ||
(this.cache = concat(of(void 0), this.trigger$).pipe(
switchMap(() => of(this.num * this.multiply++)),
shareReplay(1)
))
);
}
trigger(): void {
this.trigger$.next();
}
}
Example: https://stackblitz.com/edit/rxjs-gpyc46?file=index.ts
What is the way to test it?
This try is failed
it("trigger updates", () => {
testScheduler.run(({ expectObservable }) => {
const num$ = someClass.getNumber();
expectObservable(num$).toBe("a", { a: 3 });
someClass.trigger();
expectObservable(num$).toBe("a", { a: 6 });
someClass.trigger();
expectObservable(num$).toBe("a", { a: 9 });
});
});
Example: https://stackblitz.com/edit/rxjs-test-tricky-flow?file=src%2Fsome-class.spec.ts
UPD: so the problem here that seems it is not possible to mock the trigger$ property.
It would look like this
it("trigger updates", () => {
testScheduler.run(({ hot, expectObservable }) => {
spyOnProperty(someClass, 'trigger$', 'get').and.returnValue(hot('^--b--c'));
const num$ = someClass.getNumber();
expectObservable(num$).toBe("a--b--c", { a: 3, b: 6, c: 9 });
});
});
But the trigger$ property must be changed this way
get trigger$() {
return new Subject();
}
Example: https://stackblitz.com/edit/rxjs-test-tricky-flow-x2arxf?file=src%2Fsome-class.ts

Related

react native instance class not running in js side

I have this class:
export default class CallManager{
static instance = null
calls = []
static getInstance() {
if (CallManager.instance == null) {
CallManager.instance = new CallManager()
}
return this.instance;
}
addCall(callUUID, data){
this.calls.push({
callId : callUUID,
data: data
})
}
removeCall(callUUID){
this.calls = this.calls.filter(c => c.callId != callUUID)
}
getAllCall(){
return this.calls
}
}
When ios app killed + get incoming call (with RNCallKeep), I'm using this class to store new call like this:
RNCallKeep.addEventListener('didDisplayIncomingCall', ({ error, callUUID, handle, localizedCallerName, hasVideo, fromPushKit, payload }) => {
// you might want to do following things when receiving this event:
// - Start playing ringback if it is an outgoing call
console.log('didDisplayIncomingCall', error, callUUID, handle, localizedCallerName, hasVideo, fromPushKit, payload)
try {
CallManager.getInstance().addCall(callUUID, { ...payload})
} catch (error) {
console.log('didDisplayIncomingCall error', error)
RNCallKeep.endCall(callUUID)
}
})
const answerCall = ({callUUID}) => {
console.log(`[answerCall] ${callUUID}`)
RNCallKeep.answerIncomingCall(callUUID)
const callData = CallManager.getInstance().getAllCall().find(c => c.callId.toString().toUpperCase() === callUUID.toString().toUpperCase())
....
}
But after debug, i got getAllCall return empty. Can someone help?

geographicToWebMercator and Projected polygon appears only after click on zoom buttons angular 10

I am using geographicToWebMercator to draw a specific area on my map. The Polygon should draw as soon as the map loads. But the polygon is appearing only after click on the zoom buttons. either zoom in or zoom out. The code was working fine in arcgis 4.16 I have the upgraded version 4.16 to ArcGIS version 4.18.
Please find the code below,
import {
Component,
OnInit,
ViewChild,
ElementRef,
Input,
Output,
EventEmitter,
OnDestroy,
NgZone
} from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { GisService } from '../search/gis.service';
import { ActivatedRoute, Router } from '#angular/router';
import { map } from 'rxjs/operators';
import { loadScript, loadModules } from 'esri-loader';
import esri = __esri; // Esri TypeScript Types
import { empty } from 'rxjs';
import { AppConfig } from '../config/app.config';
#Component({
selector: 'app-esri-map',
templateUrl: './esri-map.component.html',
styleUrls: ['./esri-map.component.css'],
})
export class EsriMapComponent implements OnInit, OnDestroy {
#Output() mapLoadedEvent = new EventEmitter<boolean>();
#ViewChild('mapViewNode', { static: true }) private mapViewEl: ElementRef;
view: any;
dynamicRings: any;
wkid: any;
addr: any;
loadingmap = true;
// to keep loaded esri modules
esriModules = {
graphic: null,
geometry: {
Polygon: null,
SpatialReference: null,
support: { webMercatorUtils: null },
},
tasks: {
GeometryService: null,
support: { ProjectParameters: null },
},
};
private _zoom = 20;
private _center: Array<number> = [-95.937187, 41.258652];
private _basemap = 'gray-vector';
private _loaded = false;
private _view: esri.MapView = null;
private _nextBasemap = 'streets';
public _selectedLayer: Array<string>;
public layersMapIdxArray: string[] = ['0', '1', '2'];
public mapalllayerview: boolean;
public layersDic = {};
private readonly esriMapUri: string;
private readonly gisGeometryServer: string;
get mapLoaded(): boolean {
return this._loaded;
}
#Input()
set zoom(zoom: number) {
this._zoom = zoom;
}
get zoom(): number {
return this._zoom;
}
#Input()
set center(center: Array<number>) {
this._center = center;
}
get center(): Array<number> {
return this._center;
}
#Input()
set basemap(basemap: string) {
this._basemap = basemap;
}
get basemap(): string {
return this._basemap;
}
#Input()
set nextBasemap(nextBasemap: string) {
this._nextBasemap = nextBasemap;
}
get nextBasemap(): string {
return this._nextBasemap;
}
public onLayerChange(val: Array<string>) {
// hide all the layers before showing the selected layers
for (const al of this.layersMapIdxArray) {
this.layersDic[al].visible = false;
}
// layersDic is the feature layers added to the map
for (const v of val) {
this.layersDic[v].visible = true;
}
}
constructor(
private gisService: GisService,
private http: HttpClient,
private route: ActivatedRoute,
private router: Router,
private zone: NgZone,
private appConfig: AppConfig) {
this.esriMapUri = this.appConfig.getGisMapURL('');
this.gisGeometryServer = this.appConfig.gisGeometryServer('');
}
async initializeMap() {
try {
loadScript();
// Load the modules for the ArcGIS API for JavaScript
const [
EsriMap,
EsriMapView,
Polygon,
SpatialReference,
webMercatorUtils,
GeometryService,
ProjectParameters,
FeatureLayer,
BasemapToggle,
BasemapGallery,
Graphic,
] = await loadModules([
'esri/Map',
'esri/views/MapView',
'esri/geometry/Polygon',
'esri/geometry/SpatialReference',
'esri/geometry/support/webMercatorUtils',
'esri/tasks/GeometryService',
'esri/tasks/support/ProjectParameters',
'esri/layers/FeatureLayer',
'esri/widgets/BasemapToggle',
'esri/widgets/BasemapGallery',
'esri/Graphic',
]);
// save the modules on a property for later
this.esriModules.geometry.Polygon = Polygon;
this.esriModules.geometry.SpatialReference = SpatialReference;
this.esriModules.geometry.support.webMercatorUtils = webMercatorUtils;
this.esriModules.tasks.GeometryService = GeometryService;
this.esriModules.tasks.support.ProjectParameters = ProjectParameters;
this.esriModules.graphic = Graphic;
// Configure the Map
const mapProperties: esri.MapProperties = {
basemap: this._basemap,
};
const map: esri.Map = new EsriMap(mapProperties);
// Initialize the MapView
const mapViewProperties: esri.MapViewProperties = {
container: this.mapViewEl.nativeElement,
// center: this._center,
zoom: this._zoom,
map: map,
};
this._view = new EsriMapView(mapViewProperties);
// Add layers to the map according to the selection
for (const idx of this.layersMapIdxArray) {
this.layersDic[idx] = new FeatureLayer({
url: `${this.esriMapUri}/${idx}`,
visible: this.mapalllayerview,
outFields: ['*'],
});
map.add(this.layersDic[idx]);
}
// The layer 15 will be the stack at the top of the layers so 15 will be consider first layer
this.layersDic[15] = new FeatureLayer({
url: `${this.esriMapUri}/15`,
visible: true,
outFields: ['*'],
});
map.add(this.layersDic[15]);
// Basemap toglle section
var basemapToggle = new BasemapToggle({
view: this._view,
nextBasemap: this._nextBasemap,
});
this._view.ui.add(basemapToggle, 'bottom-right');
// Load details of SAID when click on the map
let hitself = this;
this._view.on('click', function (event) {
hitself._view
.hitTest(event, { exclude: hitself._view.graphics })
.then(function (response) {
console.log(response);
if (
typeof response.results !== 'undefined' &&
response.results.length > 0
) {
var graphic = response.results[0].graphic;
var attributes = graphic.attributes;
var category = attributes.ADDRESS;
// redirect to the corresponding SAID
// window.location.href = `/dashboard/${category}`;
// hitself.router.navigate(['dashboard', category]);
hitself.addr = category;
var dashurl = 'search/detail/' + hitself.addr;
hitself.zone.run(() => {
hitself.router.navigateByUrl(dashurl);
});
}
});
return;
});
await this._view.when(); // wait for map to load
return this._view;
} catch (error) {
console.error('EsriLoader: ', error);
}
}
// point geometry extent is null
zoomToGeometry(geom) {
// console.log(`Original Geometry: ${JSON.stringify(geom.toJSON())}`);
const geomSer = new this.esriModules.tasks.GeometryService(this.gisGeometryServer);
const outSpatialReference = new this.esriModules.geometry.SpatialReference({
wkid: 102100,
});
const params = new this.esriModules.tasks.support.ProjectParameters({
geometries: [geom],
outSpatialReference,
});
const self = this;
geomSer
.project(params)
.then(function (result) {
const projectedGeom = result[0];
if (!projectedGeom) {
return;
}
// console.log(
// `Projected Geometry: ${JSON.stringify(projectedGeom.toJSON())}`
// );
return projectedGeom;
})
.then(function (polly) {
// console.log(self.esriModules.graphic);
self._view.graphics.add(
new self.esriModules.graphic({
geometry: polly,
symbol: {
type: 'simple-fill',
style: 'solid',
color: [255, 0, 0, 0.1],
outline: {
width: 1,
color: [255, 0, 0, 1],
},
},
})
);
self._view.extent = polly.extent.clone().expand(3);
});
}
ngOnInit() {
this.route.paramMap.subscribe((params) => {
this.addr = params.get('address');
console.log(this.addr);
this.gisService.getAddressDetails(this.addr).subscribe((posts) => {
const get_wkid = posts[0]['spatialReference'];
this.wkid = get_wkid['wkid'];
const dynamicrings = posts[0]['features'];
this.dynamicRings = dynamicrings[0]['geometry']['rings'];
});
this._selectedLayer = ['1', '0', '2'];
// this.layersMapIdxArray = this._selectedLayer;
this.mapalllayerview = true;
this.initializeMap().then(() => {
// The map has been initialized
console.log('mapView ready: ', this._view.ready);
const geom = new this.esriModules.geometry.Polygon({
spatialReference: {
wkid: this.wkid, //102704,
},
rings: this.dynamicRings,
});
this.zoomToGeometry(geom);
console.log('mapView ready: ', this._view.ready);
this._loaded = this._view.ready;
this.mapLoadedEvent.emit(true);
this.loadingmap = false;
});
});
}
ngOnDestroy() {
if (this._view) {
// destroy the map view
this._view.container = null;
}
}
}

How to wait until all async calls are finished

I've got NestJS application which interact with YoutubeAPI and load videos from it.
One particular method is important and it's loadVideos from below. Method it self has multiple asyncs inside and I need to work with videoIdMap property once everything is finished
private loadVideos(
playListId: string,
channel: Channel,
nextPageToken: string,
stopLoadingOnVideoId: string,
) {
const baseUrl = YoutubeService.VIDEO_URL_SNIPPET_BY_ID + playListId;
const response = this.httpService
.get(nextPageToken ? baseUrl + '&pageToken=' + nextPageToken : baseUrl)
.pipe(map((response) => response.data));
response.subscribe((data) => {
data.items.forEach((item) => {
if (stopLoadingOnVideoId && item.snippet.resourceId.videoId === stopLoadingOnVideoId) {
return;
}
this.prepareVideoEntity(item.snippet, channel).then((partialVideo) =>
this.videoService.create(partialVideo).then((video) => {
this.videoIdMap[video.youtubeId] = video.id;
}),
);
});
if (data.nextPageToken) {
this.loadVideos(
playListId,
channel,
data.nextPageToken,
stopLoadingOnVideoId,
);
}
});
}
Ideal solution for me would be to make loadVideos async somehow so I can later do:
public methodWhichCallLoadVideos(): void {
await loadVideos(playListId, channel, null, stopLoadingOnVideoId)
// My code which have to be executed right after videos are loaded
}
Every solution I tried out end up with this.videoIdMap to be empty object or with compilation issue so any idea is more than welcome.
You could switch to promises instead of Observables, thus turning the method into an async one that recurs as long as data has a nextPageToken:
private async loadVideos(
playListId: string,
channel: Channel,
nextPageToken: string,
stopLoadingOnVideoId: string,
) {
const baseUrl = YoutubeService.VIDEO_URL_SNIPPET_BY_ID + playListId;
const response = await this.httpService
.get(nextPageToken ? url + '&pageToken=' + nextPageToken : url).toPromise();
const { data } = response;
for (const item of data.items) {
if (stopLoadingOnVideoId && item.snippet.resourceId.videoId === stopLoadingOnVideoId) {
continue;
}
const partialVideo = await this.prepareVideoEntity(item.snippet, channel);
const video = await this.videoService.create(partialVideo)
this.videoIdMap[video.youtubeId] = video.id;
}
if (data.nextPageToken) {
await this.loadVideos(
playListId,
channel,
data.nextPageToken,
stopLoadingOnVideoId,
);
}
}
In your caller you can then simply await loadVideos(...):
private async initVideoIdMap(...) {
await this.loadVideos(...);
// this.videoIdMap should be correctly populated at this point
}

Error: Error trying to diff '[object Object]'. Only arrays and iterables are allowed cannot remove console error after using the input check box

I'm trying to bind the data from api which is written in .net core with angular api using ng for i getting the value properly but when i use the check input field my console is full on unstoppable errors
I have tried many examples from stackoverflow non them worked for me
export class UsermanagementComponent {
userDetailsList: any = [];
public userList: any= [];
departmentuser: any = {};
public searchTxt:any;
isActive: boolean = false;
checkuserstatus: boolean;
constructor(private router: Router, private http: HttpClient, private
toastr: ToastrService, private appComponent: AppComponent) {
this.userList
}
private jwtHelper: JwtHelperService = new JwtHelperService();
ngOnInit() {
this.appComponent.startSpinner();
this.getuser();
;
}
edituser(userList: any) {
localStorage.setItem("userList", JSON.stringify(userList));
console.log(userList);
this.router.navigate(["/landingpage/edituser"], userList);
}
lockUnlockUser(userList: any) {
console.log(userList);
this.http.post(environment.apiUrl + "Account/LockUserAccount", userList,
{
}).subscribe(data => {
this.appComponent.stopSpinner();
this.router.navigate(["/landingpage/usermanagement"]);
this.userList = data;
this.checkuserstatus = this.userList.lockoutEnabled;
console.log(this.checkuserstatus);
if (this.checkuserstatus == true) {
let toast = this.toastr.success(MessageVariable.UserLocked);
alert(toast);
} else if (this.checkuserstatus == false) {
let toast = this.toastr.info(MessageVariable.UserUnLocked);
alert(toast);
}
}, (err) => {
this.toastr.error(MessageVariable.ErrorMsg);
});
}
getuser() {
this.appComponent.startSpinner();
var userId = localStorage.getItem('userid');
console.log(userId);
this.http.get(environment.apiUrl + "Account/GetUser", {
}).subscribe(data => {
this.appComponent.stopSpinner();
this.userList = data;
console.log(this.userList);
}, (err) => {
this.toastr.error(MessageVariable.ErrorMsg);
});
}
}
UsermanagementComponent.html:22 ERROR Error: Error trying to diff '[object Object]'. Only arrays and iterables are allowed
at

How to implement Custom Material Data Source for Data Table?

I'm trying to implement DataSource for Material DataTable with pagenator, sorting etc.
An example of implementation is described here: https://blog.angular-university.io/angular-material-data-table/
From service i'm get following model:
export interface IResult {
results: Flat[];
currentPage: number;
pageCount: number;
pageSize: number;
length: number;
firstRowOnPage: number;
lastRowOnPage: number;
}
Method in service looks following:
getObjects(sort: string, order: string,
pageNumber = 1, pageSize = 20): Observable<IResult> {
return this.http.get<IResult>(this.serviceUrl,
{
params: new HttpParams()
.set("sort", sort)
.set("order", order)
.set('pageNumber', pageNumber.toString())
.set('pageSize', pageSize.toString())
});
}
DataSource realization:
export class OtherDataSource implements DataSource<Flat> {
private flatSubject = new BehaviorSubject<Flat[]>([]);
private loadingSubject = new BehaviorSubject<boolean>(false);
public loading$ = this.loadingSubject.asObservable();
constructor(private service: ObjectsService) {
}
connect(collectionViewer: CollectionViewer): Observable<Flat[]> {
return this.flatSubject.asObservable();
}
disconnect(collectionViewer: CollectionViewer): void {
this.flatSubject.complete();
this.loadingSubject.complete();
}
loadData(filter = '',
sortDirection = 'asc', pageIndex = 1, pageSize = 20) {
this.loadingSubject.next(true);
this.service.getObjects(filter, sortDirection,
pageIndex, pageSize).pipe(
catchError(() => of([])),
finalize(() => this.loadingSubject.next(false))
)
.subscribe(obj => this.flatSubject.next(obj));
}
}
In subscribe(obj => this.flatSubject.next(obj)) i'm getting following error: IResult is not assignable to type Flat[]. I have no error when casting obj to <Flat[]>obj, also i see that's backend return data but result in UI is empty.
I think that error here subscribe(obj => this.flatSubject.next(<Flat[]>obj)) but have no ideas how it fixing. What I'm doing wrang?
I implemented an DataSource differently. Realization looks following:
export class NmarketDataSource extends DataSource<Flat> {
resultsLength = 0;
isLoadingResults = true;
isRateLimitReached = false;
cache$: Flat[];
constructor(private nmarketService: ObjectsService,
private sort: MatSort,
private paginator: MatPaginator) {
super();
}
connect(): Observable<Flat[]> {
const displayDataChanges = [
this.sort.sortChange,
this.paginator.page
];
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 1);
return merge(...displayDataChanges)
.pipe(
startWith(null),
switchMap(() => {
return this.nmarketService.getObjects(
this.sort.active,
this.sort.direction,
this.paginator.pageIndex+1,
this.paginator.pageSize);
}),
map(data => {
this.isLoadingResults = false;
this.isRateLimitReached = false;
this.resultsLength = data.rowCount;
this.cache$ = data.results;
return data.results;
}),
catchError(() => {
this.isLoadingResults = false;
this.isRateLimitReached = true;
return of([]);
})
);
}
disconnect() { }
}
It works but doesn't match in my case.
replace this code
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;">
</mat-row>
at the end of table
this is the error