Reputation: 15319
I'm using Webpack with angular 1.6.x and Typescript and I quit using angular DI in favor of ES6 imports. When I need some ng functions like $http
, $resource
and such I inject them directly using the angular.injector
function through a decorator, like this:
// inject.ts
import * as angular from 'angular';
export function inject (...params: string[]) {
function doCall ( param: string, klass: Function) {
const injector = angular.injector([ 'ng' ]);
const service = injector.get(param);
try {
klass.prototype[ param ] = service;
} catch ( e ) {
window.console.warn( e );
}
}
// tslint:disable-next-line:ban-types
return function ( klass: Function ) {
params.forEach( ( param ) => {
doCall( param, klass );
} );
};
}
// posts.service.ts
import { inject } from './inject';
import { IPost, Post } from './post';
@inject('$http')
export class PostsService {
public $http: angular.IHttpService;
get (): Promise<IPost[]> {
const posts: IPost[] = [];
const promise = new Promise<IPost[]>( (resolve, reject) => {
this.$http.get<IPost[]>('https://jsonplaceholder.typicode.com/posts')
.then(( response ) => {
response.data.forEach(item => {
posts.push( new Post(item) );
});
resolve( posts );
});
});
return promise;
}
}
// post.ts
export interface IPost {
userId: number;
id: number;
title: string;
body: string;
}
export class Post implements IPost {
userId: number;
id: number;
title: string;
body: string;
constructor (item: IPost) {
this.userId = item.userId;
this.id = item.id;
this.title = item.title;
this.body = item.body;
}
}
// controller.ts
import { IPost } from './post';
import { PostsService } from './posts.service';
export class Controller {
public postService: PostsService;
public posts: IPost[];
constructor ( private $scope: angular.IScope ) {
this.postService = new PostsService();
}
$onInit () {
this.postService.get()
.then((posts) => {
this.posts = posts;
this.$scope.$digest();
});
}
}
// index.ts
import * as angular from 'angular';
import { Controller } from './app/controller';
import './index.scss';
export const app: string = 'app';
angular
.module(app, [])
.controller('controller', Controller);
angular.bootstrap(document.body, [app]);
I don't know if it's in compliance with best practices or not, but it is working quite nicely so far.
I would like to hear your thoughts on the subject: is there any problem (performance, bad practice and such) using this approach?
Upvotes: 1
Views: 1155
Reputation: 222344
ES modules cannot replace Angular modules and DI. They compliment each other and keep the application modular and testable.
ES6 modules provide extra layer of extensibility, for example controller/service subclassing (something that doesn't look good with Angular modules and DI alone).
The recommended approach with ES6 or TypeScript is to do DI conventionally, with $inject
annotation:
export class PostsService {
static $inject = ['$http'];
constructor(
public $http: angular.IHttpService
) {}
...
}
It's also a good practice to have one module per file, this way the application stays modular and testable:
export default angular.module('app.posts', [])
.service('posts', `PostsService)
.name;`
Its default
export is module name that can be imported in another module that directly depends on it:
import postsModule from '...';
...
export default angular.module('app.controller', [postsModule])
.controller('controller', Controller)
.name;`
Application injector cannot be normally reached from a decorator. Even if it's possible with a hack to make it work in production, it will be messed up in tests.
angular.injector
creates new injector (i.e. application instance) and has very limited proper uses in production:
angular.injector(['ng']).get('$rootScope') !== angular.injector(['ng']).get('$rootScope');
It is often misused when a developer doesn't know how to get current $injector
instance. It certainly shouldn't be used in a case like this one.
Upvotes: 4