I'm trying to setup restangular for a SPA project but I'm unable to resolve it as a dependency with grunt-tsng. I've installed it using bower (and tsd for typescript definitions from definitelyTyped).
grunt task configuration
tsng: {
options: {
extension: ".ng.ts"
},
dev: {
files: [
// TODO: Automate the generation of this config based on convention
{
src: ["Client/app/**/*.ts", "!**/*.ng.ts"],
dest: "Client/app"
}
]
}
},
Module app.ts
/// <reference path="../../typings/tsd.d.ts" />
module App {
var dependencies = [
"ui.router",
"ui.bootstrap",
"restangular"
...
];
function configuration(
$stateProvider: ng.ui.IStateProvider,
$urlRouterProvider: ng.ui.IUrlRouterProvider,
RestangularProvider: restangular.IProvider, <-- here it fails
...
ts.d.ts (reference file)
/// <reference path="restangular/restangular.d.ts" />
grunt error
Running "tsng:dev" (tsng) task
Warning: Error: Can't resolve dependency for module function App.config with name restangular.IProvider Use --force to continue.
Other dependencies, such as ui-router, ui-bootstrap etc. are working fine. What could be the cause of this? Are there any incompatibilities known with grunt-tsng?
Alright, I figured it out.
When executing grunt-tsng, it scans all dependencies/modules etc. to automagically hook them into angular so you don't have to do it yourself. Here two simple rules apply: Dependencies to inject that are your own (e.g. where you have typescript inplementation code present, such as classes in your modules) can be defined simply with their name. E.g.:
constructor(service: MyModule.IMyService) {}
External dependencies, e.g. from angular itself are declared with the '$' notation. grunt-tsng will assume their dependencies itself are resolved at runtime and thus just hooks them up with their name:
constructor($routeProvider: ng.IRouteService) {}
But now services in restangular aren't named '$restangular', but just 'restangular'. This leads to the following:
When declaring it as 'restangular', grunt-tsng will look for implementations in your modules, doesn't find any and fails
When declaring it as '$restangular', angular will look for providers called '$restangular', doesn't find any and fails
Conclusion:
This is retarded.
Related
I searched a lot of posts and also the official Angular documentation, but I'm not able to get an AngularJS service running in Angular. I finally came to this page https://angular.io/api/upgrade/static/UpgradeModule#examples which seems to explain exactly what I need, but when doing all those steps I'm getting:
ERROR Error: Trying to get the AngularJS injector before it being set.
My impression is that this example is not quite complete. E.g. there is no hint were the (old) AngularJS framework must be loaded. My service looks like angular.module('my-module').service('my-service', ... thus angular needs to be defined, otherwise I'm getting an error. Furthermore many examples assume that the AngularJS code is written in TypeScript. In my case this is not true (just plain Javascript).
Unfortunately with Angular 9 there is an additional issue with the #angular/upgrade module which is not mentioned anywhere and can only be solved by disabling the new Ivy compiler in tsconfig.app.json, otherwise the compiler will throw Error: Error on worker #1: Error: getInternalNameOfClass() called on a non-ES5 class: expected UpgradeComponent to have an inner class declaration:
"angularCompilerOptions": {
"enableIvy": false
}
I'd really appreciate if somebody could post a complete example on what exactly must be done in order to run an AngularJS service in an Angular component.
UPDATE [6th July 2020]
Here you can find a GitHub repo which you can clone, to reproduce the behavior: https://github.com/berkon/angularjs-service-upgrade-test. I should also mention that I'm using the Electron framework and started based on this repo https://github.com/maximegris/angular-electron but I guess that shouldn't matter in this case.
Finally I got it working! It was really really cumbersome to figure this all out. A lot of things aren't mentioned in most tutorials and even in the official Angular guide there are only code snippets which make it hard for Angular newbies to guess where to put all that stuff. Also the bootstraping is not explained correctly. Furthermore all tutorials assume that the "old" AngularJS code is already written in TypeScript, which makes it even harder to find the right way/order to load/bootstrap/import all that stuff. Finally there seems to be an issue with the #angular/upgrade module in combination with the new Ivy compiler in Angular 9. It throws the error mentioned below. Thus it must be disabled to get things working. A real pain!!!
So roughly these are the steps:
install the angular and #angular/upgrade node modules
load all .js modules including AngularJS in the script section of angular.json
interrupt the regular Angular bootstrap process by removing the bootstrap section from #NgModule and bootstrap AngularJS via ngDoBootstrap manually. First bootstrap
AngularJS, afterwards bootstrap the AppComponent class. This way the service is available at AppComponent initialization. Otherwise you'll get an injection error!
Add a new provider in providers [] section to get access to the new service
Now the new (upgraded) service can be injected in the constructor of AppComponent
Its quite a lot of work to perform all steps below manually, but I listed them for reference. Here you can find a GitHub repo where you can clone a working app. Don't be surprised! This repo uses the Electron framework (electronjs.org). But don't worry this doesn't have any influence on my findings: https://github.com/berkon/angularjs-service-upgrade-test
And here is the step-by-step guide:
Prerequistes
execute npm install angular --save
execute npm install #angular/upgrade --save
in tsconfig.app.json add "enableIvy": false to angularCompilerOptions to avoid getting:
Error: getInternalNameOfClass() called on a non-ES5 class: expected UpgradeComponent to have an inner class declaration
add "node_modules/angular/angular.js" and the Javascript file which contains your AngularJS service (in this case "src/app/angular-js-service.js") to the scripts [] array in angular.json
app.module.ts
add ApplicationRef to the import brackets of #angular/core
add import { UpgradeModule } from '#angular/upgrade/static'
add UpgradeModule to imports [] array of #NgModule
remove bootstrap section completely from #NgModule and replace it with this: entryComponents: [AppComponent]
add this to the providers [] array in #NgModule and make sure to replace myService with the correct name of your service:
{ provide: 'myService', useFactory: (i: any) => { return i.get('myService') }, deps: ['$injector'] }
replace the constructor of AppModule with this:
constructor ( public upgradeModule: UpgradeModule ) {}
add this to the AppModule class and make sure to replace ajsAppModule with the name of your AngularJS main app module:
ngDoBootstrap ( appRef: ApplicationRef ) {
this.upgradeModule.bootstrap(document.body, ['ajsAppModule'], { strictDi: true } )
appRef.bootstrap ( AppComponent )
}
app.component.ts
add Inject to the import brackets at #angular/core
in the AppComponent class change the constructor to this and make sure to replace myService with the name of your AngularJS service
constructor ( #Inject('myService') myService: any ) {
myService.doSomething()
}
I had this same error and I solved it in my app, however I cannot remember exactly why this was happening (sorry, it was a long time ago). I wasn't upgrading services, instead I was downgrading.
Here's my app.module.ts I've added comments to the parts that were critical to get this working, I hope there may be a hint for you here. Note that I used the Angular CLI to generate the app.
setAngularJSGlobal(angular);
// Configure the angularjs app (yours might be defined elsewhere)
const app = angular.module('app', [MyFormsModule, AngularMaterialModule]);
app.run(RunAddressAutocompleteConfig);
app.run(RunDynamicQueryConfig);
// Downgrade Angular AppComponent so AngularJS can render it after bootstrapping
// my app used an Angular component as the root
app.directive('appRoot', downgradeComponent({ component: AppComponent }));
// Downgrade Angular services
app.factory('api', downgradeInjectable(ApiService));
app.factory('dynamicQuery', downgradeInjectable(DynamicQueryService));
#NgModule({
declarations: [
AppComponent,
FormDirective,
FormPageComponent,
FormsListPageComponent,
RouterLinkPreserveQueryParamsDirective,
FormEmptyStatePageComponent,
],
imports: [BrowserModule, UpgradeModule, AppRoutingModule, HttpClientModule, CommonModule],
// This was absolutely necessary for bootstrapping my app in this way
// I encountered errors otherwise
providers: [
{
provide: '$scope',
useExisting: '$rootScope',
},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
entryComponents: [AppComponent],
})
export class AppModule implements DoBootstrap {
constructor(private readonly upgrade: UpgradeModule) {}
ngDoBootstrap(appRef: ApplicationRef) {
this.upgrade.bootstrap(document.body, [app.name], { strictDi: true });
appRef.bootstrap(AppComponent);
}
}
index.html
<body>
<app-root></app-root>
</body>
I am integrating Require.js to AngularJS based web application for performance improvement.
I've imported require.conf in index.html:
<script src="bower_components/requirejs/require.js" data-main="require-conf.js">
Here is code snippet of require-conf.js:
require.config({
paths: {
'jquery': '...',
'Angular': '...',
....
'libs' : 'src/libs.js'
},
shim: {
'Angular': { exports: 'Angular'},
'libs' : ['Angular']
....
}
}
require(
[
'jquery',
'angular',
'app',
'libs',
], function (jquery, angular) {
angular.bootstrap(['app'])
}
);
Here, libs.js is the library I've built by webpack. Some plugins and libraries are concatenated in this file.
Here is webpack configuration code snippet to build libs.js.
In webpack.config.js
plugins: [
new ConcatPlugin({
fileName: 'libs.js',
filesToConcat: [
'./src/utils/bootstrap-plugins.min.js',
'./src/libs/angular-bootstrap-datetimepicker/datetimepicker.js',
'./src/libs/angular-bootstrap-datetimepicker/datetimepicker.templates.js',
'./src/libs/angular-fusioncharts/fusioncharts.js',
'./src/libs/angular-fusioncharts/angular-fusioncharts.min.js',
'./src/libs/angular-fusioncharts/types/fusioncharts.charts.js',
'./src/libs/angular-ui-tour/angular-ui-tour.js',
'./src/libs/JQ_CONFIG/flot/jquery.flot.js',
'./src/libs/JQ_CONFIG/flot/jquery.flot.pie.js',
'./src/libs/JQ_CONFIG/flot/jquery.flot.resize.js',
'./src/libs/JQ_CONFIG/flot-spline/js/jquery.flot.spline.min.js',
'./src/libs/JQ_CONFIG/flot.orderbars/js/jquery.flot.orderBars.js',
'./src/libs/JQ_CONFIG/flot.tooltip/js/jquery.flot.tooltip.min.js',
'./src/libs/JQ_CONFIG/footable/dist/footable.all.min.js',
'./src/libs/JQ_CONFIG/html5sortable/jquery.sortable.js',
'./src/libs/jquery-ui-draggable/jquery-ui-draggable.min.js',
'./src/libs/ng-table/ng-table.js',
'./src/libs/StickyTableHeaders/jquery.stickytableheaders.js',
'./src/libs/ng-quill/quill.js',
'./src/libs/ng-quill/ng-quill.js',
].map(function(fileName) {
return path.resolve(__dirname, fileName);
}),
But, App can't recognize the modules inside libs.js:
Module 'ngquill' is not available! you either misspelled the module
name or forgot it to load it. If registering a module ensure that you
specify the dependencies as the second argument.
Require.js can't recognize the modules concatenated by webpack? Is there any solutions to fix this problem?
You cannot combine AMD modules into a single file just by concatenating them into a single file. When you combine multiple modules into a single file, the modules must get hardcoded names. When you have a single module in a single file, the define for it can be:
define([ ... deps ... ], function (...) {
In this case, RequireJS infers the name of the module from the name under which it was requested.
When you combine multiple modules in a single file, the define calls must be of the form:
define("foo", [ ... deps ...], function (...) {
define("bar", [ ... deps ...], function (...) {
// etc.
The first argument to define is a string, which tells RequireJS which module is being defined. This is necessary because otherwise RequireJS won't know which module is which. This is why you cannot just concatenate.
You most likely could write a Webpack configuration that could both transform the files as I described above and concatenate them. However, that's rife with hurdles. For instance the runtime shim configuration require special handling at build time. In the end you may end up replicating the functionality of RequireJS' optimizer. I would suggest using RequireJS' optimizer instead of reinventing the wheel.
I'm using yeoman generator for scaffolding angular web application with requirejs. Its working fine but when I tried to concat and minifying all the js file into a single file through grunt task runner its started giving me above mentioned error. I've researched online about the issue and common solution is I may be mis-spelled any service injecting in the module or service does not exists, I've cross checked again all the spelling, quotation marks etc everything seems fine but still I'm unable to resolve this issue.
Here is my app.js file where my main module with dependencies is listed.
return angular
.module('arteciateYeomanApp', [
'arteciateYeomanApp.controllers.MainCtrl',
'arteciateYeomanApp.controllers.AboutCtrl',
'arteciateYeomanApp.services.Xhr',
'arteciateYeomanApp.services.Common',
'arteciateYeomanApp.controllers.ArtworkCtrl',
'arteciateYeomanApp.controllers.AddAccountCtrl',
'arteciateYeomanApp.controllers.AddArtgroupCtrl',
'arteciateYeomanApp.controllers.AddArtistCtrl',
'arteciateYeomanApp.controllers.AddArtworkCtrl',
'arteciateYeomanApp.controllers.AddCampaignsCtrl',
'arteciateYeomanApp.controllers.AddGenreCtrl',
'arteciateYeomanApp.controllers.AddInstitutionCtrl',
'arteciateYeomanApp.controllers.AdminSignupCtrl',
'arteciateYeomanApp.controllers.ArtistInfoCtrl',
'arteciateYeomanApp.controllers.DirectUserSignupCtrl',
'arteciateYeomanApp.controllers.ErrorCtrl',
'arteciateYeomanApp.controllers.ForgotPasswordCtrl',
'arteciateYeomanApp.controllers.GroupBuyingCtrl',
'arteciateYeomanApp.controllers.LoginCtrl',
'arteciateYeomanApp.controllers.AdminLoginCtrl',
'arteciateYeomanApp.controllers.ResetPasswordCtrl',
'arteciateYeomanApp.controllers.SignupCtrl',
'arteciateYeomanApp.controllers.UnblockUserCtrl',
'arteciateYeomanApp.controllers.UpdatePasswordCtrl',
'arteciateYeomanApp.controllers.DashboardCtrl',
'ngRoute','ngResource']).config(.....);
here is grunt task which I'm running for minifying the js files.
registering task
grunt.registerTask('dev', ['requirejs' ]);
Here is task running script
requirejs : {
compile : {
options : {
baseUrl : "<%= yeoman.app %>/scripts",
mainConfigFile : "<%= yeoman.app %>/scripts/main.js",
name : "main",
out : "requireArterciate.js"
}
}
}
Please let me know if I'm doing something wrong here.
If you need to minify the angularjs code, then use the following standard format syntax to define the controller and to inject the dependencies. Refer Dependency Injection
angular.module('test').controller('testController', testController);
testController.$inject = ['$scope', '$rootScope'];
function testController($scope, $rootScope) {};
I have a very simple jasmine unit test that targets testing a simple operation on an AngularJS controller as follows:
/// <reference path="../src/jasmine.js"/>
/// <reference path="../../Scripts/angular.js"/>
/// <reference path="../../Scripts/angular-mocks.js"/>
/// <reference path="../../Scripts/angular-ui-router.js"/>
/// <reference path="../../Scripts/angular-route.js"/>
/// <reference path="../../Scripts/angular-resource.js"/>
/// <reference path="../../Scripts/angular-ui/ui-bootstrap.js"/>
/// <reference path="../../app/app.js"/>
/// <reference path="../../app/controllers/myController.js"/>
describe("my Controller", function () {
var scope, ctrl, vm;
beforeEach(module("app"));
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
ctrl = $controller(MyController, { $scope: scope });
vm = ctrl;
}));
it("sets the title", function() {
expect(vm.title).toBe("This is the title");
});
});
To begin the above test passes when using either of the following:
Open SpecRunner.html which contains the same references and displays a passed unit test in the jasmine default test runner
Test without modification passes if running via ReSharper test runner (this tool is not important other than to show the test passes using another test runner; the test isn't the culprit)
However no matter whenever I do any of the following using Chutzpah the test fails:
Run from the command line using chutzpah.console.exe
Use the right-click menu open in VS.NET to Run JS Tests
Each time the following error is given:
Error: [$injector:modulerr] Failed to instantiate module
app due to: [$injector:nomod] Module
'app' 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.
So this is a very typical error if the AngularJS references are not included. However as you can see above they are for sure included and the other test runners see them and the test passes. The error is identical regardless if I run the unit test from the command line or if I view the output in VS.NET.
I even turned on the /trace parameter from the command line and viewed the chutzpah.log file. It sure enough is finding the Angular reference files so I'm not sure why the test is failing.
I've seen a ton of posts surrounding this and the solution is always that the references are missing. That's not the case here and I'm stuck. What am I missing to make this test pass using Chutzpah?
I recommend using a chutzpah.json settings file for angular projects. I placed mine at the root of my project, next to the web.config. You can specify the dependencies in one place this way, which is very helpful as the angular project grows rapidly. I can't know for sure, but I think yours would look something like the following:
{
"Framework": "jasmine",
"FrameworkVersion": "1",
"References": [
{ "Path": "./Scripts/angular/angular.js" },
{
"Path": "./Scripts/angular",
"Includes": [ "*.js" ]
},
{ "Path": "./Scripts/angular-ui/ui-bootstrap.js" },
{ "Path": "./app/app.js" },
{
"Path": "./app",
"Includes": [ "*.js" ]
}
],
"Tests": [
{ "Includes": [ "*.spec.js" ] }
]
}
This will run on jasmine version 1. I'm not sure how you It will include everything that matches "./Scripts/angular*.js" and everything that matches "./app/*.js" (including sub-directories). As you add new angular references and app modules, you should not need to modify this configuration (once you get it working).
I believe through thorough testing I have determined the cause. The issue is most likely that I'm using ES6 classes in my Angular .js files being used in my tests.
The issue is PhantomJS which Chutzpah uses does not support ES6 and the class keyword and thus it fails (see this for validation). I was able to reproduce in a non Angular sample. I used an old fashioned ES5 style IIFE and the simple test now passed. This is why it worked in Chrome and in ReSharper which used Chrome as the default test browser and Chrome supports the ES6 class keyword. If I make ReSharper use PhantomJS instead of Chrome, then my simple test also fails.
The root problem appears to be PhantomJS and its lack of support for ES6 syntax.
Here is a blog post I wrote on my findings after pulling my hair out on this for a couple of weeks. It explains more in detail about the issues with writing non-ES5 complaint code and using that with Chutzpah and PhantomJS:
Chutzpah and non-ES5 JavaScript for Unit Testing is Problematic
I'm exploring the idea of using Webpack with Backbone.js.
I've followed the quick start guide and has a general idea of how Webpack works, but I'm unclear on how to load dependency library like jquery / backbone / underscore.
Should they be loaded externally with <script> or is this something Webpack can handle like RequireJS's shim?
According to the webpack doc: shimming modules, ProvidePlugin and externals seem to be related to this (so is bundle! loader somewhere) but I cannot figure out when to use which.
Thanks
It's both possible: You can include libraries with a <script> (i. e. to use a library from a CDN) or include them into the generated bundle.
If you load it via <script> tag, you can use the externals option to allow to write require(...) in your modules.
Example with library from CDN:
<script src="https://code.jquery.com/jquery-git2.min.js"></script>
// the artifial module "jquery" exports the global var "jQuery"
externals: { jquery: "jQuery" }
// inside any module
var $ = require("jquery");
Example with library included in bundle:
copy `jquery-git2.min.js` to your local filesystem
// make "jquery" resolve to your local copy of the library
// i. e. through the resolve.alias option
resolve: { alias: { jquery: "/path/to/jquery-git2.min.js" } }
// inside any module
var $ = require("jquery");
The ProvidePlugin can map modules to (free) variables. So you could define: "Every time I use the (free) variable xyz inside a module you (webpack) should set xyz to require("abc")."
Example without ProvidePlugin:
// You need to require underscore before you can use it
var _ = require("underscore");
_.size(...);
Example with ProvidePlugin:
plugins: [
new webpack.ProvidePlugin({
"_": "underscore"
})
]
// If you use "_", underscore is automatically required
_.size(...)
Summary:
Library from CDN: Use <script> tag and externals option
Library from filesystem: Include the library in the bundle.
(Maybe modify resolve options to find the library)
externals: Make global vars available as module
ProvidePlugin: Make modules available as free variables inside modules
Something cool to note is that if you use the ProvidePlugin in combination with the externals property it will allow you to have jQuery passed into your webpack module closure without having to explicitly require it. This can be useful for refactoring legacy code with a lot of different files referencing $.
//webpack.config.js
module.exports = {
entry: './index.js',
output: {
filename: '[name].js'
},
externals: {
jquery: 'jQuery'
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
})
]
};
now in index.js
console.log(typeof $ === 'function');
will have a compiled output with something like below passed into the webpackBootstrap closure:
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
/* WEBPACK VAR INJECTION */(function($) {
console.log(typeof $ === 'function');
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1)))
/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {
module.exports = jQuery;
/***/ }
/******/ ])
Therefore, you can see that $ is referencing the global/window jQuery from the CDN, but is being passed into the closure. I'm not sure if this is intended functionality or a lucky hack but it seems to work well for my use case.
I know this is an old post but thought it would be useful to mention that the webpack script loader may be useful in this case as well. From the webpack docs:
"script: Executes a JavaScript file once in global context (like in script tag), requires are not parsed."
http://webpack.github.io/docs/list-of-loaders.html
https://github.com/webpack/script-loader
I have found this particularly useful when migrating older build processes that concat JS vendor files and app files together. A word of warning is that the script loader seems only to work through overloading require() and doesn't work as far as I can tell by being specified within a webpack.config file. Although, many argue that overloading require is bad practice, it can be quite useful for concating vendor and app script in one bundle, and at the same time exposing JS Globals that don't have to be shimmed into addition webpack bundles. For example:
require('script!jquery-cookie/jquery.cookie');
require('script!history.js/scripts/bundled-uncompressed/html4+html5/jquery.history');
require('script!momentjs');
require('./scripts/main.js');
This would make $.cookie, History, and moment globally available inside and outside of this bundle, and bundle these vendor libs with the main.js script and all it's required files.
Also, useful with this technique is:
resolve: {
extensions: ["", ".js"],
modulesDirectories: ['node_modules', 'bower_components']
},
plugins: [
new webpack.ResolverPlugin(
new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin("bower.json", ["main"])
)
]
which is using Bower, will look at the main file in each required libraries package.json. In the above example, History.js doesn't have a main file specified, so the path to the file is necessary.