Czy kontroler AngularJS może dziedziczyć z innego kontrolera w tym samym module?

198

W ramach modułu kontroler może dziedziczyć właściwości z zewnętrznego kontrolera:

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

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

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

Przykład: Dead link : http://blog.omkarpatil.com/2013/02/controller-inheritance-in-angularjs.html

Czy kontroler wewnątrz modułu może dziedziczyć od rodzeństwa?

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

Drugi kod nie działa, ponieważ $injector.invokewymaga funkcji jako pierwszego parametru i nie znajduje odniesienia do ParentCtrl.

Federico Elles
źródło
2
na bok: nie wygląda to na dziedziczenie, ale bardziej na dzielenie się metodami lub zastrzyki. Może po prostu semantyka.
alockwood05
Link do przykładu nie jest już prawidłowy.
AlexS
Link do pamięci podręcznej Google: webcache.googleusercontent.com/…, który wskazuje na ten interesujący Fiddle: jsfiddle.net/mhevery/u6s88/12
Federico Elles

Odpowiedzi:

289

Tak, może, ale $controllerzamiast tego musisz użyć usługi, aby utworzyć instancję kontrolera: -

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
źródło
ParentCtrlpowinno być a controllerczy jest możliwe użycie service?
gontard
@gontard: W tym przypadku musi to być kontroler, ponieważ $controllermogą używać tylko zarejestrowanych kontrolerów.
ZeissS
10
To bardzo dobre rozwiązanie. Dziękuję Ci. Ale jak mam to zrobić, jeśli używam składni kontrolera jako?
Do Ka
1
Powyższe skrzypce zostało zadane jako pytanie. Warto zauważyć, że controllerAs prostu przypisuje kontroler do zakresu - Tak byś zmienił $scopesię this(w teorii)
Dan Spiżarnia
4
To działało dla mnie, ale próbuję to zrobić w taki sposób, aby kontroler nadrzędny i kontroler podrzędny znajdowały się na tej samej stronie. Powoduje to dwukrotne uruchomienie operacji $ http w kontrolerze nadrzędnym. Kiedy kontroler podrzędny wstrzykuje zakres kontrolera nadrzędnego, moja tablica $ scope zostaje wypełniona dwukrotnie, ponieważ kontroler nadrzędny powoduje, że jest uruchomiony, a następnie kontroler podrzędny powoduje, że uruchamia się ponownie. Czy można temu zapobiec?
Ryan Mann,
20

Jeśli używasz vmskładni kontrolera , oto moje rozwiązanie:

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

Niestety, nie można użyć $controller.call(vm, 'BaseGenericCtrl'...), aby przekazać bieżący kontekst do funkcji zamknięcia (dla reload()), dlatego tylko jednym rozwiązaniem jest użycie thisfunkcji dziedziczonej w celu dynamicznej zmiany kontekstu.

IProblemFactory
źródło
Czy nie możesz tego właśnie zrobić? > $ controller („BaseGenericControl”, {vm: vm});
herringtown
vmjest tylko zmienną wewnątrz kontrolera, nie sądzę, żeby Angular mógł użyć go zgodnie z oczekiwaniami.
IProblemFactory
8

Myślę, że powinieneś skorzystać z fabryki lub usługi, aby zapewnić dostępne funkcje lub dane dla obu kontrolerów.

tutaj jest podobne pytanie ---> Dziedziczenie kontrolera AngularJS

LauroSkr
źródło
Tak, to jeden sposób, dzięki. Natrafiłem na ten post, gdy szukałem rozwiązania. Zastanawiałem się, czy istnieje jakiś sposób na załadowanie funkcji kontrolera i rozszerzenie jej o to.
Do Ka
Chciałbym mieć uniwersalną loadingzmienną, aby podczas ładowania danych zawsze robiłem to samo, nie sądzę, aby fabryki to potrafiły. Mój kontroler nadrzędny może mieć zmienną ładowania, ale fabryka nie może nią manipulować ... prawda ?!
PixMach
7

W odpowiedzi na problem podniesiony w tej odpowiedzi przez gmontague znalazłem metodę dziedziczenia kontrolera za pomocą $ controller () i nadal używam kontrolera jako składni.

Po pierwsze, używaj składni „as”, gdy dziedziczysz wywoływanie $ 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);

    });

Następnie w szablonie HTML, jeśli właściwość jest zdefiniowana przez element nadrzędny, użyj przycisku, parent.aby pobrać właściwości odziedziczone od elementu nadrzędnego; jeśli zdefiniowane przez dziecko, użyj go, child.aby je odzyskać.

    <div ng-controller="ChildCtrl as child">{{ parent.foo }}</div>
gm2008
źródło
5

Zrobiłem to w inny sposób. W moim przypadku chciałem funkcji, która zastosuje te same funkcje i właściwości w innych kontrolerach. Podobało mi się, oprócz parametrów. W ten sposób wszystkie twoje ChildCtrls muszą otrzymać $ 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
źródło
4

Dla tych, którzy zastanawiają się, możesz rozszerzyć kontrolery komponentów w ten sam sposób, używając metody z zaakceptowanej odpowiedzi.

Zastosuj następujące podejście:

Komponent macierzysty (z którego można rozszerzyć):

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

Komponent potomny (ten rozszerzający się):

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

Sztuką jest używanie nazwanych kontrolerów zamiast definiowania ich w definicji komponentu.

Adam Reis
źródło
2

Jak wspomniano w zaakceptowanej odpowiedzi, możesz „odziedziczyć” modyfikacje kontrolera nadrzędnego do zakresu $ i innych usług, dzwoniąc: $controller('ParentCtrl', {$scope: $scope, etc: etc});w kontrolerze podrzędnym.

Nie powiedzie się to jednak , jeśli przyzwyczaiłeś się używać składni kontrolera „as”, na przykład w

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

Jeśli foozostał ustawiony w kontrolerze nadrzędnym (przez this.foo = ...), kontroler podrzędny nie będzie miał do niego dostępu.

Jak wspomniano w komentarzach, możesz przypisać wynik $ kontrolera bezpośrednio do zakresu:

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

Uwaga: Następnie należy usunąć część „jako” ng-controller=, ponieważ określasz nazwę instancji w kodzie, a nie szablon.

Gmontague
źródło
Używanie składni „kontroler jako” nie stanowi problemu. Zobacz moją odpowiedź: stackoverflow.com/a/36549465/2197555
gm2008
2

Używałem składni „Kontroler jako” vm = thisi chciałem odziedziczyć kontroler. Miałem problemy, jeśli mój kontroler nadrzędny miał funkcję, która modyfikowała zmienną.

Korzystając z odpowiedzi IProblemFactory i Salman Abbas , wykonałem następujące czynności, aby uzyskać dostęp do zmiennych nadrzędnych:

(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
źródło
0

Możesz użyć prostego mechanizmu dziedziczenia JavaScript. Nie zapomnij również przekazać potrzebnych usług kątowych, aby wywołać metodę .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
źródło