¿Cuál es la forma recomendada de extender los controladores AngularJS?

193

Tengo tres controladores que son bastante similares. Quiero tener un controlador que estos tres extiendan y compartan sus funciones.

vladexologija
fuente

Respuestas:

302

Tal vez usted no se extienden un controlador, pero es posible extender un controlador o hacer un solo controlador mixin de varios controladores.

module.controller('CtrlImplAdvanced', ['$scope', '$controller', function ($scope, $controller) {
    // Initialize the super class and extend it.
    angular.extend(this, $controller('CtrlImpl', {$scope: $scope}));
     Additional extensions to create a mixin.
}]);

Cuando se crea el controlador principal, también se ejecuta la lógica que contiene. Consulte $ controller () para obtener más información, pero solo se $scopedebe pasar el valor. Todos los demás valores se inyectarán normalmente.

@mwarren , su preocupación es atendida automáticamente por la inyección de dependencia angular. Todo lo que necesita es inyectar $ scope, aunque podría anular los otros valores inyectados si lo desea. Tome el siguiente ejemplo:

(function(angular) {

	var module = angular.module('stackoverflow.example',[]);

	module.controller('simpleController', function($scope, $document) {
		this.getOrigin = function() {
			return $document[0].location.origin;
		};
	});

	module.controller('complexController', function($scope, $controller) {
		angular.extend(this, $controller('simpleController', {$scope: $scope}));
	});

})(angular);
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular.js"></script>

<div ng-app="stackoverflow.example">
    <div ng-controller="complexController as C">
        <span><b>Origin from Controller:</b> {{C.getOrigin()}}</span>
    </div>
</div>

Aunque $ document no se pasa a 'simpleController' cuando es creado por 'complexController', se inyecta $ document para nosotros.

Enzey
fuente
1
¡Con mucho, la solución más rápida, limpia y fácil para esto! ¡Gracias!
MK
¡solución perfecta e increíble!
Kamil Lach
8
Creo que no necesitas eso $.extend(), simplemente puedes llamar$controller('CtrlImpl', {$scope: $scope});
tomraithel
55
@tomraithel no usar angular.extend(o $.extend) en realidad significa extender el $scopeúnico, pero si su controlador base también define algunas propiedades (por ejemplo this.myVar=5), solo tiene acceso this.myVaren el controlador de extensión cuando usaangular.extend
schellmax
1
¡Esto es genial! Solo asegúrese de recordar extender todas las funciones necesarias, es decir, tenía algo como: handleSubmitClickque llamaría handleLogina su vez que tenía un loginSuccessy loginFail. Por lo tanto, en mi controlador extendida después tuve sobrecargar el handleSubmitClick, handleLoginy loginSucesspara la correcta loginSuccessfunción que se utilizará.
Justin Kruse
52

Para la herencia, puede usar patrones de herencia JavaScript estándar. Aquí hay una demostración que usa$injector

function Parent($scope) {
  $scope.name = 'Human';
  $scope.clickParent = function() {
    $scope.name = 'Clicked from base controller';
  }    
}

function Child($scope, $injector) {
  $injector.invoke(Parent, this, {$scope: $scope});
  $scope.name = 'Human Child';
  $scope.clickChild = function(){
    $scope.clickParent();
  }       
}

Child.prototype = Object.create(Parent.prototype);

En caso de que use la controllerAssintaxis (que recomiendo), es aún más fácil usar el patrón de herencia clásico:

function BaseCtrl() {
  this.name = 'foobar';
}
BaseCtrl.prototype.parentMethod = function () {
  //body
};

function ChildCtrl() {
  BaseCtrl.call(this);
  this.name = 'baz';
}
ChildCtrl.prototype = Object.create(BaseCtrl.prototype);
ChildCtrl.prototype.childMethod = function () {
  this.parentMethod();
  //body
};

app.controller('BaseCtrl', BaseCtrl);
app.controller('ChildCtrl', ChildCtrl);

Otra forma podría ser crear una función de constructor "abstracta" que será su controlador base:

function BaseController() {
  this.click = function () {
    //some actions here
  };
}

module.controller('ChildCtrl', ['$scope', function ($scope) {
  BaseController.call($scope);
  $scope.anotherClick = function () {
    //other actions
  };
}]);

Publicación de blog sobre este tema

Minko Gechev
fuente
16

Bueno, no estoy exactamente seguro de lo que quiere lograr, pero generalmente los Servicios son el camino a seguir. También puede usar las características de herencia Scope de Angular para compartir código entre controladores:

<body ng-controller="ParentCtrl">
 <div ng-controller="FirstChildCtrl"></div>
 <div ng-controller="SecondChildCtrl"></div>
</body>

function ParentCtrl($scope) {
 $scope.fx = function() {
   alert("Hello World");
 });
}

function FirstChildCtrl($scope) {
  // $scope.fx() is available here
}

function SecondChildCtrl($scope) {
  // $scope.fx() is available here
}
Andre Goncalves
fuente
Comparta las mismas variables y funciones entre los controladores que hacen cosas similares (una es para editar, otra para crear, etc.). Esta es definitivamente una de las soluciones ...
vladexologija
1
La herencia de $ scope es, con mucho, la mejor manera de hacerlo, mucho mejor que la respuesta aceptada.
snez
Al final, este parecía el camino más angular. Tengo tres parentControllers muy breves en tres páginas diferentes que solo establecen un valor $ scope que el childController puede recoger. El childController, que originalmente pensé en extender, contiene toda la lógica del controlador.
mwarren
no $scope.$parent.fx( ) sería una forma mucho más limpia de hacerlo, ya que es ahí donde realmente se define?
Akash
15

No extiendes los controladores. Si realizan las mismas funciones básicas, entonces esas funciones deben trasladarse a un servicio. Ese servicio puede inyectarse en sus controladores.

Bart
fuente
3
Gracias, pero tengo 4 funciones que ya usan servicios (guardar, eliminar, etc.) y existen en los tres controladores. Si extender no es una opción, ¿existe la posibilidad de un 'mixin'?
vladexologija
44
@vladexologija Estoy de acuerdo con Bart aquí. Creo que los servicios son mixin. Debe intentar mover la mayor parte de la lógica fuera del controlador como sea posible (a los servicios). Entonces, si tiene 3 controladores que necesitan realizar tareas similares, entonces un servicio parece ser el enfoque correcto. Los controladores extensibles no se sienten naturales en Angular.
ganaraj
66
@vladexologija Aquí hay un ejemplo de lo que quiero decir: jsfiddle.net/ERGU3 Es muy básico, pero entenderás la idea.
Bart
3
La respuesta es bastante inútil sin ningún argumento y explicación adicional. También creo que de alguna manera extrañas el punto del OP. Ya existe un servicio compartido. Lo único que debe hacer es exponer directamente ese servicio. No sé si es una buena idea. Su enfoque también falla si se necesita acceso al alcance. Pero siguiendo su razonamiento, expondría explícitamente el alcance como una propiedad del alcance a la vista para que pueda pasarse como un argumento.
un mejor oliver
66
Un ejemplo clásico sería cuando tiene dos informes basados ​​en formularios en su sitio, cada uno de los cuales depende de muchos de los mismos datos, y cada uno de los cuales utiliza muchos servicios compartidos. Teóricamente, podría intentar poner todos sus servicios separados en un gran servicio con docenas de llamadas AJAX, y luego tener métodos públicos como 'getEverythingINeedForReport1' y 'getEverythingINeedForReport2', y configurarlo todo en un objeto de alcance gigantesco, pero luego está realmente poner lo que es esencialmente la lógica del controlador en su servicio. La extensión de los controladores tiene absolutamente un caso de uso en algunas circunstancias.
tobylaroni
10

Otra buena solución tomada de este artículo :

// base controller containing common functions for add/edit controllers
module.controller('Diary.BaseAddEditController', function ($scope, SomeService) {
    $scope.diaryEntry = {};

    $scope.saveDiaryEntry = function () {
        SomeService.SaveDiaryEntry($scope.diaryEntry);
    };

    // add any other shared functionality here.
}])

module.controller('Diary.AddDiaryController', function ($scope, $controller) {
    // instantiate base controller
    $controller('Diary.BaseAddEditController', { $scope: $scope });
}])

module.controller('Diary.EditDiaryController', function ($scope, $routeParams, DiaryService, $controller) {
    // instantiate base controller
    $controller('Diary.BaseAddEditController', { $scope: $scope });

    DiaryService.GetDiaryEntry($routeParams.id).success(function (data) {
        $scope.diaryEntry = data;
    });
}]);
Nikita Koksharov
fuente
1
Esto funcionó extremadamente bien para mí. Tiene la ventaja de una fácil refactorización a partir de una situación en la que comenzó con un controlador, creó otro muy similar y luego quiso hacer que el código se seque. No necesita cambiar el código, simplemente extráigalo y listo.
Eli Albert
7

Puede crear un servicio y heredar su comportamiento en cualquier controlador con solo inyectarlo.

app.service("reusableCode", function() {

    var reusableCode = {};

    reusableCode.commonMethod = function() {
        alert('Hello, World!');
    };

    return reusableCode;
});

Luego, en su controlador que desea extender desde el servicio reusableCode anterior:

app.controller('MainCtrl', function($scope, reusableCode) {

    angular.extend($scope, reusableCode);

    // now you can access all the properties of reusableCode in this $scope
    $scope.commonMethod()

});

DEMO PLUNKER: http://plnkr.co/edit/EQtj6I0X08xprE8D0n5b?p=preview

Raghavendra
fuente
5

Puedes probar algo como esto (no lo has probado):

function baseController(callback){
    return function($scope){
        $scope.baseMethod = function(){
            console.log('base method');
        }
        callback.apply(this, arguments);
    }
}

app.controller('childController', baseController(function(){

}));
karaxuna
fuente
1
Sip. No es necesario extender, solo invoca con el contexto
TaylorMac
4

Puede ampliar con un servicio , fábricas o proveedores . son iguales pero con diferente grado de flexibilidad.

Aquí un ejemplo usando factory: http://jsfiddle.net/aaaflyvw/6KVtj/2/

angular.module('myApp',[])

.factory('myFactory', function() {
    var myFactory = {
        save: function () {
            // saving ...
        },
        store: function () {
            // storing ...
        }
    };
    return myFactory;
})

.controller('myController', function($scope, myFactory) {
    $scope.myFactory = myFactory;
    myFactory.save(); // here you can use the save function
});

Y aquí también puede usar la función de tienda:

<div ng-controller="myController">
    <input ng-blur="myFactory.store()" />
</div>
aaafly
fuente
4

Puede usar directamente $ controller ('ParentController', {$ scope: $ scope}) Ejemplo

module.controller('Parent', ['$scope', function ($scope) {
    //code
}])

module.controller('CtrlImplAdvanced', ['$scope', '$controller', function ($scope, $controller) {
    //extend parent controller
    $controller('CtrlImpl', {$scope: $scope});
}]);
Anand Gargate
fuente
1

Escribí una función para hacer esto:

function extendController(baseController, extension) {
    return [
        '$scope', '$injector',
        function($scope, $injector) {
            $injector.invoke(baseController, this, { $scope: $scope });
            $injector.invoke(extension, this, { $scope: $scope });
        }
    ]
}

Puedes usarlo así:

function() {
    var BaseController = [
        '$scope', '$http', // etc.
        function($scope, $http, // etc.
            $scope.myFunction = function() {
                //
            }

            // etc.
        }
    ];

    app.controller('myController',
        extendController(BaseController,
            ['$scope', '$filter', // etc.
            function($scope, $filter /* etc. */)
                $scope.myOtherFunction = function() {
                    //
                }

                // etc.
            }]
        )
    );
}();

Pros:

  1. No tiene que registrar el controlador base.
  2. Ninguno de los controladores necesita saber acerca de los servicios $ controller o $ injector.
  3. Funciona bien con la sintaxis de inyección de matriz de angular, que es esencial si su JavaScript se va a minimizar.
  4. Puede agregar fácilmente servicios inyectables adicionales al controlador base, sin tener que acordarse de agregarlos y pasarlos desde todos sus controladores secundarios.

Contras:

  1. El controlador base debe definirse como una variable, que corre el riesgo de contaminar el alcance global. He evitado esto en mi ejemplo de uso envolviendo todo en una función de ejecución automática anónima, pero esto significa que todos los controladores secundarios deben declararse en el mismo archivo.
  2. Este patrón funciona bien para los controladores que se crean instancias directamente desde su html, pero no es tan bueno para los controladores que cree a partir de su código a través del servicio $ controller (), porque su dependencia del inyector le impide inyectar directamente extra, no -parámetros de servicio de su código de llamada.
Dan King
fuente
1

Considero extender los controladores como una mala práctica. Más bien ponga su lógica compartida en un servicio. Los objetos extendidos en JavaScript tienden a ser bastante complejos. Si desea utilizar la herencia, le recomendaría mecanografiado. Aún así, los controladores delgados son la mejor manera de ir desde mi punto de vista.

Brecht Billiet
fuente