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

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.

Related

Correct way to load AngularJS templates for Webpack 4?

So far I've been able to bundle up all our controllers/directives/services into 1 script and I also bundled all the vendor scripts into another script file. Now I'm having trouble figuring out what the right way to load the AngularJS template files are. We are currently using Grunt and just copying the exact folder structure over to the dist folder but clearly this won't work in Webpack. Here is an example of our project structure(from the John Papa style guide)
My project inside the dist folder is currently rendered as follows:
Anybody have any input?
AngularJS templates are html files, so you need to add some loader for handling them.
You have multiple options, bundle those html files into the js bundle, using html-loader, pros of this approach is that your app won't make ajax call for the template, cons, your js bundle size will become large.
This will allow you to "require" your html template inside your controllers.
copy those raw files using copy-webpack-plugin, this way it will work the same way it works with Grunt (providing templateUrl path to the file that was copied).
In-order to be specific regarding lazy-loaded files you can attach .lazy.html suffix.
Then, enable file-loader on the .lazy.html files & assign it to templateUrl, and the regular once use with template: require('template.html').
As of best practice, I would "require" critical templates so they will be in the js bundle, and lazy load (via templateUrl) non-critical ones.
This is an example of webpack.config.js file:
module.exports = {
module: {
rules: [
{
test: /\.lazy\.html$/,
use: [
{
loader: 'file-loader',
options: {},
},
],
},
{
test: /\.html$/,
exclude: /\.lazy\.html$/
use: [
{
loader: 'html-loader',
options: {
minimize: true,
},
},
],
},
],
},
};
// critical.component.js
angular.
module('myApp').
component('greetUser', {
template: require('critical.html'),
controller: function GreetUserController() {
this.user = 'world';
}
});
// none-critical.component.js
angular.
module('myApp').
component('greetUser', {
templateUrl: require('non-critical.lazy.html'),
controller: function GreetUserController() {
this.user = 'world';
}
});

Angular "Module is not available" when switching to webpack

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.

How to use react as a component library rather than application

I'm trying to learn react and also introduce it in already existing project. Thus I want to use react in a few places where it makes sense, instead of using it everywhere.
The problem is the entry-point concept that I can't wrap my head around. I'd prefer to have:
ReactDOM.render(
<MyComponent/>,
document.getElementById("componentExample")
);
inside my cshtml near <div id='componentExample'></div> rather than in a separate jsx. I understand that I can't use jsx syntax in cshtml, but if I write
<script src="#Url.Content("~/Scripts/dist/bundle.js")"></script>
<script>
ReactDOM.render(
React.createElement(MyComponent, {}, null), document.getElementById("componentExample")
);
</script>
This doesn't really work because MyComponent is undefined. It is included in my bundle.js (I had to add it as entry point), but it is inside the scope of some webpack stuff and is not exposed.
What is the proper way of doing this?
My goal is to write some components for my app where it is more suitable and use it here and there, without completely going into react-based architecture.
My setup is webpack + babel polyfill + typescript + react and this is in ASP.NET MVC.
My webpack.config.js is:
var config = {
entry: ['babel-polyfill', "./SiteScripts/React/index.tsx", "./SiteScripts/React/Components/MyComponent.tsx"],
output: {
filename: "./Scripts/dist/bundle.js",
},
// Enable sourcemaps for debugging webpack's output.
devtool: "source-map",
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
},
module: {
rules: [
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{
enforce: 'pre',
test: /\.js$/,
loader: "source-map-loader"
},
// All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'.
{
test: /\.tsx?$/, loaders: ['babel-loader', "ts-loader"], exclude: /node_modules/
}
]
},
plugins : []
}
if (process.env.NODE_ENV === "production") {
config.devtool = "cheap-module-source-map";
}
module.exports = config;
Also this is somewhat related to the question (as question about architecture): I'd like to make my bundle.js smaller - it is 30k lines now. It is probably because I'm trying to use react in a wrong way?
I finally found the problem. I had a few mistakes (and didn't completely understand the whole theory behind webpack) and the answer is definetely beyound the code I gave in the question - so I do not have any answers.
First I had to make by bundle a library, in order to export stuff defined in it (webpack.config.js):
entry: ['babel-polyfill', "./SiteScripts/React/index.tsx"],
output: {
filename: "./Scripts/dist/bundle.js",
libraryTarget: "var",
library: "WebPack"
},
This will make types/variables defined in index.tsx (I think only last file from entry list is taken) and that are exported - available via WebPack.<name>
I needed to export my component. I've done it like this - but there are probably better ways (index.tsx):
import { MyComponent} from "./Components/mycomponent";
export var myComponent = MyComponent;
I also needed to ensure all my subcomponents are modules. This means using import everywhere, and adding a keyword export to all element, props and states.
After that I was able to render my component from:
<script src="#Url.Content("~/Scripts/dist/bundle.js")"></script>
<script>
ReactDOM.render(
React.createElement(WebPack.myComponent,
{
PropA = 2, PropB = "3"
}, null),
document.getElementById("componentExample")
);
</script>
One more thing to watch for if I am doing this is that the line <script src="#... does not happen more than once in my result html (this is very likely as I am using many components embedded in a single page). I use this javascript function to ensure I load bundle.js only once:
function loadJsOnce(url, whenFinish) {
if (!window.loadedScripts) {
window.loadedScripts = {};
}
if (!window.loadedScripts[url]) {
var script = document.createElement('script');
//script.async = true; // remove this if you don't want the script to be async
script.src = url;
script.onload = function() {
whenFinish();
}
document.getElementsByTagName('head')[0].appendChild(script);
window.loadedScripts[url] = true;
} else {
whenFinish();
}
}
In url I specify the path to bundle.js and in whenFinish function I put a function that calls ReactDOM.render(... stuff

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>

import CSS and JS files using Webpack

I have a directory structure like this:
and inside node_modules:
>node_modules
>./bin
>webpack.config.js
>bootstrap
>bootstrap.css
>bootstrap.js
I need to generate separate CSS and JS bundles like this:
custom-styles.css, custom-js.js, style-libs.css, js-libs.js
where style-libs and js-libs should contain syles and js files of all libraries like bootstrap and jQuery. Here's what I have done so far:
webpack.config.js:
const path = require('path');
const basedir = path.join(__dirname, '../../client');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const stylesPath = path.join(__dirname, '../bootstrap/dist/css');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
watch: true,
// Script to bundle using webpack
entry: path.join(basedir, 'src', 'Client.js'),
// Output directory and bundled file
output: {
path: path.join(basedir, 'dist'),
filename: 'app.js'
},
// Configure module loaders (for JS ES6, JSX, etc.)
module: {
// Babel loader for JS(X) files, presets configured in .babelrc
loaders: [
{
test: /\.jsx?$/,
loader: 'babel',
babelrc: false,
query: {
presets: ["es2015", "stage-0", "react"],
cacheDirectory: true // TODO: only on development
}
},
{
test: /\.css$/,
loader: ExtractTextPlugin.extract("style-loader", "css-loader")
},
]
},
// Set plugins (for index.html, optimizations, etc.)
plugins: [
// Generate index.html
new HtmlWebpackPlugin({
template: path.join(basedir, 'src', 'index.html'),
filename: 'index.html'
}),
new ExtractTextPlugin(stylesPath + "/bootstrap.css", {
allChunks: true,
})
]
};
Client.js
import * as p from 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.jsx';
ReactDOM.render(<App />, document.getElementById('app'));
I am able to run the app and render all of the components correctly except loading the external JS and CSS file using webpack.
I'm not much experienced with webpack and find it really difficult it wrap my hear around it. There're are a few simple questions:
1- Is this configuration correct? If yes, then how can I include my CSS and JS files in components using ES6. Something like import keyword.
2- Should I even be using webpack for CSS files?
3- How to specify individual directories for input and their respective output files in webpack? Something like all-custom.js should be output for custom1.js and custom2.js?
I know these are some very basic question and I tried Google but didn't find a single tutorial for Webpack that is simple and targets beginners.
After playing out with Webpack in multiple projects, I figured out how Webpack loads stuff. Since the question is still unanswered, I decided to do it myself for anybody with same need.
Directory structure
->assets
->css
->my-style-1.css //custom styling file 1
->my-style-2.css //custom styling file 2
->src
->app
->app.js
->variables.js
->libs.js //require all js libraries here
->styles-custom.js //require all custom css files here
->styles-libs.js //require all style libraries here
->node_modules
->index.html
->package.json
->webpack.config.js
Bundle 1 (main code of app)
app.js: assuming this is main file and app starts from here
var msgs = require('./variables');
//similarly import other js files you need in this bundle
//your application code here...
document.getElementById('heading').innerText = msgs.foo;
document.getElementById('sub-heading').innerText = msgs.bar;
Bundle 2 (js modules)
libs.js: this file will require all modules needed
require('bootstrap');
//similarly import other js libraries you need in this bundle
Bundle 3 (external css files)
styles-libs.js: this file will require all external css files
require('bootstrap/dist/css/bootstrap.css');
//similarly import other css libraries you need in this bundle
Bundle 4 (custom css files)
styles-custom.js: this file will require all custom css files
require('../assets/css/my-style-1.css');
require('../assets/css/my-style-2.css');
//similarly import other css files you need in this bundle
webpack.config.js
const path = require('path');
const webpack = require('webpack');
const extractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: {
'app': './src/app/app.js', //specifying bundle with custom js files
'libs': './src/libs.js', //specifying bundle with js libraries
'styles-custom': './src/styles-custom.js', //specifying bundle with custom css files
'styles-libs': './src/styles-libs.js', //specifying bundle with css libraries
},
module: {
loaders: [
//used for loading css files
{
test: /\.css$/,
loader: extractTextPlugin.extract({ fallbackLoader: 'style-loader', loader: 'css-loader?sourceMap' })
},
//used for loading fonts and images
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'file-loader?name=assets/[name].[hash].[ext]'
}
]
},
output: {
path: path.resolve(__dirname, 'dist'), //directory for output files
filename: '[name].js' //using [name] will create a bundle with same file name as source
},
plugins: [
new extractTextPlugin('[name].css'), //is used for generating css file bundles
//use this for adding jquery
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jQuery'
})
]
}
index.html
<head>
<link rel="stylesheet" href="dist/styles-libs.css" />
<link rel="stylesheet" href="dist/styles-custom.css" />
</head>
<body>
<h2 id="heading"></h2>
<h3>
<label id="sub-heading" class="label label-info"></label>
</h3>
<script src="dist/libs.js"></script>
<script src="dist/app.js"></script>
</body>
You can include css & JS files using import in es6 in the source files in your project. example:
import './style.css';
import Style from './path/style.js';
NB. Generally You need to code in es5 in webpack.config.js file. If you want to use es6 just follow the link How can I use ES6 in webpack.config.js?
You can use https://github.com/webpack/css-loader for CSS configuration.
You can use code splitting in webpack and specify multiple entry point but that will generate multiple output files. Have a look at multiple entry point section of that following link.
https://webpack.github.io/docs/code-splitting.html

Resources