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 am working on an angular application with a node.js backend where my architecture goes like this:
front-end => angular.service => node backend => mLab DB
Currently, I'm trying to push an object into the DB provided that it does not exist yet. If it already does, it should update. This function would be accessible via a button from the cards in my front-end.
to give a clearer understanding here's some of my code.
front-end: admin-edit-home.component.html
<a mdbBtn class='btn btn-md btn-primary' mdbWavesEffect (click)="addCard()">Add</a>
the code above is a button where I can add a card to the interface. The TS below shows how the code works.
front-end: admin-edit-home.component.ts
addCard() {
this.carousels.push(this.carousels.length);
}
To give an explanation of the TS code, 'carousels' is an array that I use to do an *ngFor loop in my HTML wherein it presents the data in a card format. It is declared as:
carousels: any = [];
So in pushing a length to the 'carousels' array, it present an empty card with no collected data but still possessing the HTML elements from the original card which contains the supposed update function that I would like to have.
My problem is, how do I do the checking of the object existence from the back-end and present the results back to the front-end? I have tried this,
back-end: api.js
router.route('/carousel/update/:id').put(function(req,res) {
var data = req.body;
const myquery = { _id: ObjectId(req.params.id) };
db.collection('home').updateOne(myquery, {
$set: {
"header" : data.header,
"subheader" : data.subheader,
"img" : data.img
}
})
if (myquery === -1) {
arr.push(obj);
} else {
arr[myquery] = obj
}
}
I know my back-end code is wrong and non-functional but I just wanted to let you guys have a visualisation of what my logic is trying to achieve.
Furthermore, this back-end code should now be accessible by my angular service through this chunk of code below:
home.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable ({
providedIn: 'root'
})
export class HomeService {
constructor(private http: HttpClient) {}
updateCard(id: string, header: string, subheader: string, img: string) {
var json = {id: id, header: header, subheader: subheader, img: img}
return this.http.put<any[]>(`./api/carousel/update/${id}`, json);
}
}
After trying these things, to sum up my problem in a more concise manner, I need to check from the database if the object is already existing via ObjectId and then update it through my input fields but if not, the updateCard() should create another object in my database. I hope to get help!
EDIT
router.route('/carousel/update/:id').put(function (req, res) {
var data = req.body;
const myquery = { "_id": ObjectId };
// console.log('header: ' + data.header + " id: " + data.id)
console.log(req.params)
db.collection("home").updateMany(myquery, {
$set: {
"img" : data.img,
"header" : data.header,
"subheader": data.subheader
}
}, (err, results) => {
res.status(200).json({status: "ok"})
})
})
This is the new api.js. Please refer.
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 have a service class that should call an api and return the results:
import {Component, View} from 'angular2/angular2';
import { Inject} from 'angular2/di';
import {Http} from 'angular2/http';
var httpMap = new WeakMap<ScheduleService, Http>();
export class ScheduleService {
meetings: Array<any>;
http: any;
constructor(#Inject(Http) http:Http){
httpMap.set(this, http);
this.http = http;
//http.get('/api/sample')
// .map(response => response.json())
// .subscribe(data => {
// this.serverData = data;
// });
}
getMeetings(){
var path = '/api/meetings/';
return httpMap.get(this).get(path);
}
}
The service class is being called and injected correctly. The issue I am having is that when ever I call the getMeetings method it never makes the request to /api/meetings. If you guys notice in the constructor there is a get request to /api/sample that works perfectly if I uncomment it and run the program I check my network tab and I can see the request was made.
Looks like http.get(path) doesn't send off a request unless you call http.get(path).subscribe(); even if you don't do anything with the response.
Plunker: https://plnkr.co/edit/b1fxoIUy31EHTC84U344?p=preview
In the Plunker, open the network view in the console and click the With Subscribe / Without Subscribe buttons for comparison.