I am using some starter kit for angular. There are webpack, eslint and other useful things.
But, I don't understand how to works with dependency. I have the following code:
import angular from 'angular';
import routing from './app.config';
import auth0 from 'auth0-js';
import jwtHelper from 'angular-jwt';
import store from 'angular-storage';
...
angular.module('app', [
uirouter,
...
jwtHelper,
store,
...
])
.config(routing)
.run(($state, auth, store, jwtHelper) => {
some code;
});
But, I get the following errors:
99:16 error 'auth' is already declared in the upper scope no-shadow
99:22 error 'store' is already declared in the upper scope no-shadow
99:29 error 'jwtHelper' is already declared in the upper scope no-shadow
Hot to use them properly?
Simply rename one of the duplicated declared variable so that your scope doesn't clutter.
You can either rename the upper part
import angularJwt from 'angular-jwt';
import angularStorage from 'angular-storage';
or simply rename the dependencies like: (and keep the original names in the $inject declaration)
routing.$inject = ['$urlRouterProvider', '$locationProvider', 'localStorageServiceProvider'];
export default function routing($urlRouterProviderCustomName, $locationProviderCustomName, localStorageServiceProviderCustomName) {
$locationProviderCustomName.html5Mode(true);
$urlRouterProviderCustomName.otherwise('/dash');
localStorageServiceProviderCustomName.setPrefix('elmahbucket');
}
Related
The case
I am in the process of upgrading an AngularJS (i.e. Angular 1.6) application to Angular (i.e. Angular 4.1.3). I chose to do the incremental upgrade so currently both AngularJS and Angular are bootstrapped and running. So far so good, but: One of the AngularJS services that should be rewritten to Angular relies on a well known service $translate which is part of the 3rd party AngularJS (read Angular 1) module pascalprecht.translate. In other words, $translate service is injected to MyService which should become an Angular (read Angular 4) service.
MyService (stripped down) looks like this:
import { Injectable } from '#angular/core';
#Injectable()
export class MyService {
// Here comes the injection (how to do it?).
constructor(private $translate: $translate) {
}
foo() {
// Use the service
this.$translate('HELLO_WORLD');
}
}
It is located within MyModule:
import { NgModule } from '#angular/core';
import { MyService } from './my.service';
#NgModule({
providers: [
MyService
]
})
export class MyModule {
}
The problem
Now, how can I inject $translate into MyService when MyService resides within an Angular module while $translate is part of a 3rd party AngularJS module?
I know how to inject an AngularJS service into an Angular service if the AngularJS service is located within the same module (or at least the module is part of my own solution). It is explained in the official docs. Is there any way to handle a 3rd party service? Do I need to register that service within MyModule's providers?
import { NgModule } from '#angular/core';
import { MyService } from './my.service';
// How this line should look line then?
import { $translate } from 'node_modules/angular-translate/...';
#NgModule({
providers: [
MyService,
$translate
]
})
export class MyModule {
}
Or am I trying to achieve impossible?
Well, after a few hours of struggling I've finally found the solution. Here it is:
Upgrade the 3rd party service
First of all, follow the Angular's official guidelines and upgrade the provider of the 3rd party service - $translate. Like this:
ajs-upgraded-providers.ts
import { InjectionToken } from '#angular/core';
import { downgradeInjectable } from '#angular/upgrade/static';
import 'angular-translate';
// Variable 'i' holds the standard AngularJS injector
export function $translateFactory(i: any) {
return i.get('$translate');
};
// There is no class representing the good old $translate service so we have
// a non-class dependency. Therefore we use an InjectionToken (Angular 4) or
// OpaqueToken (Angular 2).
export let $translate = new InjectionToken('$translate');
// Finally create the upgraded provider
export const $translateProvider = {
provide: $translate,
useFactory: $translateFactory,
deps: ['$injector']
};
One thing to notice, the $translate service might be dependent on other old AngularJS services like (in my case) $translateStaticFilesLoader or $translateMessageFormatInterpolation. If this is also your case, be sure to extend the code above and make upgraded providers for those services as well (keep them in the same file).
Make sure the import statement works
The angular-translate is installed as a node module so the statement
import 'angular-translate';
works just fine if your tsconfig.json is set up to use moduleResolution: "node".
Then, of course, you need to ensure that the import statement will work even after the code is transpiled from TypeScript to ES5 (or whichever target you use) and picked up by a module loader (SystemJS in my case).
Notice that we imported the angular-translate script without getting anything from it, just to cause side effects. Basically, the import ensures that the script containing the desired service $translate is simply executed and registers $translate service for the AngularJS $injector.
Register the upgraded provider in Angular module
Now ensure that the new upgraded provider of $translate service is registered among other providers of MyModule.
my.module.ts
import { NgModule } from '#angular/core';
import { MyService } from './my.service';
import { $translateProvider } from './ajs-upgraded-providers';
#NgModule({
providers: [
MyService,
$translateProvider
]
})
export class MyModule {
}
Use it
Finally, use the $translate service in MyService.
my.service.ts
import { Injectable, Inject } from '#angular/core';
import { $translate } from './ajs-upgraded-providers';
#Injectable()
export class MyService {
// Notice the usage of InjectionToken
constructor(#Inject($translate) private $translate: any) {
}
foo() {
this.$translate('hello.world').then((translation: string) => {
console.log(translation);
});
}
}
I am using TypeScript with AngularJS 1.5.x and in my tsconfig it all gets compiled down into SystemJS code. Now the issue I am having is only in controller classes that have an import at the top (see example below). If I take these imports out everything works just fine and the controller is found, but if I put them in, the controller is wrapped in a System.register block and it is not found when I go to my view.
I'm a beginner with SystemJS so I'm hoping I am just doing something really dumb, but it has been driving me nuts as to why when I have the imports it does not work.
import { testService} from '../services/test.service';
import { testClass} from '../models/testClass';
namespace app.controllers {
export class testController{
private testService: any;
constructor(public $http: angular.IHttpService) {
}
getData(): any {
}
}
angular.module('app')
.controller('testController', testController);
}
I've been trying to setup angular 1 with webpack and ran into an issue with import modules, let's say I want to import ramda from node_modules
import angular from 'angular';
import uirouter form 'angular-ui-router';
import routing from './app.config';
import * as ramda from 'ramda';
angular.module('app', [uirouter, ramda])
.config(routing);
I get an error: Argument 'module' is not a function, got Object. If I console ramda, it is indeed an object and I understand I need a string, but I just couldn't figure out how to get it?
Change it to import ramda from"ramda".
This is because the export of the module is the module name.
When you write import * as ramda you import the namespace object, which is an object containing all the exports of that module.
If you are using TypeScript, you may need to add a allowSyntheticDefaultImports to your tsconfig.json.
allowSyntheticDefaultImports
I'm trying to use angular-material with a ng-metadata project and I run into some issues.
I use DefinitelyTyped for angular material and the first lines are:
declare module 'angular-material' {
var _: string;
export = _;
}
In my main.ts I try to import { ngMaterial } from 'angular-material';
then bootstrap( AppComponent, [ 'ngMaterial' ] ); but all I got is:
Error:(3, 10) TS2305: Module ''angular-material'' has no exported member 'ngMaterial'.
I don't know what I am doing wrong
When being used through ES6 or TypeScript, a common pattern that Angular modules follow is that they'll use their name as the default export. For example, one of the modules in my application looks like this:
const session = angular.module("smSession", [])
.service("session", SessionService)
.component("smLogin", Login)
.config(routes)
.run(loginRedirect);
export default session.name;
The reasoning behind this is that it makes the syntax for declaring an Angular module's dependencies cleaner; for example:
import angular from "angular";
import ngAnimate from "angular-animate";
import ngMaterial from "angular-material";
import uiRouter from "angular-ui-router";
let module = angular.module("myApp", [ ngAnimate, ngMaterial, uiRouter ]);
If they instead exported the entire module, you'd have to do this:
let module = angular.module("myApp", [ ngAnimate.name, ngMaterial.name, uiRouter.name ]);
So this is why the main module declaration for angular-material looks like it does - they're simply representing the fact that all you can import from the package is that one string representing the module's name. The rest of the type definitions are ambient - you can just use the angular.material namespace anywhere in your application without having to do an import.
EDIT: To clarify, here's the actual source of the file that gets imported when you import ngMaterial:
// Should already be required, here for clarity
require('angular');
// Load Angular and dependent libs
require('angular-animate');
require('angular-aria');
// Now load Angular Material
require('./angular-material');
// Export namespace
module.exports = 'ngMaterial';
Notice that require('./angular-material') doesn't return anything - that import effectively just runs the file that sets up the Angular module behind the scenes (effectively the same sort of code as in my examples). The only thing being exported from the module is the name.
I've got a pretty serious problem. I'm trying to make steps toward ES6 imports and TypeScript in my Angular 1 application. But with angular injection many ES6 imports go unused. Here is an example:
Service-
export class MyService {
public doStuff() {}
}
Controller-
import {MyService} from './service';
export class MyController {
public constructor(private MyService: MyService) {MyService.doStuff();}
}
Note it does not matter if I rename the import using as.
The problem here is that the compiler doesn't think the MyService import is being used! So the resulting compiled systemjs code does not include it-
System.register('myController', [], function() { ... });
To get around this I could make the methods on MyService static and never inject it using angular. Ex:
import {MyService} from './service';
export class MyController {
public constructor() {MyService.doStuff();}
}
But we don't have the time to do that. We are trying to do this refactor in steps, and while that's the ultimate goal, we don't have time for that at the moment.
How do I force systemjs to include these?
From the TypeScript Handbook
Import a module for side-effects only
Though not recommended practice, some modules set up some global state that can be used by other modules. These modules may not have any exports, or the consumer is not interested in any of their exports. To import these modules, use:
import "./my-module.js";
The correct way to handle this is to register the service with the angular.service before application startup. TypeScript is eliding the import because it is only used in type position, not in value position. This is not specific to "module": "system", but applies to all external module targets.
Here is what I have used successfully in a number of production apps
my-service.ts
export class MyService {
doStuff() {}
}
my-controller.ts
import {MyService} from './service';
export class MyController {
static $inject = ['MyService'];
constructor(private myService: MyService) {
myService.doStuff();
}
}
my-app.ts
import angular from 'angular';
import {MyController} from './my-controller';
import {MyService} from './my-service';
const app = angular.module('app', []);
app.controller({ MyController });
app.service({ MyService });
export function bootstrap(target: Document | Element) {
angular.bootstrap(target, ['app'], { ngStrictDi: true });
}
To explain, the service itself needs to be registered with an angular module in order to be injected in the first place, so the elided import is a non issue since the registration code, shown in my-app.ts, uses MyService in value position when passing it as an argument to angular.service.
A nice pattern is to register all of the services that comprise a certain set of functionality in a single place. The logical place to do this is in the file containing the angular module which will hold the services.
Also note the static $inject = ['MyService']; this ensures your controller code is minification safe while allowing you to co-locate the DI annotation with the constructor that requires it.
I have found this pattern scales very well.