Angular class is not an Angular module - javascript

When I build my Angular library, publish it to npm, and use it as a dependency in another project, whenever I try to import on of my module classes into my app.module.ts, and get this error Class TekButtonModule is not an Angular module. I have followed steps from multiple different sites on how to create, build, and publish angular libraries, and I can't figure out why it won't recognize the class as a valid module class.
In my app.module.ts, this is how I am importing the module:
import { TekButtonModule } from "tek-angular-uimodules";
#NgModule({
imports: [
TekButtonModule
]
})
export class AppModule { }
My library project follows the standard directory structure outlined by Angular. I did not change anything with the build, or path settings when I setup the library project. My built library project has a bundles, esm5, esm2015, fesm5, fesm2015, lib (where my custom modules, and components are), as well as a package.json, public-api.d.ts (exports everything in the lib directory), README.md, tek-angular-uimodules.d.ts (exports the public api), and a tek-angular-uimodules.metadata.json file.
Is there some extra configuration that it needed that isn't setup by default to get modules to work correctly?
Here is my button module class:
import { CommonModule } from "#angular/common";
import { NgModule } from "#angular/core";
import { TekButton } from "./button";
#NgModule({
declarations: [
TekButton
],
exports: [
TekButton
],
imports: [
CommonModule
]
})
export class TekButtonModule {}
Here are some of the files that are generated when I build the project:
tek-angular-uimodules.d.ts:
/**
* Generated bundle index. Do not edit.
*/
export * from './public-api';
public-api.ts
export * from "./lib/button";
export * from "./lib/button-group";
export * from "./lib/nav-bar";
./lib/button/button-module.d.ts
export declare class TekButtonModule {
}
If I import the generated javascript module file from the esm5 directory manually in my app.module.ts file, then it works just fine. But I have to manually do that, when it should just work with the standard module import that WebStorm auto imports for me like any other package.
This is the generated module js file under the esm5 directory:
/**
* #fileoverview added by tsickle
* #suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
import { CommonModule } from "#angular/common";
import { NgModule } from "#angular/core";
import { TekButton } from "./button";
var TekButtonModule = /** #class */ (function () {
function TekButtonModule() {
}
TekButtonModule.decorators = [
{ type: NgModule, args: [{
declarations: [
TekButton
],
exports: [
TekButton
],
imports: [
CommonModule
]
},] }
];
return TekButtonModule;
}());
export { TekButtonModule };
//# sourceMappingURL=data:application/json;base64
Any help is appreciated. I can provide more code, and screenshots if needed.
This is the error I get when trying to import my module:

So I figured out what was causing my specific issue. It was two things. Point two is the direct reason for my issue, point one was making much more confusing to debug.
In order to test my built package, I would run ng build, and then cd into the built project in the dist directory, and run npm pack. I would then install my built package as a file dependency in an entirely separate project to ensure I did everything correctly. What I found out was (or I'm assuming) that there was a caching mechanism going on even when doing local file dependency installations. This caching seemed to be tied to the file name of the tgz file that is generated from npm pack. This caching was causing the very first version to be constantly reinstalled not matter how many changes I made to the built library. The fix, for me, was to simply rename the tgz file to something different each time. You should also change the library version number, and that should also work.
My file structure looked like this:
src
- public-api.ts
- lib
-- button
--- index.ts
--- public-api.ts
--- button.component.ts
--- button.module.ts
-- another-module
-- yet-another-module
The public-api.ts file directly in src looked like this:
export * from './lib/button'
// Other exports
The public-api.ts, and the index.ts file under src\lib\button looked like this:
// public-api.ts
export * from './button.component.ts'
export * from './button.module.ts'
// index.ts
export * from './public-api.ts'
I had assumed that by adding the index.ts files to each directory, the export line in the root public-api.ts would find the index file, and export the files accordingly, but somewhere along the line, this implicit resolution does not work, and, instead, an explicit export is needed. The fix was to change export * from './lib/button' to export * from './lib/button/index.ts' or export * from './lib/button/public-api.ts'.
Point two was the direct reason for my issue, point one just made it really confusing when trying to debug.

So I had the same issue with a different solution, so I thought I would share it here.
I had just updated my library to Angular 9. And everything seemed fine except the strange "Class my-module is not an Angular module" error.
What worked for me was to up tsconfig.lib.ts file with:
...
"angularCompilerOptions": {
...,
"enableIvy": false
}

Don't know if this will help anyone, but for me it was caused by the NgModule import path autocompleting with #angular/core/src/metadata/*, rather than #angular/core.

Related

how to bundle a module/component to js and use in another angular project?

I was just trying to build a module/component and serve it as a JS bundle. it builds:
#Component({
selector: 'app-component-overview',
template: '<button><ng-content></ng-content></button>',
})
export class ButtonComponent {}
#NgModule({
declarations: [ButtonComponent],
})
export class AppModule {}
the issue is that after building it to a javascript bundle. when I try to import it in another angular project. I get:
//ERROR in ... error TS2306 '...' is not a module.
loadChildren: () => import('...').then(m=>m.ButtonModule)
maybe I am missing the point here and things are different in angular, having webpack control makes it a lot easier but I want to avoid bringing custom builders and fiddling with the settings as little as possible.
Question is, is there a well documented way to bundle a module or even a standalone component to be used outside my project as a JS bundle? I could not find anything useful other than high level explanation of reusing and lazyloading thing already inside the same project.
It seems you are building this module as part of an application which is supposed to run in a browser. But what you are looking for is to build this module as part of a library which can be re-used in other projects.
See this official Angular guide on how to create a library.
After a few hours browsing around. I figured it out.
The answer that #json-derulo gave is part of the solution but there are more steps to make this work.
follow the guide: https://angular.io/guide/creating-libraries
here is the tricky part if you import the lib inside the workspace it will work. but that does not make much sense. You likely have another repository with a angular app that you want to consume the lib.
now to be able to import the this component as a lazy loaded route you will need to add "#angular/router" to the lib peerDependecies and run npm install again
now create a routing module and add the empty path to point to the component in the lib.
//my-lib-routing.module.ts
import { NgModule} from "#angular/core";
import { RouterModule } from "#angular/router";
import { MyLibComponent } from "./my-lib.component";
const routes= [
{ path: '', component: MyLibComponent },
];
#NgModule({
imports: [
RouterModule.forChild(
routes,
)
// other imports here
],
// providers: [RouterModule],
exports: [RouterModule]
})
export class myLibRoutingModule { }
change the libmodule to import the routing module
//
import { NgModule } from '#angular/core';
import { myLibRoutingModule } from './my-lib-routing.module';
import { MyLibComponent } from './my-lib.component';
#NgModule({
declarations: [MyLibComponent],
imports: [
myLibRoutingModule
],
})
export class MyLibModule { }
on the lib project root run 'npm run build'.
create a separate repo and "ng new app"
add the lib to the package.json of the main app, something like "my-lib": "../my-lib-ws/dist/my-lib".
open angular.json and add "preserveSymlinks": true to the build options.
run npm i to link the lib as a dependency.
add the libmoudle as a lazy loaded route
import { NgModule } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
const routes: Routes = [
{path: 'test', loadChildren: () => import('my-lib').then(m=>m.MyLibModule)}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
run npm run start. and try to access the 'test' route. you should see the component loaded in the main app router outled
Now, this is more of learning exercise the bundles almost gets doubled. and you would profit more using module federation. But it is a use case we have. Anyhow the steps are here if anyone can't reproduce let me know.

Built-in nodejs path aliasing and implied index.js module resolution

I want to set up import path aliasing in a typescript/node/express project WITHOUT using other packages like tsconfig-paths and link-module-alias. I would like to do this with built-in nodejs functionality (the Typscript aliasing is already done).
Basically:
// Change
import { myFn } from '../../../utils';
// To
import { myFn } from '#this/utils';
The glimmer of hope I have is with node's subpath imports. Theoretically, I can just add this to my package.json:
"imports": {
"#this/*": "./dist/*"
}
Here's the problem
This works for explicitly importing the *.js files, but it does fails for implied index.js (i.e. Folders as modules)
// Works
import { myFn } from '#this/utils/index.js';
// Does not work
import { myFn } from '#this/utils';
However, I would expect node to resolve the import like this:
if utils is a directory, the import should resolve to */utils/index.js
if utils is a file, the import should resolve to */utils.js
My first thought would be to update the package.json imports to include all possibilities, but this is not supported:
"imports": {
"#this/*": [
"./dist/*",
"./dist/*.js",
"./dist/*/index.js"
]
}
The other option seems to be to define an exports entry for every directory, but that is not scalable.
With --experimental-specifier-resolution=node flag the code below works for me.
index.js:
import { myFn } from '#this';
package.json:
...
"imports": {
"#this": "./utils"
}
...
What worked for me on a typescript node project using esbuild was to define the path twice, but for the second time don't include /*. That will allow you to import the index.ts using #utils while still allowing you to import other files underneath the directory
{
"paths": {
"#src/*": ["src/*"],
"#src": ["src"],
"#utils/*": ["src/utils/*"],
"#utils": ["src/utils"]
}
}

How can I make webpack treeshake my ES6 module?

I have followed every guide out there for turning on tree shaking in webpack 4 but webpack still bundles code from a NPM module that I have set up to be treeshaken. How can I enable treeshaking and not include particular code in my web app?
Full source code in GitHub repo
I have a CRA (create-react-app) that imports a NPM module I created to test it that looks like this:
package
index.mjs
dist
esm
components
image
index.js
index.js
index.js
with package.json like this:
"main": "index",
"module": "index.jsm",
"sideEffects": false,
that transpiles using babel with babelrc like:
{
"presets": [
[
"#babel/preset-env",
{
"modules": false
}
],
"#babel/react",
"#babel/flow"
]
}
I have 3 React components: image, avatar (that imports image) and login-button that has this es6 code:
import React from 'react'
document.body.innerHTML = document.body.innerHTML + "<div>This is a side effect and should never have happened</div>"
export default () => <button>Login</button>
The index file for the NPM es6 module:
import * as lib from './dist/esm'
export default lib
In the CRA's app.js I have:
import TreeshakingTestModule from 'treeshaking-test-module'
function App() {
return (
<div className="App">
<TreeshakingTestModule.Avatar />
</div>
);
}
When I npm start it builds and renders the avatar but I also see the message.
Note that login-button is never used however the default export of my NPM package which does export the component is imported, however I read tutorials that explained that even default exports and re-exports should still work with tree shaking if what is exported is never used.
What is happening? What am I doing wrong?
Full source code in GitHub repo
As far as I know, webpack three shakes by analysing imports statically, not by reading your code. You cannot expect webpack to realize you're only using Avatar from TreeshakingTestModule.
So you need to declare what you want to use at import level, by using named imports and exports:
import {Avatar} from 'treeshaking-test-module'
function App() {
return (
<div className="App">
<Avatar />
</div>
);
}
So webpack knows you're only using the Avatar export.
This means that Avatar needs to be a named export in your treeshaking-test-module module.

Unable to import my locally developed Angular module into Angular app

I'm looking at using this Yeoman generator as a starter for a mini project containing a few reusable form components I can publish. The generator builds a module and an example component, directive, pipe and service configured for use (you can see the template files for the generator here).
I then used npm link to allow me to install the generated project in my Angular app, which appears to work fine, as demonstrated below taken from the project's node_modules directory;
I have then imported the module into my module within my Angular app;
import { SampleModule } from 'my-test-library';
#NgModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
NgbModule,
MomentModule,
SampleModule // <-- Imported Here
],
declarations: [],
providers: []
})
export class TasksModule { }
This module import causes the error Uncaught Error: Unexpected value 'SampleModule' imported by the module 'TasksModule' and I cannot figure out why.
There are two things I notice when comparing the library I have imported with another third party library (e.g. ng-bootstrap)
As I have used npm link to import the library to allow me to test during development the entire folder structure has been imported (rather than just the dist directory. I assume this means the non-dist code is being imported by the TasksModule. If I attempt to import my-test-library/dist I get a module cannot be found error.
Unlike ng-bootstrap my library appears to be missing *.metadata.json files in the output.
My library's tsconfig.json file is as follows (unchanged from generator install)
{
"compilerOptions": {
"noImplicitAny": true,
"module": "commonjs",
"target": "ES5",
"emitDecoratorMetadata": true, <-- Checked to ensure this was true
"experimentalDecorators": true,
"sourceMap": true,
"declaration": true,
"outDir": "./dist"
},
"files": [
"typings/index.d.ts",
"index.ts"
],
"exclude": [
"node_modules",
"dist"
]
}
Based on these items, or something else, what am I missing to get this working? Thanks!
Edit: sample.*.d.ts files (as default after installation)
// sample.component.d.ts
export declare class SampleComponent {
constructor();
}
// sample.directive.d.ts
import { ElementRef } from '#angular/core';
export declare class SampleDirective {
private el;
constructor(el: ElementRef);
}
Edit 2
So I tried symlinking the dist directory (my-test-library/dist) to the root of node_modules and importing the module from there and it worked fine.
Is there a way to, using npm link, only import the dist directory (at the root)?
I also don't understand why updating my original import to import { SampleModule } from 'my-test-library/dist'; does not work?
Did you try to run watch on your code (something like "npm run watch")? That would give a better error message.
Assuming this is your index.ts file or similar to this (https://github.com/jvandemo/generator-angular2-library/blob/master/generators/app/templates/index.ts) What I am guessing is that either your module is not being exported properly or there are some circular dependencies among your exports. First, try changing the following four lines in your index.ts from:
export * from './src/sample.component';
export * from './src/sample.directive';
export * from './src/sample.pipe';
export * from './src/sample.service';
to
export {SampleComponent} from "./src/sample.component";
export {SampleDirective} from "./src/sample.directive";
export {SamplePipe} from "./src/sample.pipe";
export {SampleService} from "./src/sample.service";
If that doesnt work, then try changing the order of your exports. If still no luck, then please trying sharing the whole folder somewhere. Thanks,
In your file listing I do not see a sample.module.ts, only a component, directive, pipe and service. If you do not have a file with the #ngModule decorator which imports/provides these, then you are not truly importing a SampleModule.
the file should look something like this:
sample.modeule.ts
import { NgModule } from '#angular/core';
import { SampleDirective } from './sample.directive';
import { SampleComponent } from '../sample.component';
import { SamplePipe } from './sample.pipe';
import { SampleService } from './sample.service';
#NgModule({
imports: [ ],
declarations: [
SampleDirective,
SampleComponent,
SamplePipe
],
providers:[SampleService],
exports: [
SampleDirective,
SampleComponent,
SamplePipe
]
})
export class DayModule { }

Using Angular 1.x with TypeScript 1.5 and SystemJS

I'm trying to use Angular 1.x with TypeScript 1.5.3 and SystemJS. The index.html page is set up to System.import('bootstrapper') which should start things up.
bootstrapper.ts gets compiled to bootstrapper.js and works fine as long as it doesn't use angular (i.e. doing just a console.log() works ok)
Now, I'd like to import and use angular to bootstrap it. I've already done jspm install angular and I also installed some typings for angular using tsd. The typings are referenced at the top of the bootstrap.ts file.
Unfortunately doing import angular from 'angular' doesn't compile, I get Module "angular" has no default export. My questions are:
Why doesn't import angular from 'angular' compile? Looking in the angular.d.ts file I see declare module 'angular'{ export = angular; } which, if I understand correctly, is a default export from the module angular of a variable (defined above in the typings file) declare var angular: angular.IAngularStatic
I noticed that doing import 'angular' compiles and then I can actually reference angular and do e.g. angular.module(...), but I don't think I understand correctly how this works. Shouldn't import 'angular' do a "bare import", i.e. running a module only for its side effects? If that's the case, does that mean that this import actually registers angular in the global scope?
I'm pretty sure I don't understand correctly how modules/type definition files work in Typescript, thanks in advance for an explanation.
Firstly, the following is my preferred way to use AngularJS 1.5 with TypeScript and SystemJS:
index.html
<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
<script>
SystemJS.import('app')
.then(function (app) {
app.bootstrap(document);
})
.catch(function (reason) {
console.error(reason);
});
</script>
app/main.ts
import angular from 'angular';
const app = angular.module('app', []);
export function bootstrap(target: Element | Document) {
angular.bootstrap(target, [app.name], { strictDi: true });
}
tsconfig.json
{
"compilerOptions": {
"module": "system",
"allowSyntheticDefaultImports": true,
....
}
}
config.js (loader config, simplified)
SystemJS.config({
transpiler: "typescript",
packages: {
"app": {
"main": "main.ts",
"defaultExtension": "ts",
"meta": {
"*.ts": {
"loader": "typescript"
}
}
}
}
});
Notes:
If you are using JSPM 0.17 specify "plugin-typescript", not "typescript" for the loader and transpiler or just run $ jspm init and select a transpiler.
The you can import angular as a default, but it will still register itself on the global scope.
The reason you are getting syntax errors is because angular, and its angular.d.ts declaration file contains a CommonJS style export = declaration to allow for module and global namespace based usage. SystemJS, for maximum compatibility, interpolates this into a default export at runtime.
The TypeScript compiler needs to be made aware of this. This can be done by setting "module": "system", and/or specifying "allowSyntheticDefaultImports": true. I have done both for the sake of exposition and explicitness.
If you aren't using jspm, you just need to tell system JS where to find the typescript using map or package config
SystemJS.config({
map: {
"typescript": "node_modules/typescript"
}
});
[...] if I understand correctly, is a default export from the module
angular of a variable
Nope, that's not what's happening. Angular exports the entire namespace as the export, if that makes sense.
import angular from 'angular' is attempting to import the default from the module.
What you want is import * as angular from 'angular'; which imports the entire export as a variable.

Categories

Resources