Como exigir um controlador em uma diretiva angularjs

86

Alguém pode me dizer como incluir um controlador de uma diretiva em outra diretiva angularJS. por exemplo, eu tenho o seguinte código

var app = angular.module('shop', []).
config(['$routeProvider', function ($routeProvider) {
    $routeProvider.when('/', {
        templateUrl: '/js/partials/home.html'
    })
        .when('/products', {
        controller: 'ProductsController',
        templateUrl: '/js/partials/products.html'
    })
        .when('/products/:productId', {
        controller: 'ProductController',
        templateUrl: '/js/partials/product.html'
    });
}]);

app.directive('mainCtrl', function () {
    return {
        controller: function ($scope) {}
    };
});

app.directive('addProduct', function () {
    return {
        restrict: 'C',
        require: '^mainCtrl',
        link: function (scope, lElement, attrs, mainCtrl) {
            //console.log(cartController);
        }
    };
});

Eu deveria ser capaz de acessar o controlador na diretiva addProduct, mas não sou. Existe um jeito melhor de fazer isso?

Le Garden Fox
fonte
5
requiregarante a presença de outra diretiva e, em seguida, inclui seu controlador. ^requireverifica os elementos acima do atual, além do elemento atual. Portanto, você tem que usar as duas diretivas juntas para que isso funcione. Caso contrário, apenas defina um controlador com app.controllere, em seguida, use-o em ambas as diretivas. De qualquer maneira, você pode colocar isso em um Plunker simples junto com seu código HTML?
Josh David Miller

Respostas:

187

Tive sorte e respondi com um comentário sobre a pergunta, mas estou postando uma resposta completa por uma questão de integridade e, portanto, podemos marcar essa pergunta como "Respondida".


Depende do que você deseja realizar compartilhando um controlador; você pode compartilhar o mesmo controlador (embora tenha instâncias diferentes) ou pode compartilhar a mesma instância do controlador.

Compartilhe um controlador

Duas diretivas podem usar o mesmo controlador passando o mesmo método para duas diretivas, assim:

app.controller( 'MyCtrl', function ( $scope ) {
  // do stuff...
});

app.directive( 'directiveOne', function () {
  return {
    controller: 'MyCtrl'
  };
});

app.directive( 'directiveTwo', function () {
  return {
    controller: 'MyCtrl'
  };
});

Cada diretiva obterá sua própria instância do controlador, mas isso permite que você compartilhe a lógica entre quantos componentes desejar.

Requer um controlador

Se você quiser compartilhar a mesma instância de um controlador, use require.

requiregarante a presença de outra diretiva e, em seguida, inclui seu controlador como um parâmetro para a função de link. Portanto, se você tiver duas diretivas em um elemento, sua diretiva pode exigir a presença da outra diretiva e obter acesso aos seus métodos de controlador. Um caso de uso comum para isso é exigir ngModel.

^require, com a adição do acento circunflexo, verifica os elementos acima da diretiva, além do elemento atual, para tentar encontrar a outra diretiva. Isso permite criar componentes complexos onde "subcomponentes" podem se comunicar com o componente pai por meio de seu controlador com grande efeito. Os exemplos podem incluir guias, onde cada painel pode se comunicar com as guias gerais para lidar com a alternância; um conjunto de acordeão pode garantir que apenas um seja aberto por vez; etc.

Em qualquer caso, você deve usar as duas diretivas juntas para que isso funcione. requireé uma forma de comunicação entre componentes.

Confira a página de diretivas do Guia para mais informações: http://docs.angularjs.org/guide/directive

Josh David Miller
fonte
4
É possível exigir um controlador de diretiva irmão? Basicamente, preciso compartilhar a mesma instância de um controlador ou serviço entre as diretivas de irmãos (como em irmãos DOM, não no mesmo elemento DOM) que é repetido usando ng-repeat. Imagine que cada item repetido tenha uma diretiva que precise de um estado ou lógica compartilhada entre eles.
CMCDragonkai
2
@CMCDragonkai Não há como fazer isso, mas existem duas maneiras comuns de realizar a mesma coisa. A primeira é se os irmãos são todos do mesmo "tipo", então o elemento acima do ngRepeat pode ser como uma diretiva de contêiner e todos os subelementos podem então requerer essa diretiva, todos compartilhando o mesmo controlador. A solução mais comum - e geralmente mais canônica - é usar um serviço compartilhado. Você pode explicar o que esses irmãos fazem e o que eles precisam compartilhar?
Josh David Miller
Sim, acabou fazendo a primeira opção. Usando um controlador de diretiva de contêiner. Funciona bem. É para a Maçonaria.
CMCDragonkai
Esta é uma ótima resposta e solidificou meu entendimento de como tudo isso funciona. Obrigado! (Como observação, este pode ser um recurso mais recente, mas você pode usar requirepara especificar uma única diretiva ou uma série de diretivas; cada diretiva pode ser prefixada com um acento circunflexo ( ^) para requisitos mais granulares.)
jedd.ahyoung
Usar o mesmo controlador em duas diretivas não dá a cada diretiva sua própria instância.
jsbisht
27

Há uma boa resposta stackoverflow aqui por Mark Rajcok:

Controladores diretivos AngularJS que requerem controladores diretivos pai?

com um link para este jsFiddle muito claro: http://jsfiddle.net/mrajcok/StXFK/

<div ng-controller="MyCtrl">
    <div screen>
        <div component>
            <div widget>
                <button ng-click="widgetIt()">Woo Hoo</button>
            </div>
        </div>
    </div>
</div>

JavaScript

var myApp = angular.module('myApp',[])

.directive('screen', function() {
    return {
        scope: true,
        controller: function() {
            this.doSomethingScreeny = function() {
                alert("screeny!");
            }
        }
    }
})

.directive('component', function() {
    return {
        scope: true,
        require: '^screen',
        controller: function($scope) {
            this.componentFunction = function() {
                $scope.screenCtrl.doSomethingScreeny();
            }
        },
        link: function(scope, element, attrs, screenCtrl) {
            scope.screenCtrl = screenCtrl
        }
    }
})

.directive('widget', function() {
    return {
        scope: true,
        require: "^component",
        link: function(scope, element, attrs, componentCtrl) {
            scope.widgetIt = function() {
                componentCtrl.componentFunction();
            };
        }
    }
})


//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});

function MyCtrl($scope) {
    $scope.name = 'Superhero';
}
Joseph Oster
fonte
4
Para mim, o que mais fez o exemplo de Mark Rajcok clicar foi prestar atenção em como os métodos do controlador são criados. Normalmente você vê métodos de controlador criados por meio de $ scope.methodName = function () {...}, mas para que isso funcione, você deve usar this.methodName para os métodos que deseja acessar. Eu não percebi isso no começo.
coblr