raygun4js icon indicating copy to clipboard operation
raygun4js copied to clipboard

[Angular] Init of rg4js doesn't work correctly if it's in constructor

Open DownloadHelper opened this issue 4 years ago • 7 comments

Hi !

The standard init of rg4js for Angular ErrorHandler working good :

import * as rg4js from 'raygun4js';
import { ErrorHandler } from '@angular/core';

const VERSION_NUMBER = '1.0.0.0';
rg4js('apiKey', "apiKey");
rg4js('setVersion', VERSION_NUMBER);
rg4js('enableCrashReporting', true);
export class RaygunErrorHandler implements ErrorHandler {
  handleError(e: any) {
    rg4js('send', {
      error: e,
    });
  }
}

But that doesn't work correctly if we move the init into a constructor :

import * as rg4js from 'raygun4js';
import { ErrorHandler } from '@angular/core';

export class RaygunErrorHandler implements ErrorHandler {

  constructor() {
    const VERSION_NUMBER = '1.0.0.0';
    rg4js('apiKey', "apiKey");
    rg4js('setVersion', VERSION_NUMBER);
    rg4js('enableCrashReporting', true);
  }

  handleError(e: any) {
    rg4js('send', {
      error: e,
    });
  }
}

Do you have an idea? I would need this to get my configuration and my apiKey from an Angular service.

Thank you !

DownloadHelper avatar Jan 15 '21 12:01 DownloadHelper

Hi @DownloadHelper,

Which version of Angular are you using? I managed to get this to work, but with the version number and API key stored in an environment object.

Thanks, Sam

samuel-holt avatar Jan 17 '21 21:01 samuel-holt

Hi @samuel-holt

Thank you for your response ! I'm using Angular 11.0.6. That can work by using the environment.ts object. Unfortunately, i have to manage configuration in another application. So, my angular is calling and load the configuration in the main.ts class and then load the main AppModule.

  fetch('urlToGetMyConfig')
  .then(response => response.json())
  .then(config => {
    if (environment.production) {
      enableProdMode();
    }
    console.log("config loaded");

    platformBrowserDynamic([{ provide: APP_CONFIG, useValue: config}])
      .bootstrapModule(AppModule)
      .catch(err => console.error(err));
  })

But if we write some code before an angular class, that is execute before Angular lifecycle.

console.log("this is execute before config loaded");

export class RaygunErrorHandler implements ErrorHandler {

  constructor(){
    console.log("this is execute after config loaded");
  }

  handleError(e: any) {
    console.error(e);
    rg4js('send', {
      error: e,
    });
  }
}

That's why i need to init the rg4js into my constructor.

DownloadHelper avatar Jan 18 '21 10:01 DownloadHelper

Hi @DownloadHelper,

This is not a common use case, and not one I've come across before. What's the reason for loading the config values from a server?

One avenue that you could look at might be a factory provider. See this blog post for how to handle async providers.

Regards, Sam

samuel-holt avatar Jan 19 '21 23:01 samuel-holt

Hi @samuel-holt,

I need to load my config in another file than environment.ts because i have to respect a CI/CD principe : "build once deploy everywhere". If i use environment.ts file, i have to create something like environment.pre.ts and environment.prod.ts, so to deploy in pre we have to build like "ng build --pre" and to deploy in prod we have to build like "ng build --prod". With the solution that externalize config, we have only one build "ng build --prod", and with that we can deploy in all different environment. All is explain here : https://timdeschryver.dev/blog/angular-build-once-deploy-to-multiple-environments and i used this method : https://timdeschryver.dev/blog/angular-build-once-deploy-to-multiple-environments#platformbrowserdynamic.

Thank you for the link to handle async proviers. Unfortunalty, i already tried and this is the same result. With my main.ts i load config first and then load and bootstrap my app (AppModule). But when we write some js ABOVE Angular class/component : it seems to be run before all Angular lifecycle. That's why it would be nice if it worked by initializing rg4js in a constructor, or somewhere IN the RaygunErrorHandler class.

Regards, Antoine

DownloadHelper avatar Jan 21 '21 11:01 DownloadHelper

Hi @DownloadHelper,

One idea I had was to use the V1 API to initialize Raygun4JS synchronously - this could be done outside of the Angular sphere (i.e. in the <head> of the document in index.html or equivalent).

For example:

<head>
  <script type="text/javascript">
    !function(a,b,c,d,e,f,g,h){a.RaygunObject=e,a[e]=a[e]||function(){
    (a[e].o=a[e].o||[]).push(arguments)},f=b.createElement(c),g=b.getElementsByTagName(c)[0],
    f.async=1,f.src=d,g.parentNode.insertBefore(f,g),h=a.onerror,a.onerror=function(b,c,d,f,g){
    h&&h(b,c,d,f,g),g||(g=new Error(b)),a[e].q=a[e].q||[],a[e].q.push({
    e:g})}}(window,document,"script","//cdn.raygun.io/raygun4js/raygun.min.js","rg4js");
  </script>
  <script>
  window.addEventListener('load', () => {
    fetch('urlToGetMyConfig')
      .then(response => response.json())
      .then(config => {
        Raygun.init(config.raygunApiKey, {
          // Add options here...
        });
      });
    });
  </script>
</head>

Let me know if that could work for you.

Thanks, Sam

samuel-holt avatar Jan 25 '21 21:01 samuel-holt

Hello there. This is not working in all the cases. global Raygun variable might not be assigned in time and the fetch will be executed faster. Eventually you will get the message like 'Raygun is not defined'

I have a screaming to you, dear developers.

STOP PUSHING YOUR LINE, please. I see numerous issues where people complain on that they can't define api key when they want. You speak about incorrectness of that because "all must be known by the time the app is loaded". NO, it should not, let the business decide how to make their application. Don't tell people to make SSR just literally because of your library.

give just an option for some who really need it to initialize raygun asynchronously. Capture all errors as you do, Do not react in that one time setup in onLoadHandler. What the matter of all the setup if there is no API key? Give a method which will do the thing and let developers to manually call it. I have tried different cases.

  1. Raygun.init(...) - may result to Raygun not defined error if your fetch will be executed after Raygun.init code is run
  2. rg4js('boot') causes infinite loop if Raygun is not loaded in time

egarkavy avatar Apr 24 '23 16:04 egarkavy

If you use these setup instructions, does it work for you?

https://raygun.com/documentation/language-guides/javascript/angular-two/#using-raygun-with-server-side-rendering

darcythomas avatar Aug 02 '23 22:08 darcythomas