¿Puede un controlador AngularJS heredar de otro controlador en el mismo módulo?

198

Dentro de un módulo, un controlador puede heredar propiedades de un 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});
});

Ejemplo vía: Enlace muerto : http://blog.omkarpatil.com/2013/02/controller-inheritance-in-angularjs.html

¿Puede un controlador dentro de un módulo heredar de un hermano?

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
});

El segundo código no funciona ya que $injector.invokerequiere una función como primer parámetro y no encuentra la referencia a ParentCtrl.

Federico Elles
fuente
Esto debería ayudar: stackoverflow.com/questions/16828287/…
Bart
2
a un lado: esto no parece herencia, sino más bien compartir métodos o inyectar. Quizás solo semántica.
alockwood05
El enlace para el ejemplo ya no es válido.
AlexS
Enlace de caché de Google: webcache.googleusercontent.com/… que apunta a este interesante violín: jsfiddle.net/mhevery/u6s88/12
Federico Elles

Respuestas:

289

Sí, pero puede usar el $controllerservicio para instanciar el controlador en su lugar: -

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
fuente
ParentCtrldebería ser controllero es posible usar un service?
gontard
@gontard: en este caso debe ser un controlador, ya $controllerque solo puede usar controladores registrados.
ZeissS
10
Es una muy buena solución. Gracias. Pero, ¿cómo lo haría en caso de que esté usando la sintaxis Controller As?
A Ka
1
El violín anterior se hizo como una pregunta. Vale la pena señalar que el controlador As simplemente asigna el controlador al alcance, por lo que cambiaría $scopea this(en teoría)
Dan Pantry
44
Esto funcionó para mí, sin embargo, estoy tratando de hacerlo de una manera que tengo el controlador principal y el controlador secundario en la misma página. Esto hace que la operación $ http en el controlador principal se ejecute dos veces. Cuando el controlador secundario inyecta el alcance del controlador primario, mi matriz $ $. ¿Hay alguna manera de evitar eso?
Ryan Mann
20

En caso de que esté utilizando la vmsintaxis del controlador , esta es mi solución:

.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}));
})

Desafortunadamente, no puede usar $controller.call(vm, 'BaseGenericCtrl'...), para pasar el contexto actual a la función de cierre (for reload()), por lo tanto, solo una solución es usar la thisfunción heredada interna para cambiar dinámicamente el contexto.

IProblemFactory
fuente
¿No podrías haber hecho esto en su lugar? > $ controller ('BaseGenericControl', {vm: vm});
herringtown
vmes solo una variable dentro del controlador, no creo que Angular pueda usarlo como se esperaba.
IProblemFactory
8

Creo que debe usar la fábrica o el servicio para proporcionar funciones o datos accesibles para ambos controladores.

Aquí hay una pregunta similar ---> Herencia del controlador AngularJS

LauroSkr
fuente
Sí, esa es una forma, gracias. Encontré esa publicación cuando estaba buscando una solución. Estaba pensando si había alguna forma de cargar la función del controlador y extender "esto" con ella.
A Ka
Me gustaría tener una loadingvariable universal para que cuando se carguen datos siempre haga lo mismo, no creo que las fábricas puedan hacer eso. Mi controlador principal puede tener una variable de carga, pero la fábrica no puede manipularlo ... ¿verdad?
PixMach
7

En respuesta al problema planteado en esta respuesta por gmontague , he encontrado un método para heredar un controlador usando $ controller (), y todavía uso la sintaxis del controlador "como".

En primer lugar, use la sintaxis "como" cuando herede la llamada a $ 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);

    });

Luego, en la plantilla HTML, si la propiedad está definida por el padre, se usa parent.para recuperar las propiedades heredadas del padre; si está definido por un niño, entonces úselo child.para recuperarlo

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

Bueno, hice esto de otra manera. En mi caso, quería una función que aplicara las mismas funciones y propiedades en otros controladores. Me gustó, excepto por parámetros. De esta manera, todos sus ChildCtrls necesitan recibir $ 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
fuente
4

Para aquellos que se preguntan, puede extender los controladores de componentes de la misma manera, utilizando el método en la respuesta aceptada.

Use el siguiente enfoque:

Componente principal (para extender desde):

/**
 * 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 hijo (el que se extiende):

/**
 * 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;
  };
});

El truco consiste en utilizar controladores con nombre, en lugar de definirlos en la definición del componente.

Adam Reis
fuente
2

Como se menciona en la respuesta aceptada, puede "heredar" las modificaciones de un controlador principal a $ scope y otros servicios llamando a: $controller('ParentCtrl', {$scope: $scope, etc: etc});en su controlador secundario.

Sin embargo , esto falla si está acostumbrado a usar la sintaxis del controlador 'como', por ejemplo en

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

Si foose configuró en el controlador principal (vía this.foo = ...), el controlador secundario no tendrá acceso a él.

Como se menciona en los comentarios, puede asignar el resultado de $ controller directamente al ámbito:

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: A continuación, debe eliminar la parte 'como' ng-controller=porque está especificando el nombre de la instancia en el código y ya no la plantilla.

Gmontague
fuente
El uso de la sintaxis "controlador como" no tiene ningún problema. Vea mi respuesta: stackoverflow.com/a/36549465/2197555
gm2008
2

Estaba usando la sintaxis "Controlador como" vm = thisy quería heredar un controlador. Tuve problemas si mi controlador principal tenía una función que modificaba una variable.

Utilizando las respuestas de IProblemFactory y Salman Abbas , hice lo siguiente para tener acceso a las variables principales:

(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
fuente
0

Puede usar un mecanismo simple de herencia de JavaScript. Además, no olvide pasar un servicio angular necesario para invocar el 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
fuente