Angular "Module is not available" when switching to webpack - angularjs

My project is trying to switch from pure Javascript scripts to webpack. We have an html file, game.html, that has the following content:
<html>
...
<script src="bundle.js"></script>
...
<div ng-app="visualizerApp" ng-controller="VisualizerController as ctrl">
...
</div>
...
</html>
before switching to webpack, we just had a long list of scripts.
The angular module is created in a file app.js
import angular from 'angular';
class VisualizerController {...}
VisualizerController.$inject = ['$scope', '$rootScope'];
angular.module('visualizerApp', []).controller('VisualizerController', VisualizerController);
before switching, the file was identical except for the import statement.
When opening the html file, we get an error in the console:
[$injector:modulerr] Failed to instantiate module visualizerApp due to:
Error: [$injector:nomod] Module 'visualizerApp' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
Why does this happen and how can I solve it?
edit: my webpack.config.js
const path = require('path');
const mapType = require('./root/visualizer/js/map/PluginManager').mapType;
module.exports = {
entry: './plugins/root/visualizer/js/main.js',
output: {
filename: '../game_logs/bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /tests/,
use: {
loader: 'babel-loader',
options: {
presets: ['babel-preset-env']
}
}
}
]
},
resolve: {
alias: {
mapPlugin: path.resolve(__dirname, 'map/' + mapType),
}
}
};
library versions:
├── angular#1.6.8
├── babel-cli#6.26.0
├── babel-loader#7.1.2
└── webpack#3.10.0

It looks like angular is loaded but not your other scripts. I would start by looking at bundle.js to make sure that what you expect to be there is actually there.
You haven't shared your webpack configuration so it's hard to say definitively what's happening. Webpack changes the dynamic of how your scripts are loaded.
I would also suggest using angular.bootstrap in order to bootstrap your application instead of ng-app.
Try the following:
<html>
...
<script src="bundle.js"></script>
...
<div id="visualizerApp" ng-controller="VisualizerController as ctrl">
...
</div>
...
</html>
And in your Javascript:
import angular from 'angular';
class VisualizerController {...}
VisualizerController.$inject = ['$scope', '$rootScope'];
angular
.module('visualizerApp', [])
.controller('VisualizerController', VisualizerController);
angular.element(document).ready(function() {
let container = document.querySelector('#visualizerApp');
angular.bootstrap(container, ['visualizerApp'])
});
angular.bootstrap allows you to manage when your application is bootstrapped. Not just when angular loads.
Here's a working fiddle which uses angularjs 1.5.6 and ES5.

Related

AngularJS template loading late. Directive not able to find the elements

I'm working on preparing a .net based AngularJS web application for modern tooling(get rid of nuget in favor of npm, bundling with webpack, etc.) and later on re-writing it to Angular.
I'm having an issue with the bundled version where a directive is trying to bind a click events to a template anchor tag (< a >) but the template is not yet loaded.
On the old version with many < script > tags for every JS file this is not happening. The directive is first on the order and the controller of the template(which is loaded inside a ng-include and uses the directive) comes after.
On the bundled version I simply changed the .js files to .ts, added the npm dependencies, the needed imports statements on each file and in the webpack entry I kept the same order as in the old index.html. Still, when de directive code runs the elements it searches for are not there yet.
The parts affected: (already updated with #bryan60 answer suggestion)
shell.html
...
<div data-ng-if="vm.showMenuBar" data-ng-include="'/app/layout/sidebar.html'" class="fader-animation"></div>
...
shell.ts
import angular from 'angular';
const sideBarTemplate = require('./sidebar.html')
let controllerId = "shell";
angular.module("eqc").controller(controllerId, ["$rootScope", "$templateCache", "$window", "authService", "common", "config", shell]);
function shell($rootScope, $templateCache, $window, authService, common, config) {
$templateCache.put('app/layout/sidebar.html', sideBarTemplate)
sidebar.html
<div data-cc-sidebar data-ng-controller="sidebar as vm">
<div class="sidebar-filler"></div>
<div class="sidebar-dropdown">Menu</div>
<div class="sidebar-inner">
<div class="sidebar-widget"></div>
<ul class="navi">
<li class="nlightblue fade-selection-animation" data-ng-class="vm.isCurrent(r)" data-ng-repeat="r in vm.navRoutes">
</li>
</ul>
</div>
cc-sidebar directive:
app.directive('ccSidebar', function () {
// Opens and clsoes the sidebar menu.
// Usage:
// <div data-cc-sidebar>
// Creates:
// <div data-cc-sidebar class="sidebar">
var directive = {
link: link,
restrict: 'A'
};
return directive;
function link(scope, element, attrs) {
var $sidebarInner = element.find('.sidebar-inner');
var $dropdownElement = element.find('.sidebar-dropdown a');
element.addClass('sidebar');
$dropdownElement.click(dropdown); // <--- Here's the problem. 'dropdown' function is omitted but its defined in the next line
// The error on the console is: TypeError: "$dropdownElement.click is not a function"
// That's because it is never found
main.ts
import "./eqc";
import "./config"
import "./config.exceptionHandler"
import "./config.route"
//All folders bellow have index.ts in them including their .ts files
import "./common"
import "./services"
import "./Views"
import "./layout"
eqc.ts
import 'jquery';
import angular from 'angular';
import 'angular-animate';
import 'angular-route';
import 'angular-sanitize';
import 'angular-messages';
import 'angular-local-storage'
import 'angular-ui-bootstrap';
import 'angular-ui-mask';
import 'angular-loading-bar';
import 'breeze-client';
import 'breeze-client/breeze.bridge.angular';
let eqc = angular.module("eqc", [ .....
webpack.config.ts
const path = require('path');
module.exports = {
mode: "development",
entry: "./src/app/main.ts",
output: {
path: path.resolve(__dirname, "src/dist"),
filename: "bundle.js"
},
module: {
rules: [
//All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'
{
test: /\.tsx?$/,
use: { loader: "ts-loader" }
},
{
test: /\.html$/,
use: { loader: 'html-loader' }
}
]
},
resolve: {
//Add '.ts' and '.tsx' as a resovaable extension
extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
},
devtool: "source-map"
};
My folder structure:
not sure exactly what your problem is due to the lack of code / details... but very generally, the simplest way to make ng-include work with webpack is to use require statements and the template cache....
Assuming you have some template like:
<ng-include src="'app/my-included-template.html'"></ng-include>
in the controller for that template, you'll have something set up like...
const myIncludedTemplate = require('./my-included-template.html')
function MyController($templateCache) {
$templateCache.put('app/my-included-template.html', myIncludedTemplate)
}
to make the require statement work with webpack, you'll need am html loader configured, i have this to do it in my module rules array...
{
test: /\.html$/,
use: [
'html-loader'
],
},
this particular implementation will require you to npm install --save-dev html-loader
this will instruct webpack to inline your template to the source file, and then your controller will immediately put it into the template cache so that it can be loaded correctly without worrying about the webpack bundle structure itself as angular will check the template cache before loading remotely. This is also a generally more performant way of doing things.
you can also do something similar when defining your directive / component templates:
template: require('./my-directive-template.html'),
which yields the same benefits of webpack just inlining the template so it doesn't need to be loaded remotely.

HelloWorld example returns a syntax error on React

I tried to check all the libraries/packages that I needed to run a simple example of HelloWorld on React.js without success.
var React = require('react');
var ReactDOM = require('react-dom');
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('example')
);
The error is the following:
/Users/Silvio/WebstormProjects/untitled/main.js:5
<h1>Hello, world!</h1>,
^
SyntaxError: Unexpected token <
I have installed babel and ReactDOM.
In your .babelrc file you need to specify the following
{
"presets": ["react", "stage-0", "es2015"]
}
Also you need to install the above presets like
npm install -S babel-preset-react babel-preset-stage-0 babel-preset-es2015
Along with that you webpack.config.js must look something like below to enable babel for .js or .jsx file extensions
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: './main.js',
output: { path: __dirname, filename: 'bundle.js' },
module: {
loaders: [
{
test: /.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
}
]
},
};
You can refer here and here for more details
The code itself is correct, but you probably aren't running it properly as it is meant to be run in the browser, not in Node.js. If require is used to import dependencies, main.js must first be processed by a bundler like webpack before it is ready for use.
The following snippet is essentially the same code that you have posted but the dependencies (React and ReactDOM) are imported via script tags.
ReactDOM.render(<h1>Hello, world</h1>, document.getElementById("example"))
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello, world</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react-dom.js"></script>
</head>
<body>
<div id="example"></div>
</body>
</html>
Here Babel, which transpiles JSX (<h1>Hello, world</h1>) is provided by the snippet editor. This minimal example imports Babel as a dependency and transpiles the JSX portion at run time.
You need to run this through babel first - with react and stage-0 presets enabled.
We do this for our sample code here:
https://github.com/flexicious/react-redux-datagrid

Standalone React Component with Webpack

I've got a component I'd like to share/reuse in some projects. I'm trying to build/bundle this component so it doesn't take the large setup that react normally does (webpack/babel/npm/ect).
I want to
Include React/ReactDOM from a cdn somewhere on an html page.
Include my component js file (we'll call it standalone.js).
Little bit of initialization code to render this into the dom. No Babel, No Webpack, No JSX.
That's all.
I feel like I've gotton pretty close, though am stuck on item 3. I cannot figure out how render my component to the DOM.
Here's the relevant part of demo html page:
index.html (relevant parts)
<div id="app" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.min.js"></script>
<!--My Component -->
<script src="build/standalone.js" type="text/javascript"></script>
<script>
// I believe I'm doing something wrong here
var myComponent = new MyLibrary.default();
var myStandaloneElement = React.createElement(myComponent, { message: "Testing Standalone Component" });
ReactDOM.render(myStandaloneElement, document.getElementById('app'));
</script>
standalone.jsx
import React, {PropTypes} from 'react';
class Standalone extends React.Component {
render() {
return <p>{this.props.message}</p>;
}
}
Standalone.PropTypes = {
message: PropTypes.string.isRequired
}
export default Standalone;
webpack.config.js (relevant parts)
var config = {
entry: APP_DIR + '/standalone.jsx',
output: {
library: 'MyLibrary',
libraryTarget: 'umd',
path: BUILD_DIR,
filename: 'standalone.js'
},
module: {
loaders: [
{
test: /\.jsx?/,
include: APP_DIR,
loader: 'babel'
}
]
},
externals: {
react: 'React',
"react-dom": 'ReactDOM'
},
}
With trying to render the component with basic html I've tried a bunch of variations of similar things. Looking in my debugger, I can tell the object is something 'close' to a react-type object. I just don't know what to do with it.
Any pointers appreciated
You should not instantiate components with a new, rather they should be instantiated with React.createElement factory. So you just pass reference to the element class/function to createElement, see modified part of yout html:
...
// get reference to my component constructor
var myComponent = MyLibrary.default;
var myStandaloneElement = React.createElement(myComponent, { message: "Testing Standalone Component" });
ReactDOM.render(myStandaloneElement, document.getElementById('app'));
...
On a side note, to simplify debugging while in development (and only in development!) I suggest to use non minified version of react.js and react-dom.js, they are located under node_modules, for instance:
<script src="/node_modules/react/dist/react.js"></script>
<script src="/node_modules/react-dom/dist/react-dom.js"></script>
You may want to consider exposing your React component as a webcomponent, such as with https://www.npmjs.com/package/reactive-elements
<body>
<my-react-component item="{window.someValue}"></my-react-component>
</body>

Simple Example Using Angular 1 with TypeScript and SystemJS

I have been trying to convert a TypeScript Angular 1 application to use ES6 style module imports.
Dropping the use of namespace and using the import keyword to pull in modules. e.g.
import * as angular from "angular";
import * as angularroute from "angular-route";
But I have run into some issues.
I'm getting an error from angular:
Uncaught (in promise) Error: (SystemJS) [$injector:modulerr] Failed to instantiate module testApp due to:
Error: [$injector:modulerr] Failed to instantiate module ngRoute due to:
Error: [$injector:nomod] Module 'ngRoute' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument
To illustrate the issue, I have created two applications on github:
1) Angular1WithNamespaces- Original app that I want to convert.
2) Angular1WithSystemJS - My Conversion, that has an issue.
Below are snippets from the Angular1WithSystemJS example that has the issue.
index.html
<!DOCTYPE html>
<html lang="en" ng-app="testApp">
<head><base href="/"></head>
<body >
<ng-view></ng-view>
<script src="node_modules/systemjs/dist/system.js"></script>
<script src="configs/systemjs.config.js"></script>
<script>
System.import('app/boot');
</script>
</body>
</html>
systemjs.config.js
System.config({
defaultJSExtensions: true,
paths: {
"npm:*": "../node_modules/*"
},
// map tells the System loader where to look for things
map: {
"angular": "npm:angular/angular.js",
"angular-route": "npm:angular-route/angular-route.js",
},
meta: {
'angular': {
format: 'global',
},
'angular-route': {
format: 'global',
},
}
});
boot.ts
/// <reference path="../typings/tsd.d.ts" />
import * as angular from "angular";
import * as angularroute from "angular-route";
let main = angular.module("testApp", ["ngRoute"]);
main.config(routeConfig)
routeConfig.$inject = ["$routeProvider"];
function routeConfig($routeProvider: angular.route.IRouteProvider): void {
$routeProvider
.when("/dashboard", {
templateUrl: "/app/dashboard.html",
})
.otherwise("/dashboard");
}
Any help getting this working would be greatly appreciated.
I have found a solution by reading a blog post from https://legacy-to-the-edge.com/2015/01/21/using-es6-with-your-angularjs-project/
It was related to how I did the import for angular-route.
In the end it was just a one-liner change.
In boot.ts I changed the import statement from:
import * as angularroute from "angular-route";
to:
import "angular-route";
I can only assume, that the latter will also run scripts in the module file.
According to the spec on import at developer.mozilla.org
import "my-module";
Import an entire module for side effects only, without importing any bindings.
The fixed version is in github Angular1WithSystemJSFixed

$templatecache and $routeprovider - 404

I am using the grunt task grunt-angular-templates for precompiling angular templates in my app, which has resulted in output like that:
$templateCache.put('src/ng-app/views/story/page.html',
//...html
but this route is throwing a 404 on the template file
.when('/:pageId.aspx', {
templateUrl: 'src/ng-app/views/story/page.html',
I've seen another post about setting the ID of the template and specifying that in the route but I don't know how to do that for external files - the example uses inline templates like that:
<script type="text/ng-template" id="login.html">
login.html
</script>
Simple solution in the end - I had to ensure the view script generated by ngTemplates was included before route declarations (i have those in a separate script routes.js)
Its also key to ensure the module is the same in ngTemplates grunt:
var app = angular.module('MyApp.Web'...
ngtemplates: {
app: {
src: 'src/ng-app/views/**/*.html',
dest: 'dist/src/js/ng-views.js',
options: {
module: 'MyApp.Web'
}
}
}
Adding templates as a dependency made no discernible difference (as I found on another post):
angular.module('templates', []);
var app = angular.module('MyApp.Web', [
'ngRoute',
'youtube-embed',
'templates'
]);

Resources