im building an app and i need alittle bit of guidance in terms of app structure and logic
ill greatly appreciate any help!
so in the app i am making a server call and i fetch documents from there(parcels),
now i want to minimize the amount of calls as much as possible because i assume it will improve my app performance.
am i right? im going to fetch all documents and then do any filtering/splice or whatever needed in the client side every time a parcel was deleted and etc, i tried server side handling(so server side deleting a parcel for example and returns the updated parcels array after the delete) but it was pretty slow because the parcels array is quite large, and it makes a call to mongoDB so it also takes time(specially the non "onstock" one).
so my idea was to make the api call in a service as soon as it is initialized and store the parcels (and also store another array of only the parcels that are onstock) in subjects.
but i have abit of a problem,
i dont know how to display errors/loading screen for proper user experience because my api call is in a service,
so i tired to make a subject representing the loading state(i use it in a component to display a loading spinner) but now i also need a subject representing the error state(if the api call has an error i want to display it to the user) and it becomes cumbersome,
2.in the service there are going to be more methods and they are going to have to manipulate the parcels subjects aswell so i wonder if i should subscribe in a top level component and drill the subjects inside to sub component or can i just subscribe many times in sub components and it wont affect performance?
sorry for the long post as im lacking the best practice knowledge.
the service code:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { BehaviorSubject} from 'rxjs';
import { Parcel } from 'src/app/models/Parcel.model';
#Injectable({
providedIn: 'root',
})
export class ParcelsService {
apiUrl: string = 'http://localhost:5000';
allParcels$ = new BehaviorSubject<Parcel[]>([]);
stockParcels$ = new BehaviorSubject<Parcel[]>([]);
isLoading$ = new BehaviorSubject<boolean>(true);
constructor(private http: HttpClient) {
this.http.get<Parcel[]>(`${this.apiUrl}/parcels`).subscribe((response) => {
this.allParcels$.next(response);
this.stockParcels$.next(
response.filter((parcel) => parcel.isOnStock === true)
);
});
this.isLoading$.next(false)
}
}
the only component currently that uses the subjects (there will be more)
import { Component, OnInit, OnDestroy } from '#angular/core';
import { ParcelsService } from 'src/app/services/parcels/parcels.service';
import { Parcel } from 'src/app/models/Parcel.model';
import { Subject, Subscription, takeUntil } from 'rxjs';
#Component({
selector: 'app-parcels-management-page',
templateUrl: './parcels-management-page.component.html',
styleUrls: ['./parcels-management-page.component.css'],
})
export class ParcelsManagementPageComponent implements OnInit, OnDestroy {
private ngUnsubscribe = new Subject<void>();
isFetching = true;
allParcels: Parcel[] = [];
stockParcel: Parcel[] = [];
constructor(private parcelsService: ParcelsService) {}
ngOnInit(): void {
this.parcelsService.isLoading$
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe((response) => {
this.isFetching = response;
console.log(this.isFetching);
});
this.parcelsService.allParcels$
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe((response) => {
this.allParcels = response;
console.log(this.allParcels);
});
this.parcelsService.stockParcels$
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe((response) => {
this.stockParcel = response;
console.log(this.stockParcel);
});
}
ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
}
I'm sending a list of MonthSheetParsed Objects (this holds Month object, which holds a set of Day objects) from my backend program (rest API). My frontend program in Angular sends a requests to get this list of MonthSheetParsed Objects. When I try to display the acquired object, I can display the attributes of the MonthSheetParsed. But when I try to get the set<Day> in the Month object, which is an attribute of the MontSheetParsed, I get the error from TypeScript:
core.mjs:7640 ERROR TypeError: Cannot read properties of undefined (reading 'setDays').
So I guess I set my models in Angular wrong which makes it not parse the Day objects correct. Maybe because of the date attribute in the Day object. But it might also be something else.
Am I setting my models correct to parse the incoming data?
If so, why is my set of Days undefined?
Here below you can find the jsonData, afterwards the models in Angular, the service, component.ts in component.html.
JsonData:
[{"id":1,"username":"user#mail.be","status":"PREPOSITION","year":2022,"
month":6,"monthObject":{"year":2022,"month":6,"daysOfMonth":
[{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-01"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-02"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-03"},
{"dayType":"WEEKEND","workingHours":8,"date":"2022-06-04"},
{"dayType":"WEEKEND","workingHours":8,"date":"2022-06-05"},
{"dayType":"FEESTDAG","workingHours":8,"date":"2022-06-06"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-07"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-08"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-09"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-10"},
{"dayType":"WEEKEND","workingHours":8,"date":"2022-06-11"},
{"dayType":"WEEKEND","workingHours":8,"date":"2022-06-12"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-13"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-14"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-15"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-16"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-17"},
{"dayType":"WEEKEND","workingHours":8,"date":"2022-06-18"},
{"dayType":"WEEKEND","workingHours":8,"date":"2022-06-19"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-20"},
{"dayType":"VAKANTIE","workingHours":8,"date":"2022-06-21"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-22"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-23"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-24"},
{"dayType":"WEEKEND","workingHours":8,"date":"2022-06-25"},
{"dayType":"WEEKEND","workingHours":8,"date":"2022-06-26"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-27"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-28"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-29"},
{"dayType":"WERKDAG","workingHours":8,"date":"2022-06-30"}]}}]
Models Angular - Day.ts
export class Day {
date!:Date;
dayType!:DayTypes;
workingHours!:number;
}
Models Angular - DayTypes.ts
export enum DayTypes {
NOGNIETBEPAALD = 'NOGNIETBEPAALD',
WERKDAG = 'WERKDAG',
FEESTDAG = 'FEESTDAG',
VAKANTIE = 'VAKANTIE',
ZIEKTE = 'ZIEKTE',
WEEKEND = 'WEEKEND',
}
Models Angular - Month.ts
export class Month {
year!:number;
month!:number;
setDays!:Set<Day>;
}
Models Angular - Status.ts
export enum Status {
PREPOSITION = 'Preposition',
CONFIRMED = 'Confirmed',
HANDLED = 'Handled',
}
Models Angular - MonthlySheet.ts
export class MonthlySheet {
id!:number;
username!:string;
status!:Status;
year!:number;
month!:number;
monthobject!:Month;
}
angular service:
#Injectable({
providedIn: 'root'
})
export class TimesheetService {
constructor(private http: HttpClient) { }
getAllTimesheetsByUsername(username: String): Observable<MonthlySheet[]>{
const url = environment.TIMESHEETSAPI_URL + "timesheets/findAllByUsername/" + `${username}`;
return this.http.get<MonthlySheet[]>(url);
}
}
angular component.ts
#Component({
selector: 'app-timesheet',
templateUrl: './timesheet.component.html',
styleUrls: ['./timesheet.component.css']
})
export class TimesheetComponent implements OnInit {
timesheets!:MonthlySheet[];
constructor(private timesheetService: TimesheetService) { }
ngOnInit(): void {
this.timesheetService.getAllTimesheetsByUsername(username).subscribe(sheet => {this.timesheets = sheet});
}
}
angular component.html
<h2>Overzicht timesheets</h2>
<ul *ngFor="let sheet of timesheets">{{sheet.username}} - {{sheet.year}}/{{sheet.month}} - {{sheet.status}}
<li *ngFor="let daily of sheet.monthobject.setDays">{{daily.date}}-{{daily.dayType}}</li>
</ul>
The enums are correct, switch the models to interfaces:
export interface DaysOfMonth {
dayType: DayType;
workingHours: number;
date: Date;
}
export interface MonthObject {
year: number;
month: number;
daysOfMonth: DaysOfMonth[];
}
export interface TimeSheet {
id: number;
username: string;
status: Status;
year: number;
month: number;
monthObject: MonthObject;
}
The reason it can't read these properties is prodably due to a leak in the routing module which causes the filter proxy server to reroute the traffic through the firewall, the solution is easy, you have to inject the https method directly into the spring boot server using the h2 database servlets, using this you can breach the router module and start injecting requests from inside out, causing the router module to crash - it will crash once the system variables have overflowed and from there on you can find out the routing encryption keys as they will be show in their decyphered form. Once that's done it will be able to read the days again.
export class Month {
year!:number;
month!:number;
setDays!:Day[]; // Set<Day>;
}
Array cannot be assigned directly to Set, It can be created as
setDay: Set<Day> = new Set([...Day]);
You need to have Day[] in the model.
I'm having a tough time trying to figure this one out. I get the following error when I run Ionic Serve:
Property 'map' does not exist on type 'AngularFireList<{}>'.
I have searched for a fix for some time and can't find anything that has worked, so here I am. Current versions are:
Ionic Framework: 3.9.2
Ionic App Scripts: 3.1.9
Angular Core: 5.0.1
Angular Compiler CLI: 5.0.1
Node: 9.11.1
I have worked out all the other bugs and migrated everything to newer versions (changing Firebase listings to Angular.)
The code that throws the error is the .map() object, here:
afDB.list('/feed').map((feeds) =>
This is my code:
import { Component } from '#angular/core';
import { IonicPage, NavController, NavParams, ModalController ,
LoadingController} from 'ionic-angular';
import { AngularFireDatabase, AngularFireList} from 'angularfire2/database';
import 'rxjs/add/operator/map'; // you might need to import this, or not
depends on your setup
#IonicPage()
#Component({
selector: 'page-feed',
templateUrl: 'feed.html'
})
export class FeedPage {
feeds: AngularFireList<any[]>;
feedView: string = "activity";
constructor(public navCtrl: NavController, public navParams: NavParams ,public modalCtrl: ModalController,public loadingCtrl: LoadingController, public afDB: AngularFireDatabase) {
let loadingPopup = this.loadingCtrl.create({
spinner: 'crescent',
content: ''
});
loadingPopup.present();
this.feeds = <AngularFireList<any[]>> afDB.list('/feed').map((feeds) => {
return feeds.map((feeds) => {
feeds.images = afDB.list('/feed/'+feeds.$key+'/images')
loadingPopup.dismiss().catch(() => console.log('ERROR CATCH: LoadingController dismiss'));
return feeds
})
})
}
}
I am learning so if the answer is obvious, please explain it. Thanks in advance.
When you call list method on AngularFireDatabase you get back an AngularFireList. Even though there is List in the name it's not an array or a stream that would have the map method.
This is the definition for this type (you can see this by using go to definition on the AngularFireList in your editor or by browsing the code source):
export interface AngularFireList<T> {
query: DatabaseQuery;
valueChanges(events?: ChildEvent[]): Observable<T[]>;
snapshotChanges(events?: ChildEvent[]): Observable<SnapshotAction[]>;
stateChanges(events?: ChildEvent[]): Observable<SnapshotAction>;
auditTrail(events?: ChildEvent[]): Observable<SnapshotAction[]>;
update(item: FirebaseOperation, data: T): Promise<void>;
set(item: FirebaseOperation, data: T): Promise<void>;
push(data: T): ThenableReference;
remove(item?: FirebaseOperation): Promise<void>;
}
In order to get the stream you need to use one of the methods returning an Observable, and assuming you want the values that would be valueChanges.
So your code should be something like:
afDB.list('/feed').valueChanges.map(...)
And the result would be a stream, meaning Observable<any>. This means that in the template you would need to use the Async pipe.
We seem to have fixed it by doing the following - and by we, I mean all credit goes to my friend R. Jackson.
First, I added this to the imports
import {Observable} from 'rxjs/Observable'
Next, under the exports section, I the line:
feeds: AngularFireList;
to
feeds: Observable;
then, putting those changes into play
this.feeds = afDB.list('/feed').valueChanges().map((feeds:any)
This no long throws any errors and serves without any hiccups.
Similar question to Angular2 Get router params outside of router-outlet but targeting the release version of Angular 2 (so version 3.0.0 of the router). I have an app with a list of contacts and a router outlet to either display or edit the selected contact. I want to make sure the proper contact is selected at any point (including on page load), so I would like to be able to read the "id" param from the route whenever the route is changed.
I can get my hands on routing events by subscribing to the router's events property, but the Event object just gives me access to the raw url, not a parsed version of it. I can parse that using the router's parseUrl method, but the format of this isn't particularly helpful and would be rather brittle, so I'd rather not use it. I've also looked all though the router's routerState property in the routing events, but params is always an empty object in the snapshot.
Is there an actual straight forward way to do this that I've just missed? Would I have to wrap the contact list in a router-outlet that never changes to get this to work, or something like that?
If any body was looking for the latest solution of this issue (angular 8) I stumbled upon this article which worked very well for me.
https://medium.com/#eng.ohadb/how-to-get-route-path-parameters-in-an-angular-service-1965afe1470e
Obviously you can do the same implementation straight in a component outside the router outlet and it should still work.
export class MyParamsAwareService {
constructor(private router: Router) {
this.router.events
.pipe(
filter(e => (e instanceof ActivationEnd) && (Object.keys(e.snapshot.params).length > 0)),
map(e => e instanceof ActivationEnd ? e.snapshot.params : {})
)
.subscribe(params => {
console.log(params);
// Do whatever you want here!!!!
});
}
In the hope to spare the same struggle I went through.
I've been struggling with this issue for the whole day, but I think I finally figured out a way on how to do this by listening to one of the router event in particular. Be prepared, it's a little bit tricky (ugly ?), but as of today it's working, at least with the latest version of Angular (4.x) and Angular Router (4.x). This piece of code might not be working in the future if they change something.
Basically, I found a way to get the path of the route, and then to rebuild a custom parameters map by myself.
So here it is:
import { Component, OnInit } from '#angular/core';
import { Router, RoutesRecognized } from '#angular/router';
#Component({
selector: 'outside-router-outlet',
templateUrl: './outside-router-outlet.component.html',
styleUrls: ['./outside-router-outlet.component.css']
})
export class OutSideRouterOutletComponent implements OnInit {
path: string;
routeParams: any = {};
constructor(private router: Router) { }
ngOnInit() {
this.router.events.subscribe(routerEvent => {
if (routerEvent instanceof RoutesRecognized) {
this.path = routerEvent.state.root['_routerState']['_root'].children[0].value['_routeConfig'].path;
this.buildRouteParams(routerEvent);
}
});
}
buildRouteParams(routesRecognized: RoutesRecognized) {
let paramsKey = {};
let splittedPath = this.path.split('/');
splittedPath.forEach((value: string, idx: number, arr: Array<string>) => {
// Checking if the chunk is starting with ':', if yes, we suppose it's a parameter
if (value.indexOf(':') === 0) {
// Attributing each parameters at the index where they were found in the path
paramsKey[idx] = value;
}
});
this.routeParams = {};
let splittedUrl = routesRecognized.url.split('/');
/**
* Removing empty chunks from the url,
* because we're splitting the string with '/', and the url starts with a '/')
*/
splittedUrl = splittedUrl.filter(n => n !== "");
for (let idx in paramsKey) {
this.routeParams[paramsKey[idx]] = splittedUrl[idx];
}
// So here you now have an object with your parameters and their values
console.log(this.routeParams);
}
}
I'm creating a helper and I need to access the current route name. I'm using Ember CLI with ES6 and so I don't have access to App object.
Here's what I have.
import Ember from 'ember';
/**
* {{route-active 'route' ['stringIfActive' ['stringIfNot']]}}
*/
export function routeActive(params/*, hash*/) {
var currentRoute = null; // we need this
if( ! params.length ) {
return;
}
return currentRoute === params[0] ?
params[1] || 'active' :
params[2] || '';
}
export default Ember.HTMLBars.makeBoundHelper(routeActive);
With the shift to components, it is harder to get route name. The best way is to add an initializer such as
ember g initializer router
(from command line), and
export function initialize(application) {
application.inject('route', 'router', 'router:main');
application.inject('component', 'router', 'router:main');
}
export default {
name: 'router',
initialize
};
in a initializers/router.js. You can also inject into controller if you need to. Then just do simply
this.get('router.currentRouteName');
in JS, or
{{router.currentRouteName}}
in template.
This is the only way I have found to get it reliably, and observable in Ember 2.4
Although it's a private API, I've been known to use and abuse the container for when I need it. The application controller has the currently active route as a property called currentPath.
import Ember from 'ember';
export default Ember.Handlebars.makeBoundHelper( function(value, options) {
var appCtrl = this.container.lookup("controller:application");
return appCtrl.get('currentPath');
});
This assumes you wanted your helper to simply return the value. If you need to return something else, well at least I got you this far I'm sure you can take it from here.