Um controlador AngularJS pode herdar de outro controlador no mesmo módulo?

198

Dentro de um módulo, um controlador pode herdar propriedades de um controlador externo:

var app = angular.module('angularjs-starter', []);

var ParentCtrl = function ($scope, $location) {
};

app.controller('ChildCtrl', function($scope, $injector) {
  $injector.invoke(ParentCtrl, this, {$scope: $scope});
});

Exemplo via: Dead link : http://blog.omkarpatil.com/2013/02/controller-inheritance-in-angularjs.html

Um controlador dentro de um módulo também pode herdar de um irmão?

var app = angular.module('angularjs-starter', []);

app.controller('ParentCtrl ', function($scope) {
  //I'm the sibling, but want to act as parent
});

app.controller('ChildCtrl', function($scope, $injector) {
  $injector.invoke(ParentCtrl, this, {$scope: $scope}); //This does not work
});

O segundo código não funciona, pois $injector.invokerequer uma função como primeiro parâmetro e não encontra a referência ParentCtrl.

Federico Elles
fonte
2
aparte: isso não parece herança, mas sim compartilhamento de métodos ou injeção. Talvez apenas semântica.
precisa saber é o seguinte
O link para o exemplo não é mais válido.
Alexs
Google Cache Link: webcache.googleusercontent.com/... quais os pontos a esta interessante Fiddle: jsfiddle.net/mhevery/u6s88/12
Federico Elles

Respostas:

289

Sim, mas você pode usar o $controllerserviço para instanciar o controlador: -

var app = angular.module('angularjs-starter', []);

app.controller('ParentCtrl', function($scope) {
  // I'm the sibling, but want to act as parent
});

app.controller('ChildCtrl', function($scope, $controller) {
  $controller('ParentCtrl', {$scope: $scope}); //This works
});
Salman von Abbas
fonte
ParentCtrldeve ser um controllerou é possível usar um service?
Gontard
@ gontard: Nesse caso, ele deve ser um controlador, pois $controllersó pode usar controladores registrados.
ZeissS 13/03
10
É uma solução muito boa. Obrigado. Mas como eu faria isso no caso de usar a sintaxe Controller As?
Para Ka
1
O violino acima foi feito como uma pergunta. É importante notar que controllerAs simplesmente atribui o controlador para o alcance - Então você mudaria $scopepara this(em teoria)
Dan Pantry
4
Isso funcionou para mim, no entanto, estou tentando fazer isso de uma maneira que tenha o controlador pai e o controlador filho na mesma página. Isso faz com que a operação $ http no controlador pai seja executada duas vezes. Quando o controlador filho injeta o escopo do controlador pai, minha matriz $ scope.AllMembers é preenchida duas vezes quando o controlador pai faz com que ele seja executado, e o controlador filho faz com que ele seja executado novamente. Existe alguma maneira de evitar isso?
Ryan Mann
20

Caso você esteja usando a vmsintaxe do controlador , aqui está minha solução:

.controller("BaseGenericCtrl", function ($scope) {

    var vm = this;
    vm.reload = reload;
    vm.items = [];

    function reload() {
        // this function will come from child controller scope - RESTDataService.getItemsA
        this.getItems();
    }
})

.controller("ChildCtrl", function ($scope, $controller, RESTDataService) {
    var vm = this;
    vm.getItems = RESTDataService.getItemsA;
    angular.extend(vm, $controller('BaseGenericCtrl', {$scope: $scope}));
})

Infelizmente, você não pode usar $controller.call(vm, 'BaseGenericCtrl'...), para passar o contexto atual para a reload()função de fechamento (for ), portanto, apenas uma solução é usar a thisfunção herdada dentro para alterar dinamicamente o contexto.

IProblemFactory
fonte
Você não poderia ter acabado de fazer isso? > $ controller ('BaseGenericControl', {vm: vm});
herringtown
vmé apenas uma variável dentro do controlador, acho que o Angular não pode usá-lo como esperado.
IProblemFactory
8

Eu acho que você deve usar fábrica ou serviço, para fornecer funções ou dados acessíveis para os dois controladores.

aqui está pergunta semelhante ---> herança do controlador AngularJS

LauroSkr
fonte
Sim, esse é um caminho, obrigado. Me deparei com esse post quando estava procurando uma solução. Eu estava pensando se havia alguma maneira de carregar a função do controlador e estender "isso" com ele.
Para Ka
Eu gostaria de ter uma loadingvariável universal para que, quando os dados sejam carregados, eu sempre faça a mesma coisa, não acho que as fábricas possam fazer isso. Meu controlador pai pode ter uma variável de carregamento, mas a fábrica não pode manipulá-la ... certo ?!
PixMach 15/09/2015
7

Em resposta à questão levantada em nesta resposta por gmontague , encontrei um método para herdar um controlador usando $ controller () e ainda utilizo o controlador "como" sintaxe.

Primeiramente, use a sintaxe "as" ao herdar a chamada de $ controller ():

    app.controller('ParentCtrl', function(etc...) {
        this.foo = 'bar';
    });
    app.controller('ChildCtrl', function($scope, $controller, etc...) {
        var ctrl = $controller('ParentCtrl as parent', {etc: etc, ...});
        angular.extend(this, ctrl);

    });

Em seguida, no modelo HTML, se a propriedade for definida pelo pai, use parent.para recuperar propriedades herdadas do pai; se definido por filho, use-o child.para recuperá-lo.

    <div ng-controller="ChildCtrl as child">{{ parent.foo }}</div>
gm2008
fonte
5

Bem, eu fiz isso de outra maneira. No meu caso, eu queria uma função que aplique as mesmas funções e propriedades em outros controladores. Eu gostei, exceto pelos parâmetros. Dessa forma, todos os seus ChildCtrls precisam receber $ location.

var app = angular.module('angularjs-starter', []);

function BaseCtrl ($scope, $location) {
    $scope.myProp = 'Foo';
    $scope.myMethod = function bar(){ /* do magic */ };
}

app.controller('ChildCtrl', function($scope, $location) {
    BaseCtrl.call(this, $scope, $location);

    // it works
    $scope.myMethod();
});
Fabio Montefuscolo
fonte
4

Para aqueles que se perguntam, você pode estender os controladores de componentes da mesma maneira, usando o método na resposta aceita.

Use a seguinte abordagem:

Componente pai (para estender):

/**
 * Module definition and dependencies
 */
angular.module('App.Parent', [])

/**
 * Component
 */
.component('parent', {
  templateUrl: 'parent.html',
  controller: 'ParentCtrl',
})

/**
 * Controller
 */
.controller('ParentCtrl', function($parentDep) {

  //Get controller
  const $ctrl = this;

  /**
   * On init
   */
  this.$onInit = function() {

    //Do stuff
    this.something = true;
  };
});

Componente filho (aquele que se estende):

/**
 * Module definition and dependencies
 */
angular.module('App.Child', [])

/**
 * Component
 */
.component('child', {
  templateUrl: 'child.html',
  controller: 'ChildCtrl',
})

/**
 * Controller
 */
.controller('ChildCtrl', function($controller) {

  //Get controllers
  const $ctrl = this;
  const $base = $controller('ParentCtrl', {});
  //NOTE: no need to pass $parentDep in here, it is resolved automatically
  //if it's a global service/dependency

  //Extend
  angular.extend($ctrl, $base);

  /**
   * On init
   */
  this.$onInit = function() {

    //Call parent init
    $base.$onInit.call(this);

    //Do other stuff
    this.somethingElse = true;
  };
});

O truque é usar controladores nomeados, em vez de defini-los na definição do componente.

Adam Reis
fonte
2

Como mencionado na resposta aceita, você pode "herdar" as modificações de um controlador pai no $ scope e outros serviços chamando: $controller('ParentCtrl', {$scope: $scope, etc: etc});no seu controlador filho.

No entanto , isso falhará se você estiver acostumado a usar a sintaxe do controlador 'como', por exemplo, em

<div ng-controller="ChildCtrl as child">{{ child.foo }}</div>

Se foofoi definido no controlador pai (viathis.foo = ... ), o controlador filho não terá acesso a ele.

Conforme mencionado nos comentários, você pode atribuir o resultado de $ controller diretamente ao escopo:

var app = angular.module('angularjs-starter', []);
app.controller('ParentCtrl ', function(etc...) {
    this.foo = 'bar';
});
app.controller('ChildCtrl', function($scope, $controller, etc...) {
    var inst = $controller('ParentCtrl', {etc: etc, ...});

    // Perform extensions to inst
    inst.baz = inst.foo + " extended";

    // Attach to the scope
    $scope.child = inst;
});

Nota: Você deve remover a parte 'como' de ng-controller=, porque está especificando o nome da instância no código e não mais o modelo.

Gmontague
fonte
Usar a sintaxe "controller as" não tem problema. Veja minha resposta: stackoverflow.com/a/36549465/2197555
gm2008
2

Eu estava usando a sintaxe "Controller as" com vm = this e queria herdar um controlador. Eu tive problemas se meu controlador pai tivesse uma função que modificasse uma variável.

Usando as respostas de IProblemFactory e Salman Abbas , fiz o seguinte para ter acesso às variáveis ​​pai:

(function () {
  'use strict';
  angular
      .module('MyApp',[])
      .controller('AbstractController', AbstractController)
      .controller('ChildController', ChildController);

  function AbstractController(child) {
    var vm = child;
    vm.foo = 0;
    
    vm.addToFoo = function() {
      vm.foo+=1;
    }
  };
  
  function ChildController($controller) {
    var vm = this;
    angular.extend(vm, $controller('AbstractController', {child: vm}));
  };
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller="ChildController as childCtrl" layout="column" ng-cloak="" ng-app="MyApp">
  <button type="button" ng-click="childCtrl.addToFoo()">
    add
  </button>
  <span>
      -- {{childCtrl.foo}} --
  </span>
</div>

dufaux
fonte
0

Você pode usar um mecanismo simples de herança JavaScript. Além disso, não esqueça de passar um serviço angular necessário para invocar o método .call.

//simple function (js class)
function baseCtrl($http, $scope, $location, $rootScope, $routeParams, $log, $timeout, $window, modalService) {//any serrvices and your 2

   this.id = $routeParams.id;
   $scope.id = this.id;

   this.someFunc = function(){
      $http.get("url?id="+this.id)
      .then(success function(response){
        ....
       } ) 

   }
...
}

angular
        .module('app')
        .controller('childCtrl', childCtrl);

//angular controller function
function childCtrl($http, $scope, $location, $rootScope, $routeParams, $log, $timeout, $window, modalService) {      
   var ctrl = this;
   baseCtrl.call(this, $http, $scope, $location, $rootScope,  $routeParams, $log, $timeout, $window, modalService);

   var idCopy = ctrl.id;
   if($scope.id == ctrl.id){//just for sample
      ctrl.someFunc();
   }
}

//also you can copy prototype of the base controller
childCtrl.prototype = Object.create(baseCtrl.prototype);
trueboroda
fonte