Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.9k views
in Technique[技术] by (71.8m points)

typescript - AngularJS - Use ES6 imports instead of angular DI system

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share

2.1m questions

2.1m answers

63 comments

56.5k users

...