web-dev-qa-db-ja.com

Angular 1.xディレクティブとしてのES6クラスの使用

私は、ES6がもたらす便利なバッグをいじるために小さなプロジェクトを行っています。クラスをangularディレクティブとして登録しようとしていますが、このエラー「TypeError:Cannotクラスを関数として呼び出します」が、例から、クラスを作成し、angularにディレクティブとして登録するだけであることがわかります。これが私の指令です。

class dateBlock {
  constructor () {
    this.template = '/app/dateblock/dateblock.html';
    this.restrict = 'AE';
    this.scope = {};
  }
};

export default dateBlock

インデックスをインポートしてから宣言します。

import calendarController from './calendar/calendar.js'
import dateBlock from './dateblock/dateblock.js'

function setup($stateProvider) {
    $stateProvider
      .state('base', {
        url: '',
        controller: calendarController,
        templateUrl: '/app/calendar/calendar.html'
      });
    };

setup.$inject = ['$stateProvider']

var app = angular.module('calApp',['ngAnimate','ui.router','hmTouchEvents', 'templates'])
  .config(setup)
  .controller('calendarController', calendarController)
  .directive('dateBlock', dateBlock)

何か重要なステップを逃した場合、それを聞きたいです。また、すべてのアプリコンポーネントをインデックスにインポートしてそこにすべて登録するか、アプリをエクスポートしてコンポーネント内にインポートおよび登録することは、よりクリーンです。

57
Boughtmanatee

私の観点からは、register.jsのような外部ライブラリを使用する必要はありません。なぜなら、次のようにしてES6クラスとしてディレクティブを作成できるからです。

class MessagesDirective {
    constructor() {
        this.restrict = 'E'
        this.templateUrl = 'messages.html'
        this.scope = {}
    }

    controller($scope, $state, MessagesService) {
        $scope.state = $state;
        $scope.service = MessagesService;
    }

    link(scope, element, attrs) {
        console.log('state', scope.state)
        console.log('service', scope.service)
    }
}
angular.module('messages').directive('messagesWidget', () => new MessagesDirective)

ディレクティブコントローラーを使用すると、追加の宣言(例:MessagesDirective.$inject = ['$scope', '$state', 'MessagesService'])がなくても依存関係を注入できるため、必要に応じてスコープを介してリンク関数でサービスを使用できます。

63
michal.chochol

コメントで述べたように、module.directive()メソッドはコンストラクターではなくファクトリー関数を想定しています。

最も簡単な方法は、インスタンスを返す関数でクラスをラップすることです。

angular.module('app')
    .directive('dateBlock', () => new DateBlock());

ただし、これは最も限られた意味でのみ機能します。依存関係の注入は許可されず、ディレクティブのcompileおよびlink関数(定義されている場合)は期待どおりに機能しません。

実際、これは私が非常に広範囲に検討した問題であり、解決するのはかなり難しいことが判明しました(少なくとも私にとって)。

私は自分の解決法を網羅した大規模な記事を書きましたが、あなたが懸念している限り、解決する必要のある2つの主要な問題の議論を指摘できます。

  1. クラス定義を角度互換のファクトリ関数に動的に変換する

  2. ディレクティブのlinkおよびcompile関数をクラスメソッドとして定義できるようにする

完全なソリューションには、ここに貼り付けるにはコードが多すぎると思いますが、次のようなES6クラスとしてディレクティブを定義できる実用的なデモプロジェクトを作成しました。

class MyDirective {
    /*@ngInject*/
    constructor($interval) {
        this.template = '<div>I\'m a directive!</div>';
        this.restrict = 'E';
        this.scope = {}
        // etc. for the usual config options

        // allows us to use the injected dependencies
        // elsewhere in the directive (e.g. compile or link function)
        this.$interval = $interval;
    }

    // optional compile function
    compile(tElement) {
        tElement.css('position', 'absolute');
    }

    // optional link function
    link(scope, element) {
        this.$interval(() => this.move(element), 1000);
    }

    move(element) {
        element.css('left', (Math.random() * 500) + 'px');
        element.css('top', (Math.random() * 500) + 'px');
    }
}

// `register` is a helper method that hides all the complex magic that is needed to make this work.
register('app').directive('myDirective', MyDirective);

デモリポジトリはこちらこちらはregister.directive()の背後にあるコードです をご覧ください

48
Michael Bromley

@Michaelはお金が正しい:

module.directive()メソッドはファクトリー関数を期待しています

しかし、私は別のテクニックを使用してそれを解決しました、私はおそらく少しきれいです、それは私にとってはうまくいきますが、完璧ではありません... module()によって期待されるファクトリを返す静的メソッドを定義しました

class VineDirective {
    constructor($q) {
        this.restrict = 'AE';
        this.$q = $q;
    }

    link(scope, element, attributes) {
        console.log("directive link");
    }

    static directiveFactory($q){
        VineDirective.instance = new VineDirective($q);
        return VineDirective.instance;
    }
}

VineDirective.directiveFactory.$inject = ['$q'];

export { VineDirective }

そして私のアプリでは:

angular.module('vineyard',[]).directive('vineScroller', VineDirective.directiveFactory)

私はこの時点でこのようなハックを通過するクラス+ディレクティブを使用する他の方法はないと信じています、ただ簡単なものを選んでください;-)

22
bmaggi

よりシンプルでクリーンで読みやすいソリューション????。

class ClipBoardText {

  constructor() {
    console.log('constructor');

    this.restrict = 'A';
    this.controller = ClipBoardTextController;
  }

  link(scope, element, attr, ctr) {

    console.log('ctr', ctr);
    console.log('ZeroClipboard in link', ctr.ZeroClipboard);
    console.log('q in link', ctr.q);

  }

  static directiveFactory() {
    return new ClipBoardText();
  }
}

// do not $inject like this
// ClipBoardText.$inject = ['$q'];

class ClipBoardTextController {
  constructor(q) {
    this.q = q;
    this.ZeroClipboard = 'zeroclipboard';
  }
}

ClipBoardTextController.$inject = ['$q'];


export default ClipBoardText.directiveFactory;

link関数で$qを取得することはできません。thislinkundefinedまたはnullになります。 exploring-es6-classes-in-angularjs-1-x#_section-factories

Angularがリンク関数を呼び出すと、クラスインスタンスのコンテキスト内にないため、this。$ intervalは未定義になります

そのため、ディレクティブでcontroller関数を使用し、依存関係またはlink関数でアクセスするものを注入します。

19
legend80s

私の解決策:

class myDirective {
   constructor( $timeout, $http ) {
       this.restrict = 'E';
       this.scope = {};

       this.$timeout = $timeout;
       this.$http = $http;
   }
   link() {
       console.log('link myDirective');
   }
   static create() {
       return new myDirective(...arguments);
   }
}

myDirective.create.$inject = ['$timeout', '$http'];

export { myDirective }

メインアプリファイル

app.directive('myDirective', myDirective.create)
5
Alon

私のプロジェクトでは、注入に構文シュガーを使用しています。また、ES6を使用すると、ディレクティブの注入可能なファクトリを使用して、コードの重複を回避することが非常に簡単になります。このコードは、インジェクションの継承を許可し、注釈付きのインジェクションなどを使用します。これをチェックして:

最初の一歩

すべてのangular controllers\directives\services-InjectableClientの基本クラスを宣言します。 その主なタスク-注入されたすべてのパラメータをプロパティとして設定「this」この動作はオーバーライドできます。以下の例を参照してください。

class InjectionClient {

    constructor(...injected) {
        /* As we can append injections in descendants we have to process only injections passed directly to current constructor */ 
        var injectLength = this.constructor.$inject.length;
        var injectedLength = injected.length;
        var startIndex = injectLength - injectedLength;
        for (var i = startIndex; i < injectLength; i++) {
            var injectName = this.constructor.$inject[i];
            var inject = injected[i - startIndex];
            this[injectName] = inject;
        }
    }

    static inject(...injected) {
        if (!this.$inject) { 
            this.$inject = injected; 
        } else {
            this.$inject = injected.concat(this.$inject);
        }
    };
}

たとえば、SomeClassInheritedFromInjectableClient.inject( '$ scope')を呼び出す場合、ディレクティブまたはコントローラーで 'this。$ scope'として使用します

第二段階

ディレクティブクラスの$ injectedプロパティをファクトリ関数にバインドする静的メソッド「factory()」を使用して、ディレクティブの基本クラスを宣言します。また、リンク関数のコンテキストをディレクティブ自体にバインドする「compile()」メソッド。 this.myInjectedServiceとしてリンク関数内に注入された値を使用できます。

class Directive extends InjectionClient {
    compile() {
        return this.link.bind(this);
    }

    static factory() {
        var factoryFunc = (...injected) => {
            return new this(...injected);
        }
        factoryFunc.$inject = this.$inject;
        return factoryFunc;
    }
}

第三段階

これで、可能な限り多くのディレクティブクラスを宣言できます。継承あり。また、スプレッド配列を使用して簡単な方法で注入を設定できます(スーパーメソッドの呼び出しを忘れないでください)。例を参照してください:

class DirectiveFirst extends Directive {
}

DirectiveFirst.inject('injA', 'injB', 'injC');


class DirectiveSecond extends DirectiveFirst {

    constructor(injD, ...injected) {
        super(...injected);
        this.otherInjectedProperty = injD;
    }
}
// See appended injection does not hurt the ancestor class
DirectiveSecond.inject('injD');

class DirectiveThird extends DirectiveSecond {

    constructor(...injected) {
        // Do not forget call the super method in overridden constructors
        super(...injected);
    }
}    

最後のステップ

ディレクティブをangularに簡単な方法で登録します:

angular.directive('directiveFirst', DirectiveFirst.factory());
angular.directive('directiveSecond', DirectiveSecond.factory());
angular.directive('directiveThird', DirectiveThird.factory());

次に、コードをテストします。

var factoryFirst = DirectiveFirst.factory();
var factorySec = DirectiveSecond.factory();
var factoryThird = DirectiveThird.factory();


var directive = factoryFirst('A', 'B', 'C');
console.log(directive.constructor.name + ' ' + JSON.stringify(directive));

directive = factorySec('D', 'A', 'B', 'C');
console.log(directive.constructor.name + ' ' + JSON.stringify(directive));

directive = factoryThird('D', 'A', 'B', 'C');
console.log(directive.constructor.name + ' ' + JSON.stringify(directive));

これは戻ります:

DirectiveFirst {"injA":"A","injB":"B","injC":"C"}
DirectiveSecond {"injA":"A","injB":"B","injC":"C","otherInjectedProperty":"D"}
DirectiveThird {"injA":"A","injB":"B","injC":"C","otherInjectedProperty":"D"}
3
Statyan

同様の問題がありました。しかし、私の場合、本番環境にデプロイしたときに機能し、失敗しました。また、プロダクションには6to5の最新バージョンがあるため、失敗しました。これは、npm shrinkwrapを使用することで防止できます。最新のES6仕様によると、このようなクラスは使用できません。 https://github.com/babel/babel/issues/7

0
Roberto_ua

今、この問題に遭遇し、このトピックを見ました。議論で提供されたいくつかの方法を試し、最終的にこの問題を非常に簡単な方法で解決しました。

export default function archiveTreeDirective() {
    'ngInject';

    return {
        restrict: 'E',
        scope: {
            selectedNodes: "="
        },
        templateUrl: 'app/components/directives/archiveTree/archiveTree.html',
        controller: ArchiveTreeController,
        controllerAs: 'vm',
        bindToController: true
    };
}

class ArchiveTreeController {
    constructor() {
        'ngInject';
        ...
    }
    ...
}

関数を.directive( 'directiveName'、factory)引数として直接使用し、エクスポートし、後でモジュール宣言でインポートします。しかし、エクスポート時に「デフォルト」ステートメントを逃したため、エラーが発生しました。 「デフォルト」キーWordを追加すると、すべてが機能します。

この方法は、ルート設定でも機能します(機能的な方法でも)。

============あなたが私の貧しい英語を理解できることを願って:)

0
Howard
class ToggleShortcut{
constructor($timeout, authService, $compile, $state){

    var initDomEvents = function ($element, $scope) {

        var shortcut_dropdown = $('#shortcut');

        $compile(shortcut_dropdown)($scope);

        $scope.goToShortCutItem = function(state, params){
            var p = params || null;

            if(state === 'app.contacts.view'){
                var authProfile = authService.profile;
                if(authProfile){
                    p = {
                        id:authProfile.user_metadata.contact_id
                    };
                }
            }

            $state.go(state, p);
            window.setTimeout(shortcut_buttons_hide, 300);
        };

        $element.on('click', function () {
            if (shortcut_dropdown.is(":visible")) {
                shortcut_buttons_hide();
            } else {
                shortcut_buttons_show();
            }

        });

        // SHORTCUT buttons goes away if mouse is clicked outside of the area
        $(document).mouseup(function (e) {
            if (shortcut_dropdown && !shortcut_dropdown.is(e.target) && shortcut_dropdown.has(e.target).length === 0) {
                shortcut_buttons_hide();
            }
        });

        // SHORTCUT ANIMATE HIDE
        function shortcut_buttons_hide() {
            shortcut_dropdown.animate({
                height: "hide"
            }, 300, "easeOutCirc");
            $('body').removeClass('shortcut-on');

        }

        // SHORTCUT ANIMATE SHOW
        function shortcut_buttons_show() {
            shortcut_dropdown.animate({
                height: "show"
            }, 200, "easeOutCirc");
            $('body').addClass('shortcut-on');
        }
    };

    var link = function($scope, $element){
        $timeout(function(){
            initDomEvents($element, $scope);
        });
    };

    this.restrict = 'EA';
    this.link = link;
}
}

toggleShortcut.$inject = ['$timeout', 'authService', '$compile', '$state'];

function toggleShortcut($timeout, authService, $compile, $state){
return new ToggleShortcut($timeout, authService, $compile, $state);
}

angular.module('app.layout').directive('toggleShortcut', toggleShortcut);
0
Egor

私は同じ問題に直面しました。初めてES6クラスを介して問題を解決しようとしましたが、依存関係の$ injectに問題があります。 angularにコードを書くスタイルがいくつかあることに気付いた後、試しました。まったく John Papa スタイルを使用し、ES6でRailsアプリでこのコードを取得しました:

((angular) => {
 'use strict';

  var Flash = ($timeout) => {
   return {
     restrict: 'E',
     scope: {
       messages: '=messages'
     },
     template: (() => {
       return "<div class='alert flash-{{ message[0] }}' ng-repeat = 'message in messages'>" +
                "<div class= 'close' ng-click = 'closeMessage($index)' data-dismiss = 'alert' > × </div>" +
                "<span class= 'message' >{{ message[1] }}</ span>" +
              "</ div>";
     }),
     link: (scope) => {
       scope.closeMessage = (index) => {
         scope.messages.splice(index, 1)
       };

      $timeout(() => {
        scope.messages = []
      }, 5000);
    }
  }
};

Flash.$inject = ['$timeout'];

angular.module('Application').directive('ngFlash', Flash);

})(window.angular);

ES6スタイルの関数と変数で少し改善できることを知っています。役に立てば幸いです。

0
khusnetdinov