От известно време, в професионалната си работа, разработвам софтуерни приложения и на Angular. В няколко случая съм се сблъсквал с въпроси как едно или друго нещо би могло да се случи и не съм откривал бърз и еднозначен отговор за себе си. В този пост ще дискутирам две интересни неща от този характер:

  1. Как динамично от Angular да „сваляме“ в приложението third-party JavaScript библиотеки
  2. Как да се „закачим“ за процеса на инициализация на Angular приложението и да изпълним наш собствен скрипт

За илюстрация съм написал едно примерно приложение, което ще цитирам на няколко места в поста.

Кодът на примерното приложение е достъпен на: https://github.com/agyonov/NgInitGA

Самото приложение е живо на: https://agyonov.github.io/NgInitGA

Динамичен download на third-party JavaScript библиотека

За повечето библиотеки, които бихте искали да използвате, някой вече е създал, развива и поддържа NPM пакет със съответния(те) Angular модули и Вие бихте могли (а и би трябвало) да ги ползвате на готово. Но понякога няма готов NPM пакет или този, който е наличен не ви харесва и тогава можете директно да закачите исканата от вас JavaScript библиотека към вашето Angular приложение и да я използвате.

Има два лесни подхода, които са описани в документацията на Angular и примерно в тази на Google Analytics. За съжаление, те не винаги вършат работа по една или друга причина.

  1. Първото, изцяло Angular, решение е да свалите съответния .js файл от интернет. Да го добавите към Вашия проект и да поставите референция към .js библиотеката в масива scripts, на angular.json конфигурационния файл. Например:
…
"scripts": [
              "src/scripts/analytics.js"
            ]
…

Както подробно е описано в документацията на Angular.

Това е ОК като подход и ще работи.

Съображение срещу евентуалното му използване, е че при този подход .js скрипта ще се download-ва от вашия хостинг сървър, а не от оригиналния сървър (в случая https://www.google-analytics.com/analytics.js). И въпроса не е толкова в увеличения трафик към Вашия сайт и скоростта, а в това, че автора на библиотеката (Google) би могъл да променя разни неща в нея, да има нови версии и т.н., а вие няма да имате тази нова версия, докато не re-build-нете и не пре-публикувате вашето приложение.

  2. Втория подход е да се приложи инструкцията от страницата на Google Analytics:

<script>
window.ga=window.ga || function(){(ga.q=ga.q||[]).push(arguments)};
ga.l=+new Date;
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
</script>
<script async src='https://www.google-analytics.com/analytics.js'>
</script>

И горния код да се постави директно в index.html файла инициализиращ вашето Angular приложение. По-специално последния ред от горната извадка код.

Това е ОК като подход и също ще работи в частта му за download на .js библиотеката. Освен това тук възраженията, които бяха валидни за първото решение отпадат по естествен начин.

Съображение срещу евентуалното му използване, е стилистично – не е „чисто“ Angular решение.

Останалата част от отрязъка код от Google Analytics, виден по-горе, изобщо не ни върши работа! Защото при Angular имаме Single Page Application (SPA) при които, за разлика от традиционните уеб приложения, реално само първата страница се зарежда от уеб сървъра, а в последствие всеки преход от страница в страница, се случва само вътре в SPA приложението в браузера. Също ако имаме и някакви специални събития (например натискане на бутон) и те се случват вътре в Angular приложението и няма да се отразят на данните в Google Analytics.

Тези проблеми са детайлно обсъдени в самата документация на Google Analytics под темата за Single Page Application Tracking.

3. Да се върнем към въпроса с динамичния download на JavaScript библиотека. Има и трети способ, които имено ще разгледам. Понеже примера е с използване на Google Analytics – ще го разширя да се ползва и за проследяване действията на потребителите в Angular приложение.

NB: Що се касае до track-не на потребители в Angular приложение, то има чудесни, готови библиотеки, като angulartics2, които можете да ползвате. Тук въпроса не е да създавам поредната такава библиотека, а с пример да покажа възможно решение на двата въпроса зададени в началото на този пост!

Та, динамичния download – да видим извадка от следния Angular сервиз:

…
// Typescript extention of Window interface
declare global {
  interface Window { ga: any; }
}

@Injectable({
  providedIn: 'root'
})
export class GoogleAnalyticsService {

  // The source for the load
  private googleAnalyticsScript = {
    loaded: false,
    url: 'https://www.google-analytics.com/analytics.js'
  };

  constructor(@Inject(PLATFORM_ID) private platformId: Object,
    @Inject(DOCUMENT) private dom: Document) {
  }
…
  // Init the GA infrastructure
  public loadScript(): void {
    // Check already loaded
    if (!this.googleAnalyticsScript.loaded) {
      // Check if we are at browser
      if (isPlatformBrowser(this.platformId)) {
        // Create new scipt element
        const scriptElm: HTMLScriptElement = this.dom.createElement('script');
        scriptElm.src = this.googleAnalyticsScript.url;
        scriptElm.type = 'text/javascript';
        scriptElm.async = true;
        scriptElm.onload = () => {
          // Prevent from load second time
          this.googleAnalyticsScript.loaded = true;
        };

        // Define GA object
        window.ga = window.ga || function () { (window.ga.q = window.ga.q || []).push(arguments); };
        window.ga.l = +new Date;

        // Set some Google Analytics stuff
        window.ga('create', environment.propertyID, 'auto');

        // Add to document
        this.dom.body.appendChild(scriptElm);
      }
    }
  }
}

По същество това е преписване на TypeScript, на инструкцията от страницата на Google Analytics, която я има малко по-горе в поста. По важните моменти са:

  • Съвсем в началото се разширява TypeScript Window интерфейса с ново поле – ‚ga‘
  • В метода ‚loadScript‘ се извършва същинската работа:
    • Създава се скрипт елемента scriptElm и се добавя динамично към DOM-а на web странцита. Това е същото като да се постави
<script async src='https://www.google-analytics.com/analytics.js'></script>

в index.html-ла.

  • Дефинира се полето ‚ga‘, с което е разширен Window интерфейса, по начина указан ни от Google Analytics. Това е същото като да се постави
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};
ga.l=+new Date;
ga('create', 'UA-XXXXX-Y', 'auto');
</script>

И готово!

😊 Е, не съвсем… все пак трябва да се извика метода ‚loadScript‘, някъде от Angular приложението, за да се изпълни и да накара браузера да започне download-а на JavaScript библиотеката. И тук стигаме до втория въпрос:

„Закачане“ за инициализирането на Angular приложение

Но този пост стана малко дълъг. Май е време да завърша тази първа част. За „закачането“ към инициализирането на на Angular приложението, ще пиша във втората част.

Автор: Александър Гьонов

Очаквайте част 2