crowmagnumb / ng-shared-example

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ng-shared-example

This is an attempt to bring together everything I have learned about creating shared libraries using Angular between the web and mobile devices using NativeScript. I hope to not only provide a resource for myself to consult, but also use as a test-bed for anything new that I might need to share as requirements change. But I also hope this is a resource that will help others to solve the same problems I have run into.

This assumes a few things installed globally, at least, ...

  • gulp
  • NativeScript CLI

Run Examples

You could just use a clone of this repo as a starting point for your own or you could dig further and start from scratch using the rough outline below under Scaffolding.

For the former, there is a script called copy_lib_scaffold that you can as follows...

cd test-lib
./copy_lib_scaffold <your_lib_dir>

This will simply create the sandbox app. Then create (or copy) the project/lib directory here. Then edit the "project" entries in the angular.json file and change all occurrences of "mytestlib" to the name of your library under projects.

In reality, I have libraries that depend on other libraries, that depend on other libraries that are then used in final web and mobile applications. But this is a good starting point.

So to see how it works for yourself do the following after cloning this repo.

cd test-lib
npm i

Before building the lib you can verify that it works within the sandbox locally. For the web ...

ng serve

For mobile, I typically use the freaking awesome NativeScript Playground installed on my Android phone so I can test it on an actual device! So I just run ...

tns preview --bundle

... but otherwise you can do ...

tns run <android | ios> --bundle

You have to use the --bundle option or you will get ...

An uncaught Exception occurred on "main" thread.
java.lang.RuntimeException: Unable to create application com.tns.NativeScriptApplication: com.tns.NativeScriptException: Application entry point file not found. Please specify the file in package.json otherwise make sure the file index.js or bootstrap.js exists.\nIf using typescript make sure your entry point file is transpiled to javascript.
	at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5876)
	at android.app.ActivityThread.access$1100(ActivityThread.java:199)
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1650)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loop(Looper.java:193)
	at android.app.ActivityThread.main(ActivityThread.java:6669)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Caused by: com.tns.NativeScriptException: Application entry point file not found. Please specify the file in package.json otherwise make sure the file index.js or bootstrap.js exists.\nIf using typescript make sure your entry point file is transpiled to javascript.
	at com.tns.Module.bootstrapApp(Module.java:311)
	at com.tns.Runtime.run(Runtime.java:544)
	at com.tns.NativeScriptApplication.onCreate(NativeScriptApplication.java:21)
	at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1154)
	at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5871)
	... 8 more
Caused by: com.tns.NativeScriptException: Failed to find module: "./", relative to: app//
	at com.tns.Module.resolvePathHelper(Module.java:146)
	at com.tns.Module.bootstrapApp(Module.java:309)
	... 12 more

Now let's build the libraries. We need to build both the web and mobile versions of the libraries. The mobile version of the lib will be named nativescript-<libname>, and for this test will be nativescript-mytestlib. Let's build both of them.

gulp build --lib mytestlib
gulp build:ns --lib mytestlib

Now let's see if we can get it working in a completely separate app. First, if you haven't done so already we need to do an initial npm install.

cd ../test-app
npm i

Now let's update the libraries in our local node_modules directory. Ther is a gulp script for that. Let's run both...

gulp updatelib --lib mytestlib
gulp updatelib --lib nativescript-mytestlib

Now just as before we can test the web and mobile versions of this app.

ng serve

... for the web and then for the mobile ...

tns preview --bundle

... or ...

tns run <android | ios> --bundle

Voila!!

Scaffolding OR How I got here

The scaffolding of this example was created using the following commands. But I am absolutely sure this is NOT complete as I was doing/trying a whole bunch of stuff. I wrote down most of it I believe.

This assumes a few other things installed globally, at least, ...

  • angular-cli
  • @nativescript/schematics

Make Web Component Library

I recommend you actually go through these steps rather than copy this template as a starting point so as to make sure you have all the latest changes necessary to angular, nativescript, ng-packagr, etc. If you simply copy this repo you may find it out of date and it might cause you great pain. Then of course, this may not work with future releases, but we'll have to tackle that as we find the issues.

mkdir ng-shared-example
ng new
    > test-lib
cd test-lib
ng g lib
    > mytestlib

The Angular CLI created a folder projects/mytestlib. Within the src folder there was a lib folder with a sample component. I simply renamed lib to the name of my component, in this case, testcomp (renaming the files within to match). In my real world examples I have many components and services in a single library. I don't understand what the lib subdirectory is supposed to be for. I don't need to add one-level to my tree unnecessarily.

The idea behind the test-lib folder is to provide a place to hold code for a library that we can package up into a reusable Angular module. The "app" here is really just a "sandbox" or test app that we can use to test our components and services within the library to make sure they work before we bother publishing them and testing them in some other app.

I like my libraries scoped so I edit the package.json file in projects/mytestlib and change the name as follows ...

    "name": "@myscope/mytestlib",
npm i rimraf --save-dev
npm i recursive-copy --save-dev

Assets from library

I also want to be able to use assets from a library in applications.

To do this add something like the following to the assets section of angular.json. Unfortunately, I currently can only get this to work with sharing of web components. This does not seem to work for mobile apps. :(

,
              {
                "glob": "**/*",
                "input": "node_modules/@myscope/mytestlib/assets",
                "output": "/assets/myscope/mytestlib"
              }

Test Web Component Library

Now after some minor editing to make a simple example I made sure that this ran as a web component.

ng serve

Publish Web Component Library

Once that was working I could publish my library. I use Verdaccio for publishing my libraries privately. It's a little bit of work to set-up, but once you do you can completely forget about it and it just works flawlessly. Just make sure it's in your computer startup scripts so it's always running.

gulp build --lib mytestlib
npm publish dist/mytestlib

The next time you publish Verdaccio requires you to bump the version of the library because it refuses to overwrite even though I know I'm the only one affected by the version bump.

cd project/mytestlib
bump patch

...where bump is this utility "bump-version": "ianstormtaylor/bump", which I installed globally.

But for everyday changes to libraries as you are testing it outside the scope of its playground you can "cheat" by not bumping the version number and instead copying the "dist" directory over the top of the one in your node_modules/@myscope/<lib> dir. An example of this script can be found in the gulpfile.js in the test-app. Run ...

cd test-app
gulp updatelib --lib mytestlib

Make other application (or library) to use this library

Build our new app and add our above library to it now that it is published (again only locally as we are using Verdaccio).

ng new
    > test-app
npm i @myscope/mytestlib

Then we add in our component from our library and test ...

ng serve

Make shared library

cd test-lib
ng add @nativescript/schematics
./node_modules/.bin/update-ns-webpack --configs
tns install typescript   <-- Not sure this is necessary. Try without?

This kind of makes a mess since it adds an "auto-generated" component and unnecessarily creates some tns specific files that it doesn't need to. I just delete the entire auto-generated directory and then cleanup anything that depends on it. In addition, it creates a app.component.tns.ts file that is unnecessary because the app.component.ts file is completely reusable here and actually kind of the point no? Anyway, I deleted that. Maybe I'll find some reason that I'll need it back to differentiate but I can add it back if so.

Anyway, after some cleanup and making app.component.tns.html be the rough NativeScript equivalent of app.component.html we can see if this works by running.

tns preview --bundle

The --bundle is necessary here, it errors without it.

Wee! I have a sandbox phone app running on my phone!

I have made a gulp script to build the nativescript plugin into an angular recognizable bundle. For each library in the project folder you will need to add a project to angular.json. Simply copy the entry for "mytestlib" and paste it immediately afterward. First change the key for this entry from mytestlib to nativescript-mytestlib. Now for every occurrence of projects/mytestlib in this new block of code replace it with tmp/nativescript-mytestlib. The gulp script will first copy all the code from src into the tmp folder and replace all the . files with the .tns. files. (e.g. *.tns.html will overwrite *.html, *.tns.ts will overwrite *.ts, etc.). Now you can build this library with the command ng build nativescript-mytestlib but the gulp script does that for us.

Originally, upon compiling, I had in issue that it was complaining about not knowing about the one-way binding attribute of [text] on the item. Googling revealed that I needed to set a schema to ignore those errors. Sucky, since we now won't catch valid errors until run-time BUT IT MAKES IT WORK! So make sure your module has the following in the schemas section...

    schemas: [NO_ERRORS_SCHEMA]

where NO_ERRORS_SCHEMA is imported from @angular-core ...

import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";

Make other application to be able to share between web and mobile

cd test-app
ng add @nativescript/schematics
./node_modules/.bin/update-ns-webpack --configs   <-- This only because the above complained and told me I should run this. Might be fixed by the time you try this.
tns install typescript   <-- Not sure this is necessary. Try without?

About


Languages

Language:JavaScript 52.2%Language:TypeScript 41.4%Language:HTML 3.9%Language:CSS 1.8%Language:Shell 0.4%Language:SCSS 0.3%