How to use Shared Library Javascript with Angular

DISCLAIMER: This blog  post has Spanish and English version.

When we generate some NPM modules from generator-liferay-js, you should see that the generated (.jar) module is a "bit heavy". In the case of the Angular Liferay module it is more or less 20 MB. Now, imagine we want to work with a few modules of this type and instantiate them in one page. This would bring us some problems:

  • Loss of performance: JS files will be downloaded for each module (those of the dependency) which implies a longer loading time.
  • Possible version conflicts: If you use different versions of angular, there might be conflicts.
  • Longer build time: By having to package the module with all its dependencies, the build time is considerably high.

So, we ask ourselves, how can we solve these problems? One possible solution is to use "Shared Bundle", that is, have a common javascript library in a container as a provider and consume it from the angular modules. If you did not know it, keep reading the blog and we will explain how to do it step by step.

Requirements: Node, Yeoman and NPM Installed

  1. First, it is necessary to install generator-liferay-js by running this command:

npm install -g generator-liferay-js

 

  2. Run the generator inside your “liferay-workspace/modules” or some other project folder which you are using:

yo liferay-js

 

  3. Choose the “Shared Bundle” type and fill the rest of the information needed:

  4. Open the src/index.js and add after init():

console.log('common-js-bundle is working as provider...'); 


  5. Now create an Angular Project, running the generator inside your “liferay-workspace/modules” or some other project folder which you are using: 

yo liferay-js 

 

  6. Choose the “Angular Widget” type and fill in the rest information necessary:

  7. Open the package.json and copy all inside “dependencies”. 

 8. Paste as “dependencies” in your package.json file of your “Shared Bundle” created before.

  9. Inside the “Shared Bundle” project run

npm install

 

  10. Back to Angular Widget, now open the .npmbundlerrc file (see below the  configuration needed)

           a. Exclude all dependencies declared inside the project, add the following

    "exclude":{
        "*":true
     },

 

      b. Import all dependencies you need to pull from the “Shared Bundle”. We can do through “config/imports” + the name of the provider. It should look like this:

 "config":{

  "imports":{

    "common-js-bundle":{

    "@angular/animations": "10.2.2",

    "@angular/common": "10.2.2",

    "@angular/compiler": "10.2.2",

    "@angular/core": "10.2.2",

    "@angular/forms": "10.2.2",

    "@angular/platform-browser": "10.2.2",

    "@angular/platform-browser-dynamic": "10.2.2",

    "@angular/router": "10.2.2",

    "rxjs": "6.6.3",

    "tslib": "2.0.3",

    "zone.js": "0.10.3"

    }

  }

},

 

      c. Finally if you want to access the index.js from your provider, just need to add the provider without namespace inside the “imports”

    "":{
       "common-js-bundle" : "^1.0.0"
       }

 
 
  • The final code of .npmbundlerrc file should be look this way:


11. Open the src/polyfills.ts and import your provider

    a. This import, loads main file (index.js) from provider 

  12. Back to your Angular Project, eliminate everything inside the build folder.

  13. Finally, we will deploy both projects: the consumer (Angular Widget) and the provider (Shared Bundle). To do that, run inside each project: 

npm run deploy

 

                                  .... Now the Angular Project should be much lighter :) .....

 

Here my Git repo with some samples:  https://github.com/RoselaineMarquesMontero/liferay-workspace-shared-library/tree/main/modules

 

Blogs

Thanks for your post. It will be precious for us in our next micro-frontend projects.

May be it will be great if we can create a lot of js-bundle like we are doing this for fragments.It will be great to have one bundle for Angular 10, another for a graph module like d3j or c3j, another one for datatables...So we can reuse those modules in differents projects and/or differents modules.

Eric.   

Hi Eric, 

Yep, the js-bundle it's very powerful to help us in the client-side.

It's a great idea use it js-bundle for fragments too :) , to load the module is as simple as use a Liferay loader: 

Liferay.Loader.require("common-js@1.0.0", function() { });

 

Hi Roselaine,

I have tried to make a billboard npm module provider but I can not use it in my fragment.To do this, I have done like it is explain here :https://help.liferay.com/hc/en-us/articles/360046483472-How-to-load-a-third-party-npm-package-as-a-JS-provider-OSGi-bundle-in-Liferay-7-1-and-7-2-

But Billboard needs D3, I have imported D3 too :

npm i d3

npm i billboard.js

 

But after when I want to create my fragment, it doesn't work :

Liferay.Loader.require('d3-billboard@1.0.0', function(bb) {                var chart = bb.generate({                data: {                    columns: JSON.parse(configuration.data),                    type: "area-spline"                },                bindto: fragmentElement            });});

I have tried this too :

Liferay.Loader.require('d3-billboard$d3@6.7.0', function(d3) {    Liferay.Loader.require('d3-billboard$billboard.js@3.0.3', function(bb) {

            var chart = bb.generate({                data: {                    columns: JSON.parse(configuration.data),                    type: "area-spline"                },                bindto: fragmentElement            });        });});

I just want to provide a chart library for a POC... I have found an example  with a billboard-theme-contributor but theme contributor is loaded in every pages... it is not the right way I think.

Have you an example of a such npm provider ? (It seems not to be very simple)

Eric.

Hello Eric, 

The problem is you are trying to import a Class (bb), and the Loader is waiting for a function.  In this case, you could import in your index.js and assign to a variable or create another function and call them in the loader. 

Here a simple sample: https://github.com/RoselaineMarquesMontero/liferay-workspace-shared-library/tree/main/modules/billboard-provider-js

Hello Roselaine,

Thanks for your work, but I want to use this fragment and configure it in many fragments.

In your example, we saw a graph but how can I change the values or the shape of this graph ?

Here you define the configuration in the provider not in the fragment.

Eric.

Hello,

Today I have clone your repo and deploy your module provider and I have the same pb, I have always this error in Chrome :Uncaught SyntaxError: Unexpected token ':'

In Firefox it is another bug :

 Uncaught SyntaxError: invalid property id combo:50:79

 

I don't know if it is the build or the Liferay version (I use a Liferay 7.3.10).

Can your share your jar on github because I have exactly your code and it doesn't work with my docker (BASE_IMAGE=liferay/dxp:7.3.10-ga1) ?

I will try with a liferay-ce-portal-7.3.5-ga6 to see  the difference...

Eric.

That's it... with my new liferay-ce-portal-7.3.5-ga6 env, your example works !I will try again with mine... I don't know what is the pb with the other image... I have test with the docker image of Lifebank (https://github.com/martin-dominguez/lifebank73-demo-stack)

Now I can try to use the "configuration.data" of the fragment...

Hello Roselaine, is this also possible with React-Modules?

Hello Fredi, if you are asking about do the React Modules as a consumer and the Shared Bundle as a provider. Yes :) 

It's pretty same process explained here (I've planned to post with React and React-Java this week). 

Hello Roselaine, I have tried same and working pretty good.

Could you please share any example with routing

1) how can we define page with routing url  to call a xyz component in liferay portal?(As I have created multiple components  in my angular widget)

2) how can we naviagte between mutilple widgets with the help of angular routing ?

Hi Rosaline.

I have multiple Angular Remote Apps with a vendor library that is more or less 25Mb. Can I use some similar process to share this vendor library between all remote apps?