I'm not sure if an issue is the right place to put this, but we've had a lot of success using ngimport as an alternative to Angular 1's DI, to overcome some problems that come up when interoperating Angular 1 with other frameworks, as well as with TypeScript.
Here's the lib: https://github.com/bcherny/ngimport
I've reached out to @petebacondarwin about this a while ago and got some good feedback. I'm curious what the Angular maintainers think.
First of all, great you've been working on anything related to Angular(JS)!
I'm no maintainer and nor do I want to sound rude with questioning it but as I'm using TypeScript on a large scale AngularJS 1.6 project I'm wondering what the problem with its current DI system is (okay, I know it's downsides, but with regard to your remarks on the README.md I wanted to ask the following).
What is ugly about using angularjs's DI system?
class SomeController {
constructor(
private $http: angular.IHttpService
) {}
$onInit() { this.$http.get(...); }
$onChanges() { ... }
}
export class SomeComponent implements angular.IComponentOptions {
bindings: any;
controller: any;
templateUrl: string;
constructor() {
this.bindings = {
someInput: '<',
someOutput: '&'
};
this.template = require('./my.component.html');
this.controller = MyController;
}
}
No lock-in: easy migration path to Angular~2~, React, etc.
Angular uses Constructor injection. Not inject dependencies in the constructor sounds more work when it comes to migration. (No experience with this what so ever).
Avoid duplicated TypeScript interface declarations.
Could you elaborate? Maybe provide a sample?
I'm still trying to understand the need for anything like ngImport. It's not 100% clear which issues ngImport is trying to fix.
Edit: If I get this straight, all ngImport does is export the angular services in a single file, making them exportable? Does anything that uses tree shaking likes this approach at all?
Hi, usually new projects are better discussed in the mailing list etc.
I personally know that the Angular module system makes you write more stuff when used with ES6 / Typescript, but it's not the end of the world. If the project gets traction, we can include it in our external resources guide, but apart from that I don't see something actionable here. Do you think we should incorporate this into AngularJS core?
@Narretz Which stuff are you talking about that makes u write more stuff when working with AngularJS + ES6 / TypeScript? Do you have any examples?
I haven't found anything which would make me not use TS with AngularJS or make me realize I have to do quite some extra work (been using TS + AngularJS for 2 years now).
@frederikprijck
What is ugly about using angularjs's DI system?
Please see https://github.com/bcherny/ngimport#why - it's fairly complete explanation.
Not inject dependencies in the constructor sounds more work when it comes to migration.
If you use a constructor for injecting dependencies, then you can't pass any arguments when instantiating your class (a severe downside IMO). If you inject via closure, then there are many other downsides, including non-interoperability (see the "why" link above). Fundamentally, Angular 1's DI does not use standard imports, which makes it hard to use with non-Angular 1 code.
Could you elaborate? Maybe provide a sample?
Please see https://github.com/bcherny/ngimport#before-1. Here's another, more distilled example:
// before
import { IHttpService } from 'angular'
angular.factory('Car', ($http: IHttpService) => {
return class Car implements ICar {
constructor(private color: string, private id: number) {}
drive(to: Location) {
$http.post('/car/drive/' + id, to)
}
}
})
export interface ICarConstructor {
new(color: string, id: number): ICar
}
export interface ICar {
drive(to: Location): void
}
// after
import { $http } from 'ngimport'
export class Car {
constructor(private color: string, private id: number) {}
drive(to: Location) {
$http.post('/car/drive/' + id, to)
}
}
Edit: If I get this straight, all ngImport does is export the angular services in a single file, making them exportable? Does anything that uses tree shaking likes this approach at all?
I'm not sure what tree shaking has to do with DI, but exposing an importable API for builtins exactly the idea. Angular 1's DI has an API that is not in line with the way that the JS ecosystem developed since the DI system was designed, so this is a small wrapper to modernize that API.
@Narretz Integrating into core would be really interesting, though it was not my first thought. My hope was to get some more feedback from the Angular core community, and get ngimport added to the Angular docs.
If others find ngimport as useful as my team and I have, then integrating into core might ease the migration path for people with hybrid Angular1 + Angular2 or React or Ember or Vue (etc.) apps.
@bcherny How would your code look like if you have a CustomHttpService, which uses $http internally?
Out of curiosity, as you talk about an easier migration path towards Angular, how would you implement this in Angular without injecting an Http instance in the constructor but only importing it or using a service locator?
If get this right, you're resolving dependencies using $injector and export them so that you can import them without the need to inject the dependencies in the classes or inject the $injector everywhere. For what I know about Angular's DI system, this would still require a comparable approach to achieve the same?
How would your code look like if you have a CustomHttpService, which uses $http internally?
// customHTTPService.ts:
import { $http } from 'ngimport'
export function post<T>(url: string, data: T): Promise<T> {
return $http.post(url, data)
}
// Car.ts:
import { post } from './customHTTPService'
export class Car {
constructor(private color: string, private id: number) {}
drive(to: Location) {
post('/car/drive/' + id, to)
}
}
Not totally sure I understood your question - does the above code answer it? If not, feel free to post a code sample, and I can rewrite it with ngimport to help explain the approach.
So if you have a custom HttpService (use-case doesn't matter but the pattern does):
export class MyHttp {
constructor(private $http: angular.IHttpService) {}
post(url: string, data: any) {
return $http.post(url, data);
}
}
If I understand ngimport correctly, you'd write something like this:
import { $http } from 'ngimport';
export class MyHttp {
post(url: string, data: any) {
return $http.post(url, data);
}
}
If you want to create an instance for your MyHttp to be used in ur Car, would you bypass the DI system? If so, I get a feeling ngimport is about creating angularjs' core services using $injector and exporting them so that this instance can be used everywhere without needing to inject it (so no need for angularjs DI system so no need to register your custom classes with angular.module), allowing you to bypass the DI system for all your custom code.
How does this allow for easier migration towards Angular's DI system?
If you want to create an instance for your MyHttp to be used in ur Car, would you bypass the DI system?
You can use the DI system, or you can avoid it (this latter case is very useful for interop).
If so, I get a feeling ngimport is about creating angularjs' core services using $injector and exporting them so that this instance can be used everywhere without needing to inject it (so no need for angularjs DI system so no need to register your custom classes with angular.module), allowing you to bypass the DI system for all your custom code.
That's about right. Angular still creates an injector when bootstrapping the app, so you can configure builtins using .config().
The benefit is that now you can import and use these builtins outside of a DI'd context! For example, we use $http in React components to take advantage of default headers, and it works exactly as you would expect.
Apart from a certain use case where you need to use things coming from the angularjs' DI system inside a react component (:boom:) (or anything else which has no access to the angularjs' DI system), I don't really think this is a common scenario.
This would lead to code only using import and (almost) no need to be using constructor injection, right ?
I don't think 'd prefer this approach and this doesn't sound like an easier migration path as Angular still uses constructor injection if you want to take advantage of it's DI system without using the service locator.
I do think it's pretty awesome you're using $http in react tho. 馃槃
I don't really think this is a common scenario.
I think you misunderstand the use case.. Ngimport is broadly useful anywhere where people current use Angular 1's DI. It is a far more ergonomic API, and is much easier to use for all the reasons listed out here.
At my company we use it in place of Angular's DI API, and have had great success interoperating Angular, TypeScript, React, and framework-agnostic code. The developer experience is totally seamless, and is a big step up from the default DI API. Even if your app is purely Angular 1, it still yields a far better developer experience.
I would encourage you to try it out before dismissing it!
If you like, we can continue our conversation over Gitter or email. I'm curious to get more feedback from others as well.
I was recently looking for a library to make DI in AngularJS easier/prettier with TypeScript whiel moving toward Angular(2) styles, but preferably remaining light weight and not actually changing how things work under the hood. While there are many helper libraries out there such as ng-annotate, ngimport and half a dozen others, I found none of them seemed to follow the Angular(2) style or things like ngimport don't seem to follow the normal DI (if I'm understanding it correctly). There's also ng-metadata which seems to do a very good job, especially with the Angular(2) style, but I didn't want such a heavy solution at this time.
So (of course) I created my own solution: https://github.com/jbedard/ng-facade/
Note that I just threw the docs together in the last 10 minutes, so of course they are incomplete, lacking, and probably filled with typos (sorry!). The tests show more examples though.
We've been using it at my work for about a month now and so far it's been great. It has much cleaner DI compared to plain AngularJS (imo), encourages or forces the use of TS types, and also tries to keep things as close as possible to Angular(2). I hope all the services/pipes/modules could be translated directly to Angular(2), while the directives/components would just need some work to change to the Angular(2) HTML style and also drop any use of a couple AngularJS specific things.
Would be great if others gave it a shot. I'm sure there's issues to be found, especially having only used it in one app. Feedback and bug reporting would be great!
@jbedard Your library looks very cool, and is indeed more inline with the Angular2 style.
A few clarifications:
imports for DI for your own code (like people do in the React ecosystem)Yeah ng-annotate only solved the "$inject is annoying" problem and nothing else, and it solves it in a way completely different then Angular(2). But it's something I've seen mentioned for years so it was in my list of potential tools to use.
I really haven't looked into ngimport very much, so sorry for any false assumptions, but how could it support things like multiple apps with separate instances of classes (especially in tests) if DI is done via import?
I really haven't looked into ngimport very much, so sorry for any false assumptions, but how could it support things like multiple apps with separate instances of classes (especially in tests) if DI is done via import?
Ngimport just covers Angular 1 builtins. What you do with your own app code is up to you; you can still use DI if you want, but ngimport gives you the flexibility to use regular imports as well.
For us, we found that a combination of a more functional/stateless style of programming, compile time templating, and regular jasmine spies served the same role as DI did in our app, without all the runtime overhead and unsafety. I guess that there are 2 separate ideas here:
(1) is obvious, but (2) is a matter of taste. For our use case, we found that no DI lead to a simpler design and much more flexibility and interoperability with both TypeScript and non-Angular code. For your use case, if you prefer to keep the DI as is, you can definitely do that too.
馃憤 I would like to see Angular 1.x lean more towards ES Modules instead of these special "Angular modules". Provides interoperability with non-Angular code and TypeScript.
Even Angular has specialised modules that provide utility that goes beyond Es2015 modules. So I don't think anything monumental will happen with AngularJS in this regard.
This is out of scope for AngularJS development at this point.