Qual é a maneira recomendada de estender os controladores AngularJS?

193

Eu tenho três controladores que são bastante semelhantes. Eu quero ter um controlador que esses três estendam e compartilhem suas funções.

vladexologija
fonte

Respostas:

302

Talvez você não estenda um controlador, mas é possível estender um controlador ou transformar um único controlador em uma mistura de vários 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.
}]);

Quando o controlador pai é criado, a lógica contida nele também é executada. Veja $ controller () para obter mais informações sobre, mas apenas os$scope valor precisa ser passado. Todos os outros valores serão injetados normalmente.

@warwar , sua preocupação é resolvida automaticamente por injeção de dependência angular. Tudo o que você precisa é injetar $ scope, embora você possa substituir os outros valores injetados, se desejar. Veja o seguinte exemplo:

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

Embora $ document não seja passado para 'simpleController' quando é criado por 'complexController', o documento $ é injetado para nós.

Enzey
fonte
1
De longe, a solução mais rápida, limpa e fácil para isso! Obrigado!
MK
solução perfeita e incrível!
Kamil Lach
8
Eu acho que você não precisa que $.extend(), você pode simplesmente chamar$controller('CtrlImpl', {$scope: $scope});
tomraithel
5
@tomraithel não usar angular.extend(ou $.extend) na verdade significa estender a $scopeúnica, mas se o seu controlador de base também define algumas propriedades (por exemplo this.myVar=5), você só tem acesso a this.myVarno controlador estendendo ao usarangular.extend
schellmax
1
Isso é ótimo! Apenas lembre-se de estender todas as funções necessárias, ou seja, eu tinha algo como: o handleSubmitClickque chamaria o handleLoginque, por sua vez, tinha a loginSuccesse loginFail. Então, no meu controlador estendida Então eu tive que sobrecarregar o handleSubmitClick, handleLogine loginSucesspara a correcta loginSuccessfunção a ser usada.
Justin Kruse
52

Para herança, você pode usar padrões de herança JavaScript padrão. Aqui está uma demonstração 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);

Caso você use a controllerAssintaxe (que eu recomendo), é ainda mais fácil usar o padrão de herança clássico:

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

Outra maneira poderia ser criar apenas a função construtora "abstrata", que será seu controlador base:

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

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

Postagem no blog sobre este tópico

Minko Gechev
fonte
16

Bem, não sei exatamente o que você deseja alcançar, mas geralmente os Serviços são o caminho a percorrer. Você também pode usar as características de herança Scope do Angular para compartilhar 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 Gonçalves
fonte
Compartilhe as mesmas variáveis ​​e funções entre os controladores que fazem coisas semelhantes (uma é para edição, outra para criação, etc ...). Esta é definitivamente uma das soluções ...
vladexologija
1
A herança de $ scope é de longe a melhor maneira de fazê-lo, muito melhor do que a resposta aceita.
snez
No final, parecia o caminho mais angular a seguir. Eu tenho três parentControllers muito breves em três páginas diferentes que apenas definem um valor de $ scope que o childController pode pegar. O childController, que originalmente pensei em estender, contém toda a lógica do controlador.
mwarren
não $scope.$parent.fx( ) seria uma maneira muito mais limpa de fazê-lo, pois é onde é realmente definido?
Akash
15

Você não estende controladores. Se eles executam as mesmas funções básicas, essas funções precisam ser movidas para um serviço. Esse serviço pode ser injetado nos seus controladores.

Bart
fonte
3
Obrigado, mas tenho 4 funções que já usam serviços (salvar, excluir, etc.) e as mesmas existem nos três controladores. Se estender não é uma opção, existe a possibilidade de um 'mixin'?
vladexologija
4
@vladexologija Eu concordo com o Bart aqui. Eu acho que os serviços são de mixin. Você deve tentar mover o máximo possível da lógica do controlador (para serviços). Portanto, se você possui três controladores que precisam executar tarefas semelhantes, um serviço parece ser a abordagem correta a ser adotada. Controladores de extensão não parecem naturais no Angular.
21313 Ganaraj
6
@vladexologija Aqui está um exemplo do que quero dizer: jsfiddle.net/ERGU3 É muito básico, mas você entenderá.
Bart
3
A resposta é bastante inútil, sem argumentos e explicações adicionais. Eu também acho que você perdeu um pouco o argumento do OP. Já existe um serviço compartilhado. A única coisa que você faz é expor diretamente esse serviço. Não sei se é uma boa ideia. Sua abordagem também falha se o acesso ao escopo for necessário. Mas, seguindo o seu raciocínio, eu explicitamente exporia o escopo como uma propriedade do escopo à visualização, para que possa ser passado como argumento.
um oliver melhor
6
Um exemplo clássico seria quando você tem dois relatórios orientados a formulários em seu site, cada um dos quais depende de muitos dos mesmos dados e cada um deles usa muitos serviços compartilhados. Teoricamente, você pode tentar colocar todos os seus serviços separados em um grande serviço com dezenas de chamadas AJAX e, em seguida, ter métodos públicos como 'getEverythingINeedForReport1' e 'getEverythingINeedForReport2' e configurá-lo como um objeto de escopo gigantesco, mas então você está realmente colocando o que é essencialmente lógica do controlador em seu serviço. A extensão dos controladores tem absolutamente um caso de uso em algumas circunstâncias.
tobylaroni
10

Ainda outra boa solução retirada deste artigo :

// 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
fonte
1
Isso funcionou muito bem para mim. Ele tem a vantagem de facilitar a refatoração a partir de uma situação em que você começou com um controlador, criou outro muito semelhante e depois quis tornar o código mais seco. Você não precisa alterar o código, basta retirá-lo e pronto.
Eli Albert
7

Você pode criar um serviço e herdar seu comportamento em qualquer controlador apenas injetando-o.

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

    var reusableCode = {};

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

    return reusableCode;
});

Em seu controlador, você deseja estender o serviço reusableCode acima:

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
fonte
5

Você pode tentar algo como isto (não testei):

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

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

}));
karaxuna
fonte
1
Sim. Não há necessidade de estender, apenas invocação com o contexto
TaylorMac
4

Você pode estender com serviços , fábricas ou fornecedores . eles são os mesmos, mas com diferentes graus de flexibilidade.

aqui um exemplo 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
});

E aqui você também pode usar a função de armazenamento:

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

Você pode usar diretamente $ controller ('ParentController', {$ scope: $ scope}) Exemplo

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

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

Eu escrevi uma função para fazer isso:

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

Você pode usá-lo assim:

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

Prós:

  1. Você não precisa registrar o controlador base.
  2. Nenhum dos controladores precisa saber sobre os serviços $ controller ou $ injector.
  3. Funciona bem com a sintaxe de injeção de matriz do angular - o que é essencial se o seu javascript for reduzido.
  4. Você pode adicionar facilmente serviços injetáveis ​​extras ao controlador base, sem precisar se lembrar de adicioná-los e transmiti-los a todos os controladores filhos.

Contras:

  1. O controlador base deve ser definido como uma variável, que corre o risco de poluir o escopo global. Evitei isso no meu exemplo de uso, agrupando tudo em uma função de execução automática anônima, mas isso significa que todos os controladores filhos precisam ser declarados no mesmo arquivo.
  2. Esse padrão funciona bem para controladores que são instanciados diretamente do seu html, mas não é tão bom para os controladores que você cria a partir do seu código através do serviço $ controller (), porque sua dependência do injetor impede a injeção direta de extra, não parâmetros de serviço do seu código de chamada.
Dan King
fonte
1

Considero estender os controladores como uma má prática. Em vez disso, coloque sua lógica compartilhada em um serviço. Objetos estendidos em javascript tendem a ficar bastante complexos. Se você deseja usar herança, eu recomendaria texto datilografado. Ainda assim, controladores finos são a melhor maneira de seguir meu ponto de vista.

Brecht Billiet
fonte