Ive got a problem since i realized that I break the DRY(Dont repeat yourself) rule. So basically I have 2 modules(movies, cinemas) and few methods in them which look the same but use their module' state.
Example: Movies has 'movies' state. Cinemas has 'cinemas' state.
//cinemas.ts
#Mutation
deleteCinemaFromStore(id: string): void {
const cinemaIndex = this.cinemas.findIndex((item) => item.id === id);
if (cinemaIndex >= 0) {
const cinemasCopy = this.cinemas.map((obj) => {
return { ...obj };
});
cinemasCopy.splice(cinemaIndex, 1);
this.cinemas = cinemasCopy;
} else {
throw new Error("Provided id doesn't exist");
}
}
//movies.ts
#Mutation
deleteMovieFromStore(id: string): void {
const movieIndex = this.movies.findIndex((item) => item.id === id);
if (movieIndex >= 0) {
const moviesCopy = this.movies.map((obj) => {
return { ...obj };
});
moviesCopy.splice(movieIndex, 1);
this.movies = moviesCopy;
} else {
throw new Error("Provided id doesn't exist");
}
}
My struggle is: How can I seperate these methods into utils.ts if they have reference to 2 different states?
define another function that take the id, state and store context (this) as parameters :
function delete(id:string,itemsName:string,self:any){
const itemIndex= self[itemsName].findIndex((item) => item.id === id);
if (itemIndex>= 0) {
const itemsCopy = self[itemsName].map((obj) => {
return { ...obj };
});
itemsCopy.splice(itemIndex, 1);
self[itemsName] = itemsCopy;
} else {
throw new Error("Provided id doesn't exist");
}
}
then use it like :
//cities.ts
#Mutation
deleteCityFromStore(id: string): void {
delete(id,'cities',this)
}
//countries.ts
#Mutation
deleteCountryFromStore(id: string): void {
delete(id,'countries',this)
}
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;
}
}
}
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
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
I want to hide the header on scroll in Ionic 4 Beta 5.
I tried all the directives solutions, but none of them work for me.
So, are there any methods that work?
Use below directive
import { IonContent, DomController } from '#ionic/angular';
import { Directive, ElementRef, Input, Renderer2, SimpleChanges } from '#angular/core';
#Directive({
selector: '[scrollHide]'
})
export class ScrollHideDirective {
#Input('scrollHide') config: ScrollHideConfig;
#Input('scrollContent') scrollContent: IonContent;
contentHeight: number;
scrollHeight: number;
lastScrollPosition: number;
lastValue: number = 0;
constructor(private element: ElementRef, private renderer: Renderer2, private domCtrl: DomController) {
}
ngOnChanges(changes: SimpleChanges) {
if(this.scrollContent && this.config) {
this.scrollContent.scrollEvents = true;
let scrollStartFunc = async (ev) => {
const el = await this.scrollContent.getScrollElement();
this.contentHeight = el.offsetHeight;
this.scrollHeight = el.scrollHeight;
if (this.config.maxValue === undefined) {
this.config.maxValue = this.element.nativeElement.offsetHeight;
}
this.lastScrollPosition = el.scrollTop;
};
if(this.scrollContent && this.scrollContent instanceof IonContent) {
this.scrollContent.ionScrollStart.subscribe(scrollStartFunc);
this.scrollContent.ionScroll.subscribe(async (ev) => this.adjustElementOnScroll(ev));
this.scrollContent.ionScrollEnd.subscribe(async (ev) => this.adjustElementOnScroll(ev));
} else if(this.scrollContent instanceof HTMLElement) {
(this.scrollContent as HTMLElement).addEventListener('ionScrollStart', scrollStartFunc);
(this.scrollContent as HTMLElement).addEventListener('ionScroll',async (ev) => this.adjustElementOnScroll(ev));
(this.scrollContent as HTMLElement).addEventListener('ionScrollEnd',async (ev) => this.adjustElementOnScroll(ev));
}
}
}
private adjustElementOnScroll(ev) {
if (ev) {
this.domCtrl.write(async () => {
const el = await this.scrollContent.getScrollElement();
let scrollTop: number = el.scrollTop > 0 ? el.scrollTop : 0;
let scrolldiff: number = scrollTop - this.lastScrollPosition;
this.lastScrollPosition = scrollTop;
let newValue = this.lastValue + scrolldiff;
newValue = Math.max(0, Math.min(newValue, this.config.maxValue));
this.renderer.setStyle(this.element.nativeElement, this.config.cssProperty, `-${newValue}px`);
this.lastValue = newValue;
});
}
}
}
export interface ScrollHideConfig {
cssProperty: string;
maxValue: number;
}
Steps to use:
In your HTML
<ion-header [scrollHide]="headerScrollConfig" [scrollContent]="pageContent">
.
.
.
<ion-content #pageContent>
In your controller: Add config variables
footerScrollConfig: ScrollHideConfig = { cssProperty: 'margin-bottom', maxValue: undefined };
headerScrollConfig: ScrollHideConfig = { cssProperty: 'margin-top', maxValue: 54 };