1

enter image description hereI have been doing R&D over a bug in angular(not calling it a bug). But In SPA, js loses its scope after routing to a different component. I have read almost every answer available on the internet. Let me put it out in a simpler manner what the problem is.

  1. Scope ends after we route to a different element(we are using external js)
  2. Since my js are interconnected, it all had to load at once. so loading a particular js through component.ts method is not feasible as it again changes the scope of DOM and the other features stop working.

so if anyone is encountering such issues, let's discuss here.

Updated Image: enter image description here

My CODE : Script-loader.ts

import { Injectable } from '@angular/core'; @Injectable({
providedIn: 'root' }) export class ScriptLoaderService {

private scripts: any = {};

load(...scripts: string[]) {
    this.scripts = scripts;
    let promises: any[] = [];
    scripts.forEach((script) => promises.push(this.loadScript(script)));
    return Promise.all(promises);
}

loadScript(name: string) {
    return new Promise((resolve, reject) => {
        let script = (document.createElement('script') as any);
        script.type = 'text/javascript';
        script.src = name;
        script.async = false;
        script.defer = false;
        


        if (script.readyState) {  //IE
            script.onreadystatechange = () => {
                if (script.readyState === 'loaded' || script.readyState === 'complete') {
                    script.onreadystatechange = null;
                    resolve({script: name, loaded: true, status: 'Loaded'});
                }
            };
        } else {  //Others
            script.onload = () => {
                resolve({script: name, loaded: true, status: 'Loaded'});
            };
        }

        script.onerror = (error: any) => resolve({script: name, loaded: false, status: 'Loaded'});
        document.getElementsByTagName('head')[0].appendChild(script);
    });
}

} Blockquote

blogs.component.ts

import { Component, OnInit, AfterViewInit } from '@angular/core'; import { ScriptLoaderService } from '../script-loader.service'; declare var $: any;

@Component({ selector: 'app-blogs', templateUrl: './blogs.component.html', styleUrls: ['./blogs.component.css'] }) export class BlogsComponent implements OnInit, AfterViewInit {

constructor(private scriptloader: ScriptLoaderService) { }

ngAfterViewInit(): void { this.scriptloader.load( 'assets/js/app.js', 'assets/data/tipuedrop_content.js', 'assets/js/global.js', 'assets/js/navbar-v1.js', 'assets/js/main.js', 'assets/js/widgets.js',

  'assets/js/lightbox.js',
  'assets/js/feed.js',
  
);

   }

ngOnInit(): void { }

}

Blogs.module.ts

import { NgModule } from '@angular/core';

import { Routes, RouterModule } from "@angular/router"; import { BlogsComponent } from './blogs.component';

const routes: Routes = [ { path: "", component: BlogsComponent } ];

@NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class BlogsModule { }

videos.component.ts

import { Component, OnInit, AfterViewInit } from '@angular/core'; import { ScriptLoaderService } from '../script-loader.service'; declare var $: any; @Component({ selector: 'app-videos',
templateUrl: './videos.component.html', styleUrls: ['./videos.component.css'] }) export class VideosComponent implements OnInit, AfterViewInit {

constructor(private scriptloader: ScriptLoaderService) { }
ngAfterViewInit(): void { this.scriptloader.load( 'assets/js/app.js', 'assets/data/tipuedrop_content.js', 'assets/js/global.js', 'assets/js/navbar-v1.js', 'assets/js/main.js',

  'assets/js/widgets.js',
  
  'assets/js/lightbox.js',
  'assets/js/feed.js',
);

   }

ngOnInit(): void { }

}

videos.module.ts

import { NgModule } from '@angular/core';

import { Routes, RouterModule } from "@angular/router"; import { VideosComponent } from './videos.component';

const routes: Routes = [ { path: "", component: VideosComponent } ];

@NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class VideosModule { }

App-routing.module.ts

import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { PreloadingStrategy,
PreloadAllModules } from "@angular/router"; import { BlogsModule } from './blogs/blogs.module'; import { FeedModule } from './feed/feed.module'; import { HangoutModule } from './hangout/hangout.module'; import { HomeModule } from './home/home.module'; import { VideosModule } from './videos/videos.module';

const routes: Routes = [

{ path:"feed", loadChildren: "./feed/feed.module#FeedModule" }, { path:"read", loadChildren: "./blogs/blogs.module#BlogsModule" }, { path:"watch", loadChildren: "./videos/videos.module#VideosModule" }, { path:'home', loadChildren:"./home/home.module#HomeModule" }, { path:"hangout", loadChildren:"./hangout/hangout.module#HangoutModule" }

];

@NgModule({ imports: [RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })], exports: [RouterModule] }) export class AppRoutingModule { }

Now what is happening that when I am routing from blogs to videos. It is adding all the js below previous ones again. I don't know if it is a good practice or not. Here is the DOM HEAD to show what's happening

enter image description here

P.S. There is no exact solution which will work for you.Please write your own js in component.ts. You can import jquery and write qwery

4
  • Hvae you thought about putting whatever js you need to work across multiple components into a service and then calling on those functions/variables from the service? Commented Apr 18, 2021 at 22:12
  • I have updated the code. I used this but it didn't work to I made a script.service.ts file and define the function after that I call it by using ngonit. then since js are interconnected. It all have to load at once. but it didn't work. Commented Apr 19, 2021 at 23:28
  • Hey! I have used your code to implement and solve my bug. It solved it up to some extent but it is loading all the js files again in the DOM. As in if I have 3 js files(or 2 script tags) on the first refresh. Then if I route to another component. it is reloading the js but now the HTML DOM has 6 script tags. If you want me to share the codes I will. Commented May 5, 2021 at 22:37
  • It is showing like this. Including only one js file. ON first visit to page <script scr = 'app.js'>></script>. Then on routing to another component now it has 2 same js in the field Commented May 5, 2021 at 22:37

3 Answers 3

1

Other way.

You can load your scripts on ngOnInit() or ngAfterViewInit() of your component, I prefer ngAfterViewInit(), but how:

First, create a .ts file named "script-loader.service.ts" or what ever you want. Paste the following code in it.

import { Injectable } from '@angular/core';
@Injectable()
export class ScriptLoaderService {

    private scripts: any = {};

    load(...scripts: string[]) {
        this.scripts = scripts;
        let promises: any[] = [];
        scripts.forEach((script) => promises.push(this.loadScript(script)));
        return Promise.all(promises);
    }

    loadScript(name: string) {
        return new Promise((resolve, reject) => {
            let script = (document.createElement('script') as any);
            script.type = 'text/javascript';
            script.src = name;

            if (script.readyState) {  //IE
                script.onreadystatechange = () => {
                    if (script.readyState === 'loaded' || script.readyState === 'complete') {
                        script.onreadystatechange = null;
                        resolve({script: name, loaded: true, status: 'Loaded'});
                    }
                };
            } else {  //Others
                script.onload = () => {
                    resolve({script: name, loaded: true, status: 'Loaded'});
                };
            }

            script.onerror = (error: any) => resolve({script: name, loaded: false, status: 'Loaded'});
            document.getElementsByTagName('head')[0].appendChild(script);
        });
    }

}

Then, provide this service in a top module, for example app.module.ts

import { NgModule } from '@angular/core';
import { ScriptLoaderService } from './script-loader.service';

@NgModule({
    imports: [
    ],
    providers: [
        ScriptLoaderService
    ],
    declarations: [
    ],
    exports: [
                ]
})
export class AppModule { }

Now it's time to use this service, lets assume you want to use it in ngAfterViewInit() initialization method, so you have to implement AfterViewInit in your target component; for example:

import { Component, AfterViewInit } from '@angular/core';
import { ScriptLoaderService } from '@shared/utils/script-loader.service';

@Component({
    templateUrl: './your.component.html'
})

export class YourComponent implements AfterViewInit {

    constructor(
        private scriptLoader: ScriptLoaderService) {

    }

    ngAfterViewInit() {
        this.scriptLoader.load(
            'assets/dist/jquery.min.js',
            'assets/ckeditor/ckeditor.js'
        );
    }
}

By this way, every time that your component lazy loaded by angular router, needed scripts initialize without hard refreshing your page.

5
  • I gotta try this method. I just wanna ask one thing. Assume I have 5 components all lazy loaded. one is attached to routerlink eg: a component which routes through blog posts, video posts, status and other component remains same. so whenever I route to one component to another after refresh. Only component which lazy loaded again stops working it's js. other components keeps working as they were earlier, just js is not effective on routed component. Any way to solve this? Commented May 1, 2021 at 21:11
  • Do you hard refresh any page when routing from a component to another? If you do that I think you have to change your way and never hard refreshing SPA's like Angular. If not, please share your codes. Commented May 2, 2021 at 0:46
  • 1
    Hey! I have used your code to implement and solve my bug. It solved it up to some extent but it is loading all the js files again in the DOM. As in if I have 3 js files(or 2 script tags) on the first refresh. Then if I route to another component. it is reloading the js but now the HTML DOM has 6 script tags. If you want me to share the codes I will. – Commented May 6, 2021 at 10:29
  • 1
    I have added the DOM Image. Commented May 6, 2021 at 12:02
  • Hi! I have shared the code above please check and suggest something. Thank you Commented May 9, 2021 at 19:55
0

You can add your JS libraries or even your CSS files into angular.json file for higher scope purposes (Globally):

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "mehdi-daustany": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
            "assets": [
              "src/assets",
              "src/favicon.png"
            ],


            "styles": [
              "src/public-styles.css"
            ],

            "scripts": [
              "node_modules/jquery/dist/jquery.min.js",
              "node_modules/@microsoft/signalr/dist/browser/signalr.min.js",
              "src/assets/common/js/components/util.js"
            ]


          } ...

Importing these files compiled as a file into your DOM, named: scripts.js and styles.js. So import them by your project logic sequences.

2
  • Yes, I have tried it. This is the first thing I did. No results. It should not have to work in that manner but it is. whenever routing occurs through the routing link, the routed component loses its functionality then I have to refresh the page to make it work again. But if I have to use refresh there is no use of angular. Commented Apr 19, 2021 at 23:23
  • You can load your scripts on ngOnInit() or ngAfterViewInit() of your component, let me explain another way for you in another post. Commented Apr 20, 2021 at 1:12
0

if you still see this issue after trying the suggested answer of dynamically importing the scripts, with the help https://stackoverflow.com/a/67171327/11801411

you might be repeating the same id in different components. i encountered this issue while working with apex chart. it happened I had an id of #chart in my product component, as well as another id #chart in my dashboard component. Although they might appear not to both be present in the dom at the same time, i had issues with some scripts when I was navigating.

Just ensure to use different id's for your element even if they are not present in the same component

Angular Community

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.