Como faço para injetar um controlador em outro controlador em AngularJS

97

Sou novo no Angular e estou tentando descobrir como fazer as coisas ...

Usando o AngularJS, como posso injetar um controlador para ser usado em outro controlador?

Eu tenho o seguinte snippet:

var app = angular.module("testApp", ['']);

app.controller('TestCtrl1', ['$scope', function ($scope) {
    $scope.myMethod = function () {
        console.log("TestCtrl1 - myMethod");
    }
}]);

app.controller('TestCtrl2', ['$scope', 'TestCtrl1', function ($scope, TestCtrl1) {
    TestCtrl1.myMethod();
}]);

Quando eu executo isso, obtenho o erro:

Error: [$injector:unpr] Unknown provider: TestCtrl1Provider <- TestCtrl1
http://errors.angularjs.org/1.2.21/$injector/unpr?p0=TestCtrl1Provider%20%3C-%20TestCtrl1

Devo tentar usar um controlador dentro de outro controlador ou devo tornar isso um serviço?

Scottie
fonte
2
Você não pode injetar controladores um no outro. Sim, você deve mudar TestCtrl1para um serviço.
Sly_cardinal
Exatamente, usar serviços
Miguel Mota
3
e se eu tivesse que atualizar uma propriedade de um controlador que se vincula ao modo de exibição. Esta propriedade é impactada pelo evento ocorrendo em outro controlador.
Ankit Tanna

Respostas:

129

Se sua intenção é obter um controlador já instanciado de outro componente e se você está seguindo uma abordagem baseada em componente / diretiva, você pode sempre usar requireum controlador (instância de um componente) de outro componente que segue uma certa hierarquia.

Por exemplo:

//some container component that provides a wizard and transcludes the page components displayed in a wizard
myModule.component('wizardContainer', {
  ...,
  controller : function WizardController() {
    this.disableNext = function() { 
      //disable next step... some implementation to disable the next button hosted by the wizard
    }
  },
  ...
});

//some child component
myModule.component('onboardingStep', {
 ...,
 controller : function OnboadingStepController(){

    this.$onInit = function() {
      //.... you can access this.container.disableNext() function
    }

    this.onChange = function(val) {
      //..say some value has been changed and it is not valid i do not want wizard to enable next button so i call container's disable method i.e
      if(notIsValid(val)){
        this.container.disableNext();
      }
    }
 },
 ...,
 require : {
    container: '^^wizardContainer' //Require a wizard component's controller which exist in its parent hierarchy.
 },
 ...
});

Agora, o uso dos componentes acima pode ser algo assim:

<wizard-container ....>
<!--some stuff-->
...
<!-- some where there is this page that displays initial step via child component -->

<on-boarding-step ...>
 <!--- some stuff-->
</on-boarding-step>
...
<!--some stuff-->
</wizard-container>

Há muitas maneiras de configurar o require .

(sem prefixo) - Localize o controlador necessário no elemento atual. Lance um erro se não for encontrado.

? - Tente localizar o controlador necessário ou passe nulo para o link fn se não for encontrado.

^ - Localize o controlador necessário, pesquisando o elemento e seus pais. Lance um erro se não for encontrado.

^^ - Localize o controlador necessário pesquisando os pais do elemento. Lance um erro se não for encontrado.

? ^ - Tenta localizar o controlador requerido pesquisando o elemento e seus pais ou passa null para o link fn se não for encontrado.

? ^^ - Tenta localizar o controlador necessário pesquisando os pais do elemento, ou passa null para o link fn se não for encontrado.



Resposta antiga:

Você precisa injetar $controllerserviço para instanciar um controlador dentro de outro controlador. Mas esteja ciente de que isso pode levar a alguns problemas de design. Você sempre pode criar serviços reutilizáveis ​​que seguem a responsabilidade única e injetá-los nos controladores conforme necessário.

Exemplo:

app.controller('TestCtrl2', ['$scope', '$controller', function ($scope, $controller) {
   var testCtrl1ViewModel = $scope.$new(); //You need to supply a scope while instantiating.
   //Provide the scope, you can also do $scope.$new(true) in order to create an isolated scope.
   //In this case it is the child scope of this scope.
   $controller('TestCtrl1',{$scope : testCtrl1ViewModel });
   testCtrl1ViewModel.myMethod(); //And call the method on the newScope.
}]);

Em qualquer caso, você não pode chamar TestCtrl1.myMethod()porque anexou o método na $scopeinstância do controlador e não na instância do controlador.

Se você estiver compartilhando o controlador, será sempre melhor fazer: -

.controller('TestCtrl1', ['$log', function ($log) {
    this.myMethod = function () {
        $log.debug("TestCtrl1 - myMethod");
    }
}]);

e enquanto consome, faça:

.controller('TestCtrl2', ['$scope', '$controller', function ($scope, $controller) {
     var testCtrl1ViewModel = $controller('TestCtrl1');
     testCtrl1ViewModel.myMethod();
}]);

No primeiro caso, é realmente o $scopeseu modelo de visualização e, no segundo caso, é a própria instância do controlador.

PSL
fonte
4
E isso depende da funcionalidade fornecida pelo controlador, se você está tornando-o mais parecido com um modelo de visão que você precisa compartilhar entre os componentes, tudo bem, mas se for mais uma funcionalidade de provedor de serviço, então eu simplesmente iria criar um serviço .
PSL de
Deveria var testCtrl1ViewModel = $scope.$new();ser var testCtrl1ViewModel = $rootScope.$new();? consulte: docs.angularjs.org/guide/controller @PSL
leonsPAPA
No exemplo acima, você está acessando o container no controlador de diretiva, mas não consigo fazer isso funcionar. Posso acessar os controladores necessários por meio do quarto parâmetro na função de link da própria diretiva. Mas eles não estão vinculados ao controlador de diretiva como no exemplo acima. Alguém mais tendo esse problema?
Sammi
33

Eu sugiro que a pergunta que você deve fazer é como injetar serviços nos controladores. Serviços gordos com controladores magros é uma boa regra, também conhecida como apenas use controladores para colar seu serviço / fábrica (com a lógica de negócios) em suas visualizações.

Os controladores obtêm o lixo coletado nas alterações de rota, então, por exemplo, se você usar controladores para manter a lógica de negócios que renderiza um valor, você perderá o estado em duas páginas se o usuário do aplicativo clicar no botão Voltar do navegador.

var app = angular.module("testApp", ['']);

app.factory('methodFactory', function () {
    return { myMethod: function () {
            console.log("methodFactory - myMethod");
    };
};

app.controller('TestCtrl1', ['$scope', 'methodFactory', function ($scope,methodFactory) {  //Comma was missing here.Now it is corrected.
    $scope.mymethod1 = methodFactory.myMethod();
}]);

app.controller('TestCtrl2', ['$scope', 'methodFactory', function ($scope, methodFactory) {
    $scope.mymethod2 = methodFactory.myMethod();
}]);

Aqui está uma demonstração de trabalho da fábrica injetada em dois controladores

Além disso, sugiro que você leia este tutorial sobre serviços / fábricas.

atrevido
fonte
13

Não há necessidade de importar / injetar seu controlador em JS. Você pode simplesmente injetar seu controlador / controlador aninhado por meio de seu HTML. Funcionou para mim. Gostar :

<div ng-controller="TestCtrl1">
    <div ng-controller="TestCtrl2">
      <!-- your code--> 
    </div> 
</div>
Chetanpawar
fonte
2
verdade ... mas ainda sinto que é melhor colocar todos os elementos comuns em um serviço e injetar o serviço no respectivo controlador.
Neel
-1
<div ng-controller="TestCtrl1">
    <div ng-controller="TestCtrl2">
      <!-- your code--> 
    </div> 
</div>

Isso funciona melhor no meu caso, onde TestCtrl2 tem suas próprias diretivas.

var testCtrl2 = $controller('TestCtrl2')

Isso me dá um erro dizendo erro de injeção do scopeProvider.

   var testCtrl1ViewModel = $scope.$new();
   $controller('TestCtrl1',{$scope : testCtrl1ViewModel });
   testCtrl1ViewModel.myMethod(); 

Isso realmente não funciona se você tiver diretivas em 'TestCtrl1'; na verdade, essa diretiva tem um escopo diferente deste criado aqui. Você acaba com duas instâncias de 'TestCtrl1'.

binRAIN
fonte
-1

A melhor solução:-

angular.module("myapp").controller("frstCtrl",function($scope){$scope.name="Atul Singh";}).controller("secondCtrl",function($scope){angular.extend(this, $controller('frstCtrl', {$scope:$scope}));console.log($scope);})

// Aqui você recebeu a primeira chamada do controlador sem executá-la

Atul Singh
fonte
-1

você também pode usar $rootScopepara chamar uma função / método do primeiro controlador a partir do segundo controlador como este,

.controller('ctrl1', function($rootScope, $scope) {
     $rootScope.methodOf2ndCtrl();
     //Your code here. 
})

.controller('ctrl2', function($rootScope, $scope) {
     $rootScope.methodOf2ndCtrl = function() {
     //Your code here. 
}
})
usuário5943763
fonte
1
Downvote: Este é apenas um código ruim: você está apenas tornando sua função global. É melhor descartar completamente o Angular se esta é a maneira que você deseja codificar ... Use um serviço como sugerido pela maioria das outras respostas.
HammerNL
Isso não é recomendado. $ rootScope torna o código desajeitado e leva a problemas a longo prazo.
Harshit Pant
-2

use typescript para a sua codificação, porque é orientado a objetos, estritamente tipado e fácil de manter o código ...

para mais informações sobre o typescipt clique aqui

Aqui está um exemplo simples que criei para compartilhar dados entre dois controladores usando Typescript ...

module Demo {
//create only one module for single Applicaiton
angular.module('app', []);
//Create a searvie to share the data
export class CommonService {
    sharedData: any;
    constructor() {
        this.sharedData = "send this data to Controller";
    }
}
//add Service to module app
angular.module('app').service('CommonService', CommonService);

//Create One controller for one purpose
export class FirstController {
    dataInCtrl1: any;
    //Don't forget to inject service to access data from service
    static $inject = ['CommonService']
    constructor(private commonService: CommonService) { }
    public getDataFromService() {
        this.dataInCtrl1 = this.commonService.sharedData;
    }
}
//add controller to module app
angular.module('app').controller('FirstController', FirstController);
export class SecondController {
    dataInCtrl2: any;
    static $inject = ['CommonService']
    constructor(private commonService: CommonService) { }
    public getDataFromService() {
        this.dataInCtrl2 = this.commonService.sharedData;
    }
}
angular.module('app').controller('SecondController', SecondController);

}

UniCoder
fonte