Open-source project
bcherny/ngimport avatar
bcherny/ngimport

ngimport: Using Angular 1 Services as ES6 Module Imports

Easy to use ES6 imports for $http, $log, and other Angular 1 services

100 stars5 forksTypeScriptLicense varies

At a glance

What is it?
ngimport is a TypeScript package that exposes Angular 1 built-in services such as $http and $log as standard ES6 named exports, eliminating the need for dependency injection syntax when consuming those services in new or migrating code. It targets teams maintaining Angular 1 applications who want to write module-style code without restructuring the entire codebase.
Who is it for?
ngimport is worth adopting only in a specific scenario: an Angular 1 codebase that is actively being migrated to ES6 modules and wants a gradual path that preserves testability. The README's use of $provide and $httpBackend in unit tests confirms that the standard Angular 1 testing patterns still work when services are accessed this way.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Probably not. The repository last received commits 105 months ago, on January 29, 2018.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What ngimport does and what problem it solves

Angular 1 uses a dependency injection system that predates JavaScript module standards. To use a service like $http, you register a factory or service with Angular's module system and declare $http as an injected parameter. That approach made sense before CommonJS or ES Modules existed, but it creates friction in a codebase that has adopted ES6 imports.

The README identifies several specific problems. First, Angular DI requires wrapping code in factory or service registrations, which forces a different file structure than plain ES6 modules. Second, TypeScript users end up declaring service types twice: once in the concrete service class and once in an interface exported separately, because the DI closure prevents exporting the class directly. Third, constructor injection for DI conflicts with the normal use of constructors to accept instance arguments.

ngimport addresses these by using Angular's $injector internally. It registers a run block that retrieves each built-in service from Angular's injector and assigns it to a module-level variable, which is then exported as a named export. The result is that you can write:

ts
import {IPromise} from 'angular'
import {$http, $log} from 'ngimport'

export function Get(url: string): IPromise<string> {
  return $http.get(url).then(data => {
    $log.info('Got data!', data)
    return data
  })
}

instead of injecting $http and $log through a factory callback. The function is now a plain ES6 export that TypeScript can check for type correctness without a separate interface declaration.

Why Angular 1 DI became a problem with ES6 modules

The README's 'Why?' section explains the design tension clearly. Angular 1's injector was designed for a world where there was no standard module system. You declared dependencies by name as strings, and Angular resolved and injected them at runtime. This worked when all code lived in global script tags, but it does not compose well with ES6 import/export or CommonJS require.

The portability argument in the README is the strongest one: code written with ES6 imports can be used across Angular 1, Angular 2 or later, and React without modification. A factory-wrapped Angular 1 service cannot be imported directly into a React component or an Angular 2 module. The ngimport approach makes the service function itself the export, so it is available wherever standard imports work.

This argument was most relevant during the 2015 to 2018 migration period when Angular 1 codebases were moving to Angular 2 incrementally and needed code that would work in both environments simultaneously. The README explicitly describes ngimport as an adapter for incremental migration.

Installing ngimport and registering the module

Install ngimport alongside Angular 1:

sh
# Using Yarn:
yarn add ngimport angular

# Or, using NPM:
npm install ngimport angular --save

If you use ngResource, the README notes that a separate companion package is available: ngimport-ngresource.

Once installed, add 'bcherny/ngimport' to your Angular module's dependency array:

ts
angular.module('myModule', ['bcherny/ngimport'])

This registration is required. Without it, the run block that populates the exported service variables will not execute, and all ngimport exports remain undefined. After this, any file in your application can import $http, $log, or other services directly from ngimport.

Wrapping your own Angular 1 modules with ES6 exports

The README documents a technique for extending the same approach to your own Angular 1 services. If you have a legacy fooService registered through Angular's DI, you can expose it as a standard ES6 export by creating a thin wrapper module:

ts
import {IPromise, module} from 'angular'
export let fooService = undefined

interface FooService {
  foo: () => IPromise<{ data: string }>
}

module('myModule').run(function ($injector) {
  fooService = <FooService>$injector.get('fooService')
})

After this, any code that needs fooService can write import {fooService} from './fooService' instead of injecting it. The README notes that you can then migrate the service to TypeScript and ES6 at your own pace, since the export wrapper allows consumers to use import syntax regardless of how the service itself is implemented.

This wrapper pattern is the same mechanism ngimport uses internally for built-in Angular services. Understanding it lets you handle services that ngimport does not cover natively.

Limitations: two gotchas the README documents

The README lists two limitations explicitly.

The first is initialization order. Angular built-in services like $http and $rootScope are undefined until Angular bootstraps the application. The exported variables are populated in Angular's run phase, which runs after the module declaration but before user interaction. Any code that runs at module load time (at the top level of an ES6 module, outside a function) will see undefined if it tries to use an ngimport export. You must either delay such code until after bootstrap or structure it so that ngimport exports are only accessed inside functions called after app startup.

The second limitation is specific to CommonJS transpilation. When TypeScript or Babel compiles ES6 imports to CommonJS require calls, there is a difference between destructured imports and default imports. The README specifies that you must destructure the import rather than importing the module as a default value. If you write const ngimport = require('ngimport') and then use ngimport.$http, you hold a reference to the object at the time of require. But the exported variable is reassigned later when Angular bootstraps. The destructured form const {$http} = require('ngimport') holds a reference to the binding in the module scope, which gets the updated value after bootstrap.

Neither of these is a defect in the implementation; both are consequences of how Angular's injector and JavaScript module systems work.

Maintenance status and licensing

The package is available on npm under the MIT license as stated in the README. The repository's package.json also records the license as MIT. The last push was on 2018-01-29. Angular 1 itself entered long-term support in December 2021 and formally ended support in December 2022, which means ngimport now addresses a scenario involving a framework that is no longer under active development from its authors.

The README's Todo section lists services that were not yet covered at the time of the last push, including $animate, $cookies, $routeParams, $sanitize, $swipe, and others. Those gaps remain as of the 2018 snapshot. A team that needs any of those services would need to write the wrapper code described in the 'Using this technique to wrap your own legacy modules' section of the README.

Editorial conclusion

ngimport is worth adopting only in a specific scenario: an Angular 1 codebase that is actively being migrated to ES6 modules and wants a gradual path that preserves testability. The README's use of $provide and $httpBackend in unit tests confirms that the standard Angular 1 testing patterns still work when services are accessed this way. Teams that are still on Angular 1 and have no migration plans should weigh the upside against the risk of a dependency with a last push date of 2018-01-29 and no further updates. Anyone starting a new project should use a current framework. The one concrete check before adoption: verify that you bootstrap the Angular app before any ngimport-based code runs at the module level, because the README is explicit that built-in services are undefined until bootstrap.

Frequently asked questions

Why are ngimport services undefined at module load time?

Angular 1 populates its built-in services during the run phase, which happens after module declaration but before user interaction. Any code that accesses ngimport exports at the top level of a module, before Angular bootstraps, will see undefined. The README recommends using ngimport exports only inside functions called after app startup.

Does ngimport work with Angular 1 unit testing tools?

Yes. The README states that you can still mock Angular dependencies with $provide and assert against HTTP requests with $httpBackend in unit tests, the same way as with standard Angular 1 DI. The ngimport approach does not require changes to your test setup.

Does ngimport cover all Angular 1 built-in services?

No. The README's Todo section lists services that were not covered at the time of the last push in January 2018, including $animate, $cookies, $routeParams, $sanitize, and others. For those, you need to write a custom wrapper using the $injector.get pattern documented in the README.

Official sources

  1. Official README
  2. Project repository