Typescript control flow behavior - javascript

I am new to JS, TS and Angular...
So I have this angular component:
export class AdminProductsMenuComponent implements OnInit{
constructor(private productService: ProductService,
private alertService: AlertService,
private router: Router) {
this.subscribeToDeleteProductEvents();
}
productsAdminModel: IGetProductAdminModel[] = [];
private productId: string;
ngOnInit(): void {
this.executeGetAllProductsAsAdmin();
}
executeGetAllProductsAsAdmin() {
this.productService.getAllProductsAsAdmin().subscribe({
next: (productData) => this.productsAdminModel = productData
})
}
private subscribeToDeleteProductEvents() {
this.alertService.getSubjectAlertEvent().subscribe({
next: (isConfirmed) => {
if (isConfirmed) {
this.productService.deleteProduct(this.productId).subscribe({
next: () => {
this.reloadCurrentResources();
}
});
}
}
});
}
private reloadCurrentResources(): void {
// save current route first
this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
this.router.navigate(['/AdminProducts']); // navigate to same route
});
}
executeProductDelete(id: string) {
this.productId = id;
this.alertService.confirmationAlertProductDelete();
}
}
Brief explanation:
I have subscription in the constructor which listens for events during the lifetime of the component.
An event is fired when the last method is called (through the template) which prompts a SweetAlert confirm dialog. Depending on the selected the event is true or false.
Now here is the tricky part - if I move the executeProductDelete() method above reloadCurrentResources() and subscribeToDeleteProductEvents() and invoke it (executeProductDelete) it will complete the action and throw error
I have a feeling that it executes again the subscribeToDeleteProductEvents() and reloadCurrentResources() .
If I move the executeDeleteProduct() as the last method, no error occurs.
Why is this behavior? I have a feeling that they continue to run synchronously. They are not invoked anywhere else.

There seems to be 2 main problems there:
Avoid at all costs "reloading" the same component, try to abstract the reload logic into methods. This could cause weird issues and unecessary loads, as the SPA is meant to be a single page application.
Since you are problably re-instancianting the component over and over again through your reloadResources, the alert service behaviour subjects creates new subscriptions. And since you haven't unsubscribed from them, they will keep listening forever.

Related

Reload data without refresh a component when navigate to another route

I have the component B that contains a post method which insert new data and also navigates to the component A directly ,this one contains a get method which displays the inserted data.
Now I would like to know how I can have the new data without refreshing the page knowing that I succeeded but I have to put navigation end on the ngOnInit and the constructor.
constructor(private auth: FirstAksamProspectService, private router: Router) {
this.router.events.subscribe((router) => {
if (router instanceof NavigationEnd) {
this.getlistAffectaion_employe();
}
});
}
ngOnInit(): void {
this.router.events.subscribe((router) => {
if (router instanceof NavigationEnd) {
this.getlistAffectaion_employe();
}
});
this.getlistAffectaion_employe();
}
Is there another way to do it?
thank you for your help
You can use a service that would hold a BehaviorSubject and an observable:
private yourSubject: BehaviorSubject<void> = new BehaviorSubject<void>(); public yourObservable: Observable<void> = this.yourSubject.asObservable();
When you post you have to trigger a next() on the BehaviorSubject through a public method in your Service and then in your component A, in the ngOnInit you subscribe to yourObservable. In the subscribe you can then call this.getlistAffectaion_employe();

RxJS execution order after subject emission

I want to understand the code execution order following a 'next' call on a Subject.
Background: I have 3 classes (call them HaveSubject, HaveSubscription1, HaveSubscription2). HaveSubject needs to tell HS1 and HS2 to do something through a Subject that HS1 and HS2 are subscribed to. Their tasks must be completed before HaveSubject goes on to execute method somethingVeryImportant.
Pseudocode:
class HaveSubject {
// Angular service
public mySubject$: Subject<string> = new Subject<string>();
public codeImExecuting() {
this.mySubject$.next('input for tasks')
this.somethingVeryImportant();
}
private somethingVeryImportant() {
// stuff
}
}
class HaveSubscription1 {
// Angular service
constructor(private hs: HaveSubject) {
this.hs.mySubject$.subscribe(inputStr => {
// did I do this before somethingVeryImportant is called?
});
}
}
class HaveSubscription2 {
// Angular service
constructor(private hs: HaveSubject) {
this.hs.mySubject$.subscribe(inputStr => {
// did I do this before somethingVeryImportant is called?
});
}
}
My question is: what is the best way to ensure that HS1 and HS2 have executed the code attached to their subscriptions before going on to execute method somethingVeryImportant? If the order of operations is: HaveSubject calls 'next' on subject -> HS1 and HS2 do their tasks -> HaveSubject goes on to its next line of code, which is somethingVeryImportant, then I have no issues. I'm just not sure that subscriptions are executed immediately after they receive the 'next' item in the subscription.
NOTE: There are a few things I can't do that I would normally, such as have HaveSubject inject into the other two, because the other two are created dynamically (i.e. I may have none, one, or both of HaveSubscriptionX, not clear how many will be created, and these are Angular services that are provided by a component, not in the root...).
Thoughts?
Simplest call events on finishing side work in HaveSubscription# (not ideal a lot duplicated run check second option)
class HaveSubject {
// Angular service
public mySubject$: Subject<string> = new Subject<string>();
public mySubjectDone$: Subject<void> = new Subject<void>();
public constructor() {
this.mySubjectDone$.subscribe(this.somethingVeryImportant.bind(this));
}
public codeImExecuting() {
this.mySubject$.next('input for tasks')
}
private somethingVeryImportant() {
// stuff
}
}
class HaveSubscription1 {
// Angular service
constructor(private hs: HaveSubject) {
this.hs.mySubject$.subscribe(inputStr => {
// did I do this before somethingVeryImportant is called?
hs.mySubjectDone$.next()
});
}
}
class HaveSubscription2 {
// Angular service
constructor(private hs: HaveSubject) {
this.hs.mySubject$.subscribe(inputStr => {
// did I do this before somethingVeryImportant is called?
hs.mySubjectDone$.next()
});
}
}
or if you dont want to require any actions from HaveSubscription# trigger delayed action
class HaveSubject {
// Angular service
public mySubject$: Subject<string> = new Subject<string>();
public constructor() {
this.mySubject$.pipe(
debounceTime(16) // delay or auditTime, debounceTime, ...
).subscribe(this.somethingVeryImportant.bind(this));
}
public codeImExecuting() {
this.mySubject$.next('input for tasks')
}
private somethingVeryImportant() {
// stuff
}
}
class HaveSubscription1 {
// Angular service
constructor(private hs: HaveSubject) {
this.hs.mySubject$.subscribe(inputStr => {
// did I do this before somethingVeryImportant is called?
});
}
}
class HaveSubscription2 {
// Angular service
constructor(private hs: HaveSubject) {
this.hs.mySubject$.subscribe(inputStr => {
// did I do this before somethingVeryImportant is called?
});
}
}
If you have some versioning mechanism that will react to changes done in HaveSubscription# you can do this:
this.mySubject$.pipe(
map(this.calculateVersion),
distinctUntilChanged(),
).subscribe(this.somethingVeryImportant.bind(this));
so this looks like you've made some suspect architecture decisions, since the flow is
service 1 function executes and emits from observable
service 2/3/etc subscribers to service 1 observable execute code that produces some output or side effect
service 1 function needs to execute dependent on some output or side effect from service 2/3/etc functions
this requires the subject to be aware of it's observers, which is the opposite of the rxjs philosophy. The ideal solution here would be to fix these architectural concerns. It's tough to say how to accomplish this without knowing more about how or why things occur this way or what the overall goal is.
However, you can accomplish this in a pretty reliable way, you need to add some subject on your first function that can signal completion by the dependent services:
class HaveSubject {
// Angular service
public mySubject$: Subject<string> = new Subject<string>();
private workDone$: Subject<void> = new Subject<void>();
imDone() {
this.workDone$.next();
}
public codeImExecuting() {
if (this.mySubject$.observers.length) { // check for observers
// if observers, listen for that many emissions from workDone$, only reacting to the last one
this.workDone$.pipe(take(this.mySubject$.observers.length), last())
.subscribe(v => this.somethingVeryImportant());
} else { // or just run the function, doesn't matter
this.somethingVeryImportant();
}
this.mySubject$.next('input for tasks');
}
private somethingVeryImportant() {
// stuff
}
}
and have the observers of mySubject$ call imDone() when they're done.

Angular service not updating subscribed components

I have an Angular 2/4 service which uses observables to communicate with other components.
Service:
let EVENTS = [
{
event: 'foo',
timestamp: 1512205360
},
{
event: 'bar',
timestamp: 1511208360
}
];
#Injectable()
export class EventsService {
subject = new BehaviorSubject<any>(EVENTS);
getEvents(): Observable<any> {
return this.subject.asObservable();
}
deleteEvent(deletedEvent) {
EVENTS = EVENTS.filter((event) => event.timestamp != deletedEvent.timestamp);
this.subject.next(EVENTS);
}
search(searchTerm) {
const newEvents = EVENTS.filter((obj) => obj.event.includes(searchTerm));
this.subject.next(newEvents);
}
}
My home component is able to subscribe to this service and correctly updates when an event is deleted:
export class HomeComponent {
events;
subscription: Subscription;
constructor(private eventsService: EventsService) {
this.subscription = this.eventsService.getEvents().subscribe(events => this.events = events);
}
deleteEvent = (event) => {
this.eventsService.deleteEvent(event);
}
}
I also have a root component which displays a search form. When the form is submitted it calls the service, which performs the search and calls this.subject.next with the result (see above). However, these results are not reflected in the home component. Where am I going wrong? For full code please see plnkr.co/edit/V5AndArFWy7erX2WIL7N.
If you provide a service multiple times, you will get multiple instances and this doesn't work for communication, because the sender and receiver are not using the same instance.
To get a single instance for your whole application provide the service in AppModule and nowhere else.
Plunker example
Make sure your Component is loaded through or using its selector. I made a separate component and forgot to load it in the application.

Angular 2+ window.onfocus and windows.onblur

So I need in Angular 2 or 4 to manage when the browser tab of my app is focused or not. Is there any way to use the window.onfocus and window.onblur ?
Thanks a lot
You can use a component with #HostListener.
Something like:
#Component({})
export class WindowComponent {
constructor(){}
#HostListener('window:focus', ['$event'])
onFocus(event: any): void {
// Do something
}
#HostListener('window:blur', ['$event'])
onBlur(event: any): void {
// Do something
}
}
Just check that you don't have multiple WindowComponent running at the same time, because you will have an unexpected behavior, due that each instance will react to these events.
Turns out this doesn't work in services, which was my requirement. My solution was doing it "the old way":
#Injectable()
export class WindowService {
constructor(){
window.addEventListener('focus', event => {
console.log(event);
});
window.addEventListener('blur', event => {
console.log(event);
});
}
}
Not sure I did it the "correct" way, but it works on Chrome. What I'm not sure about is if I should destroy the event listener or not, and if it works in other browsers. Let me know if I'm inadvertently shooting myself in the foot here. Will update answer if so, or delete it if need be.
In a more reactive approach, I use this injection token:
export const WINDOW_FOCUS = new InjectionToken<Observable<boolean>>(
'Shared Observable based on `window focus/blurred events`',
{
factory: () => {
return merge(fromEvent(window, 'focus'), fromEvent(window, 'blur')).pipe(
startWith(null),
map(() => window.document.hasFocus()),
distinctUntilChanged(),
share(),
);
},
},
);
Ideally, you do not want to rely on the global windows variable, you could replace it with injecting the WINDOW and DOCUMENT tokens from https://github.com/ng-web-apis/common.
To use the WINDOW_FOCUS injection token, in any component or service, it can be added to the constructor like this:
#Injectable()
export class SomeService {
constructor(
#Inject(WINDOW_FOCUS) private readonly windowFocus$: Observable<boolean>
) {}
}

Angular2: Binding and Callbacks

I'm trying to create a small Directive to capture the windows global-keyup and then invoke a callback, so I basically captue the global window in a service and the keyup on my Directive:
export class EnterActivationDirective implements OnInit {
private _enterClicked: Action;
#Input() public set enterClicked(action: Action) {
this._enterClicked = action;
}
constructor(public el: ElementRef, public windowWrapperService: WindowWrapperService) {
}
ngOnInit() {
this.windowWrapperService.nativeWindow.onkeyup = this.onWindowKeyUp.bind(this);
}
private onWindowKeyUp(event: any) {
if (event.code === 'Enter' && this._enterClicked) {
this._enterClicked();
}
}
}
The Service and Action-Type aren't that interesting, since the Service just passes the native window and the Action-Type is a generic Callback without any parameters or return-value.
The logic itself works, but I get some weird effects regarding the binding to the action. So, one of my other Components registers to the Directive:
<div appEnterActivation [enterClicked]="onKeyUp.bind(this)">
<div>
... Amended
Which then triggers a search-operation:
public search(): void {
this.searchInProgress = true;
const param = this.createSearchParams();
this.searchStarted.emit(param);
this.timeReportEntryApiService.searchTimeReportEntries(param)
.then(f => {
const newObjects = ArrayMapper.MapToNewObjects(f, new TimeReportEntry());
this.searchFinished.emit(newObjects);
this.searchInProgress = false;
}).catch(f => {
this.searchInProgress = false;
throw f;
});
}
public get canSearch(): boolean {
return this.form.valid && !this.searchInProgress;
}
public onKeyUp(): void {
debugger ;
if (this.canSearch) {
this.search();
}
}
Not too much logic here, but if the search is started from the callback, it seems like the properties and functions are in place, but they are on some kind of different object:
The searchInProgress-property is set tu true, but on the second enter, it is false again
I have some animations and bindings in place, none of them are triggered
Since everything is working with a plain button, I'm almost certain it kindahow has to do with the callback and the binding to this.
I researched a bit regarding this bind, but regarding this thread Use of the JavaScript 'bind' method it seems to be needed. I also tested without binding, but then the this is bound to the global window variable.
Why are you using an #Input? Angular made #Output for such a use case:
template:
<div appEnterActivation (enterClicked)="onEnter()"></div>
class:
export class EnterActivationDirective implements OnInit {
#Output()
public readonly enterClicked: EventEmitter<any> = new EventEmitter();
#HostBinding('document.keyup.enter')
onEnter(): void {
this.enterClicked.emit();
}
}
No need for difficult checks or wrappers :)
Since you are using TypeScript you can use arrow function, that manages this correctly.
public onKeyUp = () => {
debugger ;
if (this.canSearch) {
this.search();
}
}
In that case you can just setup the property binding as
[enterClicked]="onKeyUp"

Categories

Resources