Subject change detection not working on page load - javascript

Here is my app.component.ts (excerpt) -
export class AppComponent {
_subscription;
constructor(private themeService: ThemeService){
themeService.getDefaultTheme();
this._subscription = themeService.themeChange.subscribe((value) => {
//Some code
});
}
}
and theme.service.ts (excerpt) -
export class ThemeService {
themeChange: Subject<boolean> = new Subject<boolean>();
getDefaultTheme(){
this.changeTheme(true);
}
changeTheme(val:boolean){
//Some code
this.themeChange.next(val);
}
}
As app-root is my root component, the constructor in app.component.ts is called shortly after the initial page load. The constructor calls getDefaultTheme() which causes the Subject in theme.service.ts to emit an event. I am subscribing to that event back in the this._subscription ... part.
In short, on the initial page load, getDefaultTheme() should be called and the subscription should be handled as well.
But when I load the page, the getDefaultTheme() method is called but the subscription is not handled. I do not get any error at the compile time as well as in the run time.
I delayed the execution of getDefaultTheme() like
setTimeout(function(){
themeService.getDefaultTheme();
}, 5000);
Now the subscription was handled. I suspect that the event is not ready to be subscribed at the page load. How can I solve this?

It looks like you're emitting your subject before you've registered the subscription in the constructor. Swap over the call to your subject to be after you've registered the subscription.
export class AppComponent {
_subscription;
constructor(private themeService: ThemeService){
this._subscription = themeService.themeChange.subscribe((value) => {
//Some code
});
// After Subscription is listening
themeService.getDefaultTheme();
}
}

Subscribers to Subject could only receive notifications pushed to it's source after the subscription. Instead you could use ReplaySubject with buffer 1. It can "hold/buffer" the current value pushed to it and emit it immediately to future subscribers.
export class ThemeService {
themeChange: ReplaySubject<boolean> = new ReplaySubject<boolean>(1);
getDefaultTheme(){
this.changeTheme(true);
}
changeTheme(val:boolean){
//Some code
this.themeChange.next(val);
}
}
While BehaviorSubject is also a viable alternative, it requires a default value during initialization:
themeChange: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);

Related

Can you wrap all typescript instance methods into a single custom error handler?

I would like to ensure that, even when it screws up, the event tracking code in my front end never causes an error that impacts the rest of the application. The event tracking is all handled by a few custom classes that deal with labeling evenings and adding the correct properties:
export class TaskTrackerService {
constructor(private abstractTracker: TrackerService) { }
// ------
created(task: Task): void {
this. abstractTracker.track('Task created', this.taskProps(task))
}
// ------
edited(task: Task): void {
this.abstractTracker.track('Task edited', this.taskProps(task))
}
// ------
private taskProps(task: Task): EventProperties{
return {
id: task.id,
task_title_char_count: task.title.length,
task_duration_seconds: task.duration,
completed: task.done,
created_at: new Date(task.created_at)
}
}
}
This class contains logic which could cause errors (e.g. task.created_at is null in the code above) and I want to ensure I catch these before they cause damage.
I want to neatly firewall this code
I want to ensure that any errors that are triggered in the tracking class don't propagate anywhere else in the app.
The only way I can think to do this is to add error handling for each individual tracking method as shown below:
export class TaskTrackerService {
constructor(private abstractTracker: TrackerService) { }
// ------
created(task: Task): void {
try{
this.abstractTracker.track('Task created', this.taskProps(task))
} catch(error){
this.abstractTracker.trackError('Error occurred')
}
}
...
}
Is there any way to wrap all instance-methods in a single, custom error handler?
To reduce the amount of boilerplate code, I'd like some means to ensure that all of the instanc methods bubble their errors into a class-level handler. Something like this:
export class TaskTrackerService {
constructor(private abstractTracker: TrackerService) { }
// I know this isn't possible but...
catchAll(error){
this.abstractTracker.trackError('Error occurred')
}
// ------
created(task: Task): void {
this.abstractTracker.track('Task created', this.taskProps(task))
}
...
}
Is anything like this possible?

Call http request on destroy angular

http request is not executed
#HostListener('window:beforeunload')
async ngOnDestroy() {
await this.microSitioService.cancelarTransaccion(this.tarjetaCreditoService.seguimientoEtapa).then(() => {});
}
I need to execute an http request when the on destroy is executed
EDIT: Revised Answer
Ok, this is the format for when you want to make certain onDestroy is called - and also stop navigation away, using $event and preventDefault. I've also added how you can return a message to the browser to describe why nav was halted. You can use this to ensure that the http request is working as intended.
#HostListener('window:beforeunload', ['$event'])
async ngOnDestroy($event) {
if (this.componentSub) {
// handle unsubscriptions
this.componentSub.unsubscribe();
}
await this.microSitioService
.cancelarTransaccion(this.tarjetaCreditoService.seguimientoEtapa)
.then(() => {});
$event.preventDefault();
$event.returnValue = 'A message.';
}
Side Note: Have you included onDestroy as an implements on the class definition?
import { Component, OnDestroy, HostListener } from '#angular/core';
class MyComponent implements onDestroy {
If you are looking for an event that executes when the angular app is destroyed, you can use the PlatformRef which has an OnDestroy() callback
in main.ts
function doSomethingOnAppDestory() {
console.log('test');
}
platformBrowserDynamic().bootstrapModule(AppModule).then(ref => {
// Ensure Angular destroys itself on hot reloads.
if (window['ngRef']) {
window['ngRef'].destroy();
}
window['ngRef'] = ref;
ref.onDestroy(doSomethingOnAppDestory);
// Otherwise, log the boot error
}).catch(err => console.error(err));
See the stackblitz demo

Button click event is not reponding in Angular

In my .html file, I have the below code :-
Here the button Data Import appears....
<button mat-menu-item (click)="download()">
<mat-icon>cloud_download</mat-icon>
<span>Data Import</span>
</button>
In the component.ts file :-
Here I have defined the functioned to be called after the button is clicked::
constructor(
private downloadService: DownloadService
)
download(){
this.downloadService.getDownload();
}
In downloadservice.ts file :-
Here the service has been created which is going to call the api /Download at the backend.
export class DownloadService {
etext : String;
baseUrl: string = environment.apiUrl + '/Download';
constructor(private http: HttpClient) { }
getDownload() {
return this.http.get(this.baseUrl);
this.etext="The operation has been done";
}
}
When I click on the Data Import button ..nothing happens and no event is generated.
1- The second line will not be executed as first statement has a return keyword:
return this.http.get(this.baseUrl);
this.etext="The operation has been done";
2- As Martin Čuka commented below, you need to subscribe the Observable being returned by httpclient.
this.downloadService.getDownload().subscribe(resp => { // do whatever });
Nothing happens because httpClient is returning Observable you need to subscribe to it.
Add subsribe to your service
this.downloadService.getDownload().subscribe();
as for the line
this.etext="The operation has been done";
compiler will say to you it's unreachable nevertheless the real problem is in missing subscribe
export class Component {
constructor(private downloadService: DownloadService){}
download(){
this.downloadService.getDownload().subscribe(
() => {
// success code
},
(error) => {
// error code
}
);
}
}}
I think that the http request is fired.
However, you don't know when it finished because you are not subscribing to the Observable that the http.get returns.
component.ts
export class Component {
constructor(private downloadService: DownloadService){}
download(){
this.downloadService.getDownload().subscribe(
() => {
// success code
},
(error) => {
// error code
}
);
}
}
Be careful with subscription, you have to unsubscribe when the subscription finish.
https://blog.angularindepth.com/the-best-way-to-unsubscribe-rxjs-observable-in-the-angular-applications-d8f9aa42f6a0

Refreshing data through constructor in Angular 2 / Ionic 2

I have Ionic 2 app with one view for 3 different data sets. Data are loaded in constructor and based on variable in page params, it's decided which data set to show.
At every successful data call by observable, event handler logs success when data are loaded. But this only works when I click/load view for a first time. If I click for 2nd or any other time, data are not re-loaded (no log). Also, when I just console log anything, it won't show at 2nd+ click.
So I wonder what should I change to load data everytime and how constructor works in this manner.
This is how my code looks like. Jsons are called from namesListProvider.
#Component({
templateUrl: '...',
})
export class ListOfNames {
...
private dataListAll: Array<any> = [];
private dataListFavourites: Array<any> = [];
private dataListDisliked: Array<any> = [];
constructor(private nav: NavController, ...) {
...
this.loadJsons();
console.log('whatever');
}
loadJsons(){
this.namesListProvider.getJsons()
.subscribe(
(data:any) => {
this.dataListFavourites = data[0],
this.dataListDisliked = data[1],
this.dataListAll = data[2]
if (this.actualList === 'mainList') {
this.listOfNames = this.dataListAll;
this.swipeLeftList = this.dataListDisliked;
this.swipeRightList = this.dataListFavourites;
}
else if (...) {
...
}
this.listSearchResults = this.listOfNames;
}, err => console.log('hey, error when loading names list - ' + err),
() => console.info('loading Jsons complete')
)
}
What you're looking for are the Lifecycle events from Ionic2 pages. So instead of using ngOnInit you can use some of the events that Ionic2 exposes:
Page Event Description
---------- -----------
ionViewLoaded Runs when the page has loaded. This event only happens once per page being created and added to the DOM. If a page leaves but is cached, then this event will not fire again on a subsequent viewing. The ionViewLoaded event is good place to put your setup code for the page.
ionViewWillEnter Runs when the page is about to enter and become the active page.
ionViewDidEnter Runs when the page has fully entered and is now the active page. This event will fire, whether it was the first load or a cached page.
ionViewWillLeave Runs when the page is about to leave and no longer be the active page.
ionViewDidLeave Runs when the page has finished leaving and is no longer the active page.
ionViewWillUnload Runs when the page is about to be destroyed and have its elements removed.
ionViewDidUnload Runs after the page has been destroyed and its elements have been removed.
In your case, you can use the ionViewWillEnter page event like this:
ionViewWillEnter {
// This will be executed every time the page is shown ...
this.loadJsons();
// ...
}
EDIT
If you're going to obtain the data to show in that page asynchronously, since you don't know how long would it take until the data is ready, I'd recommend you to use a loading popup so the user can we aware of something happening in the background (instead of showing a blank page for a few seconds until the data is loaded). You can easily add that behaviour to your code like this:
// Import the LoadingController
import { LoadingController, ...} from 'ionic/angular';
#Component({
templateUrl: '...',
})
export class ListOfNames {
...
private dataListAll: Array<any> = [];
private dataListFavourites: Array<any> = [];
private dataListDisliked: Array<any> = [];
// Create a property to be able to create it and dismiss it from different methods of the class
private loading: any;
constructor(private loadingCtrl: LoadingController, private nav: NavController, ...) {
...
this.loadJsons();
console.log('whatever');
}
ionViewWillEnter {
// This will be executed every time the page is shown ...
// Create the loading popup
this.loading = this.loadingCtrl.create({
content: 'Loading...'
});
// Show the popup
this.loading.present();
// Get the data
this.loadJsons();
// ...
}
loadJsons(){
this.namesListProvider.getJsons()
.subscribe(
(data:any) => {
this.dataListFavourites = data[0],
this.dataListDisliked = data[1],
this.dataListAll = data[2]
if (this.actualList === 'mainList') {
this.listOfNames = this.dataListAll;
this.swipeLeftList = this.dataListDisliked;
this.swipeRightList = this.dataListFavourites;
}
else if (...) {
...
}
this.listSearchResults = this.listOfNames;
}, err => console.log('hey, error when loading names list - ' + err),
() => {
// Dismiss the popup because data is ready
this.loading.dismiss();
console.info('loading Jsons complete')}
)
}
The solution is don't do this in the constructor, use ngOnInit() instead. Components are created only once, therefore the constructor will only be called when first created.
Your component class must implement the OnInit interface:
import { Component, OnInit } from '#angular/core';
#Component({
templateUrl: '...',
})
export class ListOfNames implements OnInit {
constructor(...)
ngOnInit() {
this.loadJsons();
}
private loadJsons() {
...
}
}
i'm coming from Angular 2 world, not ionic, but angular 2 has the option to register callbacks on init/destory (ngInit/ngDestory).
try to move initialization to ngInit, save subscription handler, and don't forget to unsubscribe it on destory.
i think your issue related to that you are not unsubscribing.. :\

Angular2 event called only on SOME of the elements

So i have a Modal Component with a form in it, this component is used for both creating an entry to the DB and editing an existing one.
It has a subscription option to the onSubmit event, which is being executed on a successful submit.
What happens for some reason is that some of this component's element subscription executes and some won't, and it looks like those on the "create-mode" will and those on the "edit-mode" wont.
Code Section
CreateOrUpdateTransactionComponent:
#Component({
selector: 'create-update-transaction',
templateUrl: './CreateOrUpdateTransaction.html',
providers: [AccountTransactionsService]
})
export class CreateOrUpdateTransactionComponent {
closeResult: string;
modalRef: NgbModalRef;
#Input() transaction: Transaction = new Transaction();
#Input() isCreate: boolean;
#Output() onSubmit: EventEmitter<void> = new EventEmitter<void>();
constructor(private modalService: NgbModal,
private transactionsService: AccountTransactionsService) {}
sendTransaction(): void{
let localModalRef = this.modalRef;
this.transactionsService.createOrUpdateTransaction(this.transaction, (isSuccessful)=>{
if (isSuccessful) {
this.onSubmit.emit(); //<--- The problem is here
localModalRef.close();
}
});
}
}
The HTML:
<table>
<caption>Account Transactions</caption>
<thead>
// Omitted thead
</thead>
<template let-transaction ngFor [ngForOf]="accountTransactions" let-i="index">
<tr data-toggle="collapse" [attr.data-target]="'#'+i">
// Omitted <td>s
<td> //<----These updateTransactions() are not being executed
<create-update-transaction [isCreate]="false" [transaction]="transaction" (onSubmit)="updateTransactions()"></create-update-transaction>
</td>
</tr>
<div class="container collapse" [attr.id]="i">
// some content
</div>
</template>
</table>
<create-update-transaction [isCreate]="true" (onSubmit)="updateTransactions()"></create-update-transaction>
//<---- This updateTransactions() successfully executes
Notice
If I only display one row in the table not using ngFor (keeping the call to the back-end to update the DB), it works perfectly fine.
Any idea why would this happen?
Thanks in advance!
Update1
Debugging i could notice that when on the create-mode the this.onSubmit.observers is an array with one observer and on the edit-mode its an array with 0 observers, so thats the problem. any idea why?
Update2
Debugging again and found that the this in this.transactionsService.createOrUpdateTransaction... is fine and its' onSubmit.observers contains 1 observer, before reaching the callback's code, in which the this.onSubmit.observers is an array of 0 observers
AccountTransactionsService:
#Injectable()
export class AccountTransactionsService{
private loggedBankAccount: number;
private queryObservable: ObservableQuery;
constructor(private userManagingService: UserManagingService) {
this.loggedBankAccount = userManagingService.getLoggedBankAccountNumber();
this.queryAccountTransactions();
}
queryAccountTransactions(): void{
this.queryObservable = // querying the back-end
}
createOrUpdateTransaction(transaction: Transaction, callback: (isSuccessfull: boolean) => void): void{
let isSuccessful: boolean = false;
client.mutate(/*inserting the backend*/).then((graphQLResult) => {
const { errors, data } = graphQLResult;
if (data) {
console.log('got data', data);
isSuccessful = true;
}
if (errors) {
console.log('got some GraphQL execution errors', errors);
}
callback(isSuccessful);
}).catch((error) => {
console.log('there was an error sending the query', error);
callback(isSuccessful);
});
}
getAccountTransactions(): ObservableQuery{
return this.queryObservable;
}
}
Notice
If i just execute the callback give to the AccountTransactionService.createOrUpdateTransaction (removing the call to the back-end to actually update the DB) it works perfectly fine and all the subscribers to this onSubmit event are being called.
this Console image
Set the null as a parameter :
this.onSubmit.emit(null);
So I found out the case was that the data the ngFor is bound to was being replaced by a new instance as I updated the backend, hence, it rerendered it's child Components causing reinitializing (destory + init) of them, which made the instance of the Component to be overwritten.
In order to solve this issue i have changed the querying of the accountTransaction to be only one time querying, on initializing of the parent component, and never refetching again (which triggers the rerendering).
Im displaying to the client the changes only if they succeeded on the server side, and if they failed i use a backup of the data, so the client is kept update of the real state of the server WITHOUT refetching
For the future lookers to come:
The key to the problem was that the Parent Component's *ngFor depended on data that was changing in the Child Components, causing reinitializing of the *ngFor (the Child Components) BEFORE finishing executions of the Child Components methods.
Hope it'll be helpful to somebody :)

Categories

Resources