Jak wstrzyknąć kontroler do innego kontrolera w AngularJS

97

Jestem nowy w Angular i próbuję wymyślić, jak to zrobić ...

Korzystając z AngularJS, w jaki sposób mogę wstrzyknąć kontroler do użycia w innym kontrolerze?

Mam następujący fragment:

var app = angular.module("testApp", ['']);

app.controller('TestCtrl1', ['$scope', function ($scope) {
    $scope.myMethod = function () {
        console.log("TestCtrl1 - myMethod");
    }
}]);

app.controller('TestCtrl2', ['$scope', 'TestCtrl1', function ($scope, TestCtrl1) {
    TestCtrl1.myMethod();
}]);

Kiedy to wykonuję, pojawia się błąd:

Error: [$injector:unpr] Unknown provider: TestCtrl1Provider <- TestCtrl1
http://errors.angularjs.org/1.2.21/$injector/unpr?p0=TestCtrl1Provider%20%3C-%20TestCtrl1

Czy powinienem nawet próbować użyć kontrolera w innym kontrolerze, czy powinienem uczynić to usługą?

Scottie
źródło
2
Nie możesz wstrzyknąć sobie kontrolerów. Tak, TestCtrl1zamiast tego powinieneś zmienić się w usługę.
Sly_cardinal
Dokładnie, korzystaj z usług
Miguel Mota,
3
co gdybym musiał zaktualizować właściwość kontrolera, który wiąże się z widokiem. Na tę właściwość ma wpływ zdarzenie zachodzące w innym kontrolerze.
Ankit Tanna

Odpowiedzi:

129

Jeśli Twoim zamiarem jest zdobycie już utworzonego kontrolera innego komponentu i jeśli stosujesz podejście oparte na komponencie / dyrektywie, zawsze możesz requiremieć kontroler (instancję komponentu) z innego komponentu, który jest zgodny z określoną hierarchią.

Na przykład:

//some container component that provides a wizard and transcludes the page components displayed in a wizard
myModule.component('wizardContainer', {
  ...,
  controller : function WizardController() {
    this.disableNext = function() { 
      //disable next step... some implementation to disable the next button hosted by the wizard
    }
  },
  ...
});

//some child component
myModule.component('onboardingStep', {
 ...,
 controller : function OnboadingStepController(){

    this.$onInit = function() {
      //.... you can access this.container.disableNext() function
    }

    this.onChange = function(val) {
      //..say some value has been changed and it is not valid i do not want wizard to enable next button so i call container's disable method i.e
      if(notIsValid(val)){
        this.container.disableNext();
      }
    }
 },
 ...,
 require : {
    container: '^^wizardContainer' //Require a wizard component's controller which exist in its parent hierarchy.
 },
 ...
});

Teraz użycie powyższych komponentów może wyglądać mniej więcej tak:

<wizard-container ....>
<!--some stuff-->
...
<!-- some where there is this page that displays initial step via child component -->

<on-boarding-step ...>
 <!--- some stuff-->
</on-boarding-step>
...
<!--some stuff-->
</wizard-container>

Wymaganie można skonfigurować na wiele sposobów .

(bez prefiksu) - zlokalizuj wymagany kontroler w bieżącym elemencie. Jeśli nie zostanie znaleziony, wyrzuć błąd.

? - Spróbuj znaleźć wymagany kontroler lub podaj wartość null do łącza fn, jeśli nie zostanie znaleziony.

^ - znajdź wymagany kontroler, wyszukując element i jego elementy nadrzędne. Jeśli nie zostanie znaleziony, wyrzuć błąd.

^^ - Znajdź wymagany kontroler, wyszukując rodziców elementu. Jeśli nie zostanie znaleziony, wyrzuć błąd.

? ^ - próba zlokalizowania wymaganego kontrolera poprzez przeszukanie elementu i jego elementów nadrzędnych lub przekazanie wartości null do łącza fn, jeśli nie zostanie znaleziony.

? ^^ - Próba zlokalizowania wymaganego kontrolera poprzez wyszukanie elementów nadrzędnych elementu lub przekazanie wartości null do łącza fn, jeśli nie zostanie znaleziony.



Stara odpowiedź:

Musisz wstrzyknąć $controllerusługę, aby utworzyć wystąpienie kontrolera wewnątrz innego kontrolera. Należy jednak pamiętać, że może to prowadzić do problemów projektowych. Zawsze możesz utworzyć usługi wielokrotnego użytku, które są zgodne z Single Responsibility i wstrzyknąć je do kontrolerów, gdy potrzebujesz.

Przykład:

app.controller('TestCtrl2', ['$scope', '$controller', function ($scope, $controller) {
   var testCtrl1ViewModel = $scope.$new(); //You need to supply a scope while instantiating.
   //Provide the scope, you can also do $scope.$new(true) in order to create an isolated scope.
   //In this case it is the child scope of this scope.
   $controller('TestCtrl1',{$scope : testCtrl1ViewModel });
   testCtrl1ViewModel.myMethod(); //And call the method on the newScope.
}]);

W każdym razie nie można wywołać, TestCtrl1.myMethod()ponieważ metoda została dołączona do $scopeinstancji kontrolera, a nie do.

Jeśli udostępniasz kontroler, zawsze lepiej byłoby zrobić: -

.controller('TestCtrl1', ['$log', function ($log) {
    this.myMethod = function () {
        $log.debug("TestCtrl1 - myMethod");
    }
}]);

i podczas konsumpcji:

.controller('TestCtrl2', ['$scope', '$controller', function ($scope, $controller) {
     var testCtrl1ViewModel = $controller('TestCtrl1');
     testCtrl1ViewModel.myMethod();
}]);

W pierwszym przypadku jest $scopeto twój model widoku, aw drugim jest to sama instancja kontrolera.

PSL
źródło
4
Zależy to od funkcjonalności zapewnianej przez kontroler.Jeśli robisz to bardziej jak model widoku, który musisz udostępnić w komponencie, to jest w porządku, ale jeśli jest to bardziej funkcjonalność dostawcy usług, po prostu chciałbym stworzyć usługę .
PSL
Powinien var testCtrl1ViewModel = $scope.$new();być var testCtrl1ViewModel = $rootScope.$new();? zobacz: docs.angularjs.org/guide/controller @PSL
leonsPAPA
W powyższym przykładzie uzyskujesz dostęp do kontenera na kontrolerze dyrektywy, ale nie mogę uruchomić tego. Mam dostęp do wymaganych kontrolerów poprzez czwarty parametr mojej funkcji łącza w samej dyrektywie. Ale nie są związani z kontrolerem dyrektywy, jak w powyższym przykładzie. Czy ktoś jeszcze ma ten problem?
Sammi
33

Sugerowałbym pytanie, które powinieneś zadać, to jak wstrzyknąć usługi do kontrolerów. Grube usługi z chudymi kontrolerami to dobra zasada, czyli po prostu użyj kontrolerów, aby przykleić usługę / fabrykę (z logiką biznesową) do swoich widoków.

Kontrolery zbierają elementy bezużyteczne przy zmianach tras, więc na przykład, jeśli używasz kontrolerów do przechowywania logiki biznesowej, która renderuje wartość, utracisz stan dwóch stron, jeśli użytkownik aplikacji kliknie przycisk Wstecz przeglądarki.

var app = angular.module("testApp", ['']);

app.factory('methodFactory', function () {
    return { myMethod: function () {
            console.log("methodFactory - myMethod");
    };
};

app.controller('TestCtrl1', ['$scope', 'methodFactory', function ($scope,methodFactory) {  //Comma was missing here.Now it is corrected.
    $scope.mymethod1 = methodFactory.myMethod();
}]);

app.controller('TestCtrl2', ['$scope', 'methodFactory', function ($scope, methodFactory) {
    $scope.mymethod2 = methodFactory.myMethod();
}]);

Oto działające demo fabryczne wstrzyknięte do dwóch kontrolerów

Sugerowałbym również przeczytanie tego samouczka na temat usług / fabryk.

bezczelny drań
źródło
13

Nie ma potrzeby importowania / wstrzykiwania kontrolera w JS. Możesz po prostu wstrzyknąć swój kontroler / zagnieżdżony kontroler za pośrednictwem kodu HTML. Lubić :

<div ng-controller="TestCtrl1">
    <div ng-controller="TestCtrl2">
      <!-- your code--> 
    </div> 
</div>
chetanpawar
źródło
2
prawda ... ale nadal czuję, że lepiej jest umieścić wszystkie typowe elementy w usłudze i wstrzyknąć usługę do odpowiedniego kontrolera.
Neel
-1
<div ng-controller="TestCtrl1">
    <div ng-controller="TestCtrl2">
      <!-- your code--> 
    </div> 
</div>

Działa to najlepiej w moim przypadku, gdzie TestCtrl2 ma swoje własne dyrektywy.

var testCtrl2 = $controller('TestCtrl2')

To daje mi błąd z informacją o błędzie wstrzyknięcia scopeProvider.

   var testCtrl1ViewModel = $scope.$new();
   $controller('TestCtrl1',{$scope : testCtrl1ViewModel });
   testCtrl1ViewModel.myMethod(); 

To tak naprawdę nie działa, jeśli masz dyrektywy w 'TestCtrl1', ta dyrektywa w rzeczywistości ma inny zakres niż ta utworzona tutaj. Kończysz z dwoma wystąpieniami „TestCtrl1”.

binRAIN
źródło
-1

Najlepszym rozwiązaniem:-

angular.module("myapp").controller("frstCtrl",function($scope){$scope.name="Atul Singh";}).controller("secondCtrl",function($scope){angular.extend(this, $controller('frstCtrl', {$scope:$scope}));console.log($scope);})

// Tutaj otrzymałeś pierwsze wywołanie kontrolera bez jego wykonywania

Atul Singh
źródło
-1

możesz również użyć $rootScopedo wywołania funkcji / metody pierwszego kontrolera z drugiego kontrolera w ten sposób,

.controller('ctrl1', function($rootScope, $scope) {
     $rootScope.methodOf2ndCtrl();
     //Your code here. 
})

.controller('ctrl2', function($rootScope, $scope) {
     $rootScope.methodOf2ndCtrl = function() {
     //Your code here. 
}
})
user5943763
źródło
1
Głos przeciw: to po prostu zły kod: po prostu sprawiasz, że twoja funkcja jest globalna. Lepiej całkowicie zrezygnuj z Angulara, jeśli chcesz w ten sposób kodować ... Użyj usługi, zgodnie z sugestią większości innych odpowiedzi.
HammerNL,
Nie jest to zalecane. $ rootScope sprawia, że ​​kod jest niezgrabny i prowadzi do problemów w dłuższej perspektywie.
Harshit Pant
-2

używaj maszynopisu do kodowania, ponieważ jest on zorientowany obiektowo, ściśle wpisany na maszynie i łatwy w utrzymaniu kodu ...

aby uzyskać więcej informacji o typach kliknij tutaj

Oto jeden prosty przykład, który utworzyłem, aby udostępniać dane między dwoma kontrolerami za pomocą Typescript ...

module Demo {
//create only one module for single Applicaiton
angular.module('app', []);
//Create a searvie to share the data
export class CommonService {
    sharedData: any;
    constructor() {
        this.sharedData = "send this data to Controller";
    }
}
//add Service to module app
angular.module('app').service('CommonService', CommonService);

//Create One controller for one purpose
export class FirstController {
    dataInCtrl1: any;
    //Don't forget to inject service to access data from service
    static $inject = ['CommonService']
    constructor(private commonService: CommonService) { }
    public getDataFromService() {
        this.dataInCtrl1 = this.commonService.sharedData;
    }
}
//add controller to module app
angular.module('app').controller('FirstController', FirstController);
export class SecondController {
    dataInCtrl2: any;
    static $inject = ['CommonService']
    constructor(private commonService: CommonService) { }
    public getDataFromService() {
        this.dataInCtrl2 = this.commonService.sharedData;
    }
}
angular.module('app').controller('SecondController', SecondController);

}

UniCoder
źródło