I am trying to lazy load a feature module from another server.I have managed to export the chunk of lazyloaded module from the node server.By referring to Solution: load independently compiled Webpack 2 bundles dynamically. On running it, browser successfully loads the script from the server, But fails to navigate to the new location and getting an error
ERROR TypeError: Cannot read property 'LazyModule' of undefined
at eval (app.module.ts:14)
at HTMLScriptElement.script.onload [as __zone_symbol__ON_PROPERTYload
(app.module.ts:58)
app.module
const appRoutes: Routes = [
{
path: 'Module1', loadChildren: () => new Promise((resolve, reject) => {
loadPlugin('http://localhost:4000/chunk.js', (exports) => {
console.log(exports);
resolve(exports.LazyModule);
});
})
},
{ path: '*', component: AppComponent }
];
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes),
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
function loadPlugin(pluginUri, mainCallback) {
installMainCallback(pluginUri, mainCallback);
loadPluginChunk(pluginUri, mainCallback);
}
function installMainCallback(pluginUri, mainCallback) {
var _pluginIdent = pluginIdent(pluginUri)
window[_pluginIdent] = function (exports) {
delete window[_pluginIdent]
mainCallback(exports)
}
}
function loadPluginChunk(pluginUri, callback) {
return loadScript(pluginUri, callback)
}
function loadScript(url, callback) {
var script = document.createElement('script')
script.src = url
script.onload = function () {
document.head.removeChild(script)
callback && callback()
}
document.head.appendChild(script)
}
function pluginIdent(pluginUri) {
return '_' + pluginUri.replace(/\./g, '_')
}
By referring to the error log, I guess that the error is due to lazyloaded module is invoked nowhere. What should I return to LoadChildren to invoke it?
Related
I want to use the CacheModule in the AuthModule and I have written this code but still getting the error in the console:
The Auth.module.ts file in which I want to import the cache module:
#Module({
providers: [CacheConfigService, SupertokensService],
exports: [],
imports: [CacheConfigModule, UsersModule],
controllers: [],
})
export class AuthModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthMiddleware).forRoutes('*');
}
static forRoot({
connectionURI,
apiKey,
appInfo,
}: AuthModuleConfig): DynamicModule {
return {
// providers and exports
imports: [CacheConfigModule],
};
}
}
The cache config.module.ts file code. The cache config.service.ts file contains the logic:
#Module({
providers: [CacheConfigService],
exports: [CacheConfigService],
imports: [
CacheModule.register<RedisClientOptions>({
isGlobal: true,
// store: redisStore,
url: 'redis://' + process.env.REDIS_HOST + ':' + process.env.REDIS_PORT,
}),
],
})
export class CacheConfigModule {}
I want to use the cache service in the following class:
#Injectable()
export class SupertokensService {
private redisClient = redis.createClient({
url: this.cacheConfigService.url,
});
constructor(
#Inject(forwardRef(() => UsersService)) private userService: UsersService,
private cacheConfigService: CacheConfigService
) {
supertokens.init({
appInfo: this.config.appInfo,
supertokens: {
connectionURI: this.config.connectionURI,
apiKey: this.config.apiKey,
},
recipeList: [
ThirdPartyEmailPassword.init({
providers: [
ThirdPartyEmailPassword.Google({
clientSecret: 'TODO: GOOGLE_CLIENT_SECRET',
clientId:
'CLIENT_ID',
}),
],
signUpFeature: {
...signup logic
},
override: {
apis: (originalImplementation: any) => {
return {
...originalImplementation,
emailPasswordSignInPOST: async (input: any) => {
if (
originalImplementation.emailPasswordSignInPOST === undefined
) {
throw Error('Should never come here');
}
let response: any =
await originalImplementation.emailPasswordSignInPOST(input);
// retrieving the input from body logic
const { email } = inputObject;
const user = await this.userService.findOneByEmail(email);
const id = user?.id;
const token = jwt.sign({email, id}, 'mysecret';, {
expiresIn: '2h',
});
response.token = token;
await this.redisClient.set("Token", token, {
EX: 60 * 60 * 24,
});
return response;
},
};
},
},
}),
],
});
}
}
The error has been thrown because you have used ConfigService in CacheConfigService, but it has never been imported into CacheConfigModule.
If you want to have ConfigService then you must import it into AppModule, as well as CacheConfigModule.
app.module.ts:
import { ConfigModule } from '#nestjs/config';
import configuration from './config/configuration'; // or wherever your config file exists
#Module({
imports: [
ConfigModule.forRoot({
load: [configuration],
}),
// rest of code
],
// rest of code
})
export class AppModule {}
config.module.ts:
import { ConfigModule } from '#nestjs/config';
#Module({
providers: [CacheConfigService],
exports: [CacheConfigService],
imports: [
CacheModule.register<RedisClientOptions>({
isGlobal: true,
// store: redisStore,
url: 'redis://' + process.env.REDIS_HOST + ':' + process.env.REDIS_PORT,
}),
ConfigModule,
],
})
export class CacheConfigModule {}
I'm using eleventy to create a static site with a sprinkle of JavaScript. I'm not using webpack or other bundlers. JavaScript is transpiled by calling 'transformFileAsync' via eleventys beforeBuild event. Here's the relevant part of eleventy config:
babel.transformFileAsync("src/assets/js/index.js").then((result) => {
fs.outputFile("dist/assets/main.js", result.code, (err) => {
if (err) throw err;
console.log("JS transpiled.");
});
});
My babel.config.js is as follows:
module.exports = (api) => {
api.cache(true);
const presets = [
[
"#babel/preset-env",
{
bugfixes: true,
modules: "systemjs",
useBuiltIns: "usage",
corejs: { version: 3, proposals: true },
},
],
];
const plugins = [];
return { presets, plugins };
};
Babel works as advertised and transpiles my js just fine. But I can't figure out how I can include (without help from a bundler) corejs polyfills in the final production bundle.
For example, the following code:
Array.from(document.getElementsByTagName("p")).forEach((p) => {
console.log(`p ${index}, startsWith('W')`, p, p.innerHTML.startsWith("W"));
});
Is transpiled to:
import "core-js/modules/es.array.for-each";
import "core-js/modules/es.array.from";
import "core-js/modules/es.string.iterator";
import "core-js/modules/es.string.starts-with";
import "core-js/modules/web.dom-collections.for-each";
System.register([], function (_export, _context) {
"use strict";
return {
setters: [],
execute: function () {
Array.from(document.getElementsByTagName("p")).forEach(function (p) {
console.log("p ".concat(index, ", startsWith('W')"), p, p.innerHTML.startsWith("W"));
});
}
};
});
How would I go about having the actual polyfill in the final bundle instead of all the imports?
I have a Angular service that looks something like this:
import {environment} from '../environment'
....
public something() {
if(environment.production)
{
// do stuf
} else {
// do something else
}
}
Now i want to test both cases (dev and prod environemnt). How do I "mock" when the environment is imported ?
So i came up with a solution where I didn't have to check for the environment in my service:
I used the useFactory:
NgModule({
declarations: [],
imports: [],
providers: [
{
provide: Service,
useFactory: (httpClient: HttpClient) => {
if (environment.production) {
return new MyCustomService(httpClient);
} else {
return new Service();
}
},
deps: [HttpClient],
}
],
bootstrap: [AppComponent]
})
This way i manage which service is provided when the application is started up
When adding the formbuilder in the constructor. I've been getting the error. I've added the ReactiveFormsModule in the app.module already.
import { Component } from '#angular/core';
import { FormBuilder, FormGroup, Validators, FormControl } from '#angular/forms';
#Component({
selector: 'app',
template: require('./app.component.pug'),
styles: [require('./app.component.scss')]
})
export class AppComponent {
private loginForm: FormGroup;
constructor(private fb: FormBuilder) {}
}
Sample app.module.ts
import { ReactiveFormsModule } from '#angular/forms';
#NgModule({
imports: [BrowserModule, ReactiveFormsModule],
declarations: [AppComponent],
providers: [],
bootstrap: [AppComponent]
})
Here is the error:
Added emitDecoratorMetadata: true to tsconfig.json file
This works for me in a Stackblitz example.
Some comments though:
Why do you require the pug file as a template? If you are using pug (and pug-watch), the html file should be there as well.
why do you require the css? Just use simply styleUrls: [ './app.component.css' ] .
Are you sure your node, npm and angular version is up to date and compatible?
Edit:
If removing the 'require' parts helps, try this:
#Component({
selector: 'app',
template: './app.component.html',
styles: ['./app.component.scss']
})
There is no native pug support in angular yet, so you should rely on other tools to translate your page to a html file. In the #Component decorator you should try to stick to the official angular documentation.
Personally, I use Gulp js for starting my dev app and a pug watcher at the same time.
Here is my gulpfile:
const gulp = require('gulp');
const clean = require('gulp-clean');
const pug = require('gulp-pug');
const git = require('gulp-git');
const file = require('gulp-file');
const spawn = require('child_process').spawn;
const shell = require('gulp-shell');
const install = require('gulp-install');
const notify = require('gulp-notify');
// *********
// BUILD APP
// *********
gulp.task('npm-install', function () {
gulp.src(['./package.json'])
.pipe(install());
});
gulp.task('clean-dist', ['npm-install'], function () {
return gulp.src('./dist', {read: false})
.pipe(clean())
});
gulp.task('pug-build', function buildHTML() {
return gulp.src('./src/**/*.pug')
.pipe(pug({doctype: 'html', pretty: false}))
.on('error', notify.onError(function (error) {
return 'An error occurred while compiling pug.\nLook in the console for details.\n' + error;
}))
.pipe(gulp.dest('./src'))
});
gulp.task('ng-build', ['clean-dist', 'pug-build'], function (cb) {
spawn('npm', ['run', 'ngbuild'], {stdio: 'inherit'})
});
gulp.task('build', ['ng-build'], function () {
});
// ****************
// DEVELOPMENT MODE
// ****************
gulp.task('pug-watch', ['pug-build'], function (cb) {
gulp.watch('./src/**/*.pug', ['pug-build'])
});
gulp.task('ng-serve', function (cb) {
spawn('ng', ['serve'], {stdio: 'inherit'});
});
gulp.task('dev-start', ['pug-watch', 'ng-serve']);
(in the package.json I have an entry:"ngbuild": "ng build --aot --progress=false" )
The problem is that in routing i have to click twice to trigger ngOnInit code.
The weird thing is, if I have two routes: A and B, and I clicked on A first, it will trigger the constructor only, and if I clicked on B after it, it will trigger A's onInit before calling B's constructor.
using angular 2.0.0-rc.4 and routes 3.0.0-beta.2
error displayed on page load:
vendors.js:2291 Unhandled promise rejection Error: Cannot match any routes: ''
at Observable._subscribe (http://localhost:54037/js/app.js:19280:28)
at Observable.subscribe (http://localhost:54037/js/app.js:56291:60)
at Observable._subscribe (http://localhost:54037/js/app.js:56328:26)
at MergeMapOperator.call (http://localhost:54037/js/app.js:26178:21)
at Observable.subscribe (http://localhost:54037/js/app.js:56291:36)
at Observable._subscribe (http://localhost:54037/js/app.js:56328:26)
at MergeMapOperator.call (http://localhost:54037/js/app.js:26178:21)
at Observable.subscribe (http://localhost:54037/js/app.js:56291:36)
at Observable._subscribe (http://localhost:54037/js/app.js:56328:26)
at MapOperator.call (http://localhost:54037/js/app.js:56831:21)
gulp file
/// <binding Clean='default, clean, resources' />
/*
This file in the main entry point for defining Gulp tasks and using Gulp plugins.
Click here to learn more. http://go.microsoft.com/fwlink/?LinkId=518007
*/
var gulp = require('gulp');
var sourcemaps = require('gulp-sourcemaps');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var typescript = require('gulp-typescript');
var systemjsBuilder = require('systemjs-builder');
const del = require("del");
// Compile TypeScript app to JS
gulp.task('compile:ts', function () {
return gulp
.src([
"appTS/**/*.ts",
"typings/*.d.ts"
])
.pipe(sourcemaps.init())
.pipe(typescript({
"module": "system",
"moduleResolution": "node",
"outDir": "app",
"target": "ES5"
}))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('app'));
});
// Generate systemjs-based bundle (app/app.js)
gulp.task('bundle:app', function () {
var builder = new systemjsBuilder('./', './system.config.js');
return builder.buildStatic('app', 'wwwroot/js/app.js');
});
// Copy and bundle dependencies into one file (vendor/vendors.js)
// system.config.js can also bundled for convenience
gulp.task('bundle:vendor', function () {
return gulp.src([
'node_modules/core-js/client/shim.min.js',
'node_modules/systemjs/dist/system-polyfills.js',
'node_modules/reflect-metadata/Reflect.js',
'node_modules/zone.js/dist/zone.js',
'node_modules/systemjs/dist/system.js',
'system.config.js'
])
.pipe(concat('vendors.js'))
.pipe(gulp.dest('build'));
});
// Copy dependencies loaded through SystemJS into dir from node_modules
gulp.task('copy:vendor', function () {
return gulp.src([
'node_modules/rxjs/bundles/Rx.js',
'node_modules/#angular/**/*'
])
.pipe(gulp.dest('build'));
});
gulp.task('vendor', ['bundle:vendor', 'copy:vendor']);
gulp.task('app', ['compile:ts', 'bundle:app']);
// Bundle dependencies and app into one file (app.bundle.js)
gulp.task('bundle', ['vendor', 'app'], function () {
return gulp.src([
'build/app.js',
'build/vendors.js'
])
.pipe(concat('app.bundle.js'))
.pipe(gulp.dest('wwwroot/js/app'));
});
/**
* Copy all resources that are not TypeScript files into build directory.
*/
gulp.task("resources", () => {
return gulp.src(["Scripts/app/**/*", "!**/*.ts"])
.pipe(gulp.dest("wwwroot/app"));
});
/**
* Remove build directory.
*/
gulp.task('clean', (cb) => {
return del(["build"], cb);
});
gulp.task('default', ['bundle']);
app.routes
import { provideRouter, RouterConfig } from '#angular/router';
import { MediaItemFormComponent } from './media-item-form.component';
import { MediaItemListComponent } from './media-item-list.component';
export const routes: RouterConfig = [
{ path: 'list', component: MediaItemListComponent },
{ path: 'add', component: MediaItemFormComponent }
];
export const APP_ROUTER_PROVIDERS = [
provideRouter(routes)
];
list component
import {Component, Inject, OnInit } from '#angular/core';
import 'rxjs/Rx';
import {MediaItemComponent} from './media-item.component';
import {CategoryListPipe} from './category-list.pipe';
import {MediaItemService} from './media-item.service';
#Component({
selector: 'media-item-list',
directives: [MediaItemComponent],
pipes: [CategoryListPipe],
providers: [MediaItemService],
templateUrl: 'app/media-item-list.component.html',
styleUrls: ['app/media-item-list.component.css']
})
export class MediaItemListComponent implements OnInit {
mediaItems;
constructor(private mediaItemService: MediaItemService) {
console.log("constructor MediaItemList");
}
ngOnInit() {
console.log("ngOnInit MediaItemList");
this.getMediaItem();
}
onMediaItemDeleted(mediaItem) {
this.mediaItemService.delete(mediaItem)
.subscribe(() => {
this.getMediaItem();
});
}
getMediaItem() {
this.mediaItemService.get().subscribe(mediaitems => {
this.mediaItems = mediaitems;
},
function (error) { console.log("Error happened" + error) },
function () {
}
);
}
}
system.js
// map tells the System loader where to look for things
var map = {
'app': 'Scripts/app',
'rxjs': 'node_modules/rxjs',
'#angular': 'node_modules/#angular'
};
// packages tells the System loader how to load when no filename and/or no extension
var packages = {
'app': { main: 'main', defaultExtension: 'js' },
'rxjs': { defaultExtension: 'js' },
};
var packageNames = [
'#angular/common',
'#angular/compiler',
'#angular/core',
'#angular/forms',
'#angular/http',
'#angular/platform-browser',
'#angular/platform-browser-dynamic',
'#angular/router',
'#angular/testing',
'#angular/upgrade',
];
// add package entries for angular packages in the form '#angular/common': { main: 'index.js', defaultExtension: 'js' }
packageNames.forEach(function (pkgName) {
packages[pkgName] = { main: 'index.js', defaultExtension: 'js' };
});
System.config({
map: map,
packages: packages
});
index.html
<html>
<head>
<title>MeWL</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<base href="/" />
<link href="resets.css" rel="stylesheet">
<script src="js/vendors.js"></script>
<script src="js/app.js"></script>
<style>
body {
margin: 0px;
padding: 0px;
background-color: #32435b;
}
</style>
</head>
<body>
<media-tracker-app>Loading...</media-tracker-app>
</body>
</html>
Update:
I'll include html of list and the component nested inside if it helps
<media-item
*ngFor="let mediaItem of mediaItems"
[mediaItemToWatch] ="mediaItem"
(deleted)="onMediaItemDeleted($event)"
[ngClass]="{'medium-movies': mediaItem.medium === 'Movies', 'medium- series' : mediaItem.medium === 'Series'}" ></media-item>
MediaItem html:
<h2>{{mediaItem.name }}</h2>
<div>{{mediaItem.category}}</div>
<div>{{mediaItem.year}}</div>
<div class="tools">
<a class="delete" (click)="onDelete()">
remove
</a>
<a class="details">
watch
</a>
</div>
Media Item ts:
import {Component, Input, Output, EventEmitter} from '#angular/core';
import {FavoriteDirective} from './favorite.directive';
#Component({
selector: 'media-item',
directives: [FavoriteDirective],
templateUrl: 'app/media-item.component.html',
styleUrls: ['app/media-item.component.css']
})
export class MediaItemComponent {
#Input('mediaItemToWatch') mediaItem;
#Output('deleted') delete = new EventEmitter();
onDelete() {
this.delete.emit(this.mediaItem);
}
}
It seems
vendors.js:2291 Unhandled promise rejection Error: Cannot match any routes: ''
causes change detection to not run
To avoid this error add a route for the '' path like
{ path: '', redirectTo: '/list', pathMatch: 'full' }
or
{ path: '', component: DummyComponent, pathMatch: 'full' }
I think better answer is to add "onSameUrlNavigation" option on
RouterModule.forRoot(
appRoutes,
{
useHash: false,
anchorScrolling: "enabled",
onSameUrlNavigation: "reload",
enableTracing: true,
scrollPositionRestoration: "enabled"
})