I have problem with scopes of controllers. I'm using controller as directive and I have code similar to this example:
<div ng-controller="ItemsController as itemCtrl">
<table> .. some data ... </table>
<a ng-click="itemCtrl.createItem()">Create new item</a>
</div>
<div id="create-form" ng-controller="ItemFormController as itemFormCtrl">
<form ng-submit="itemFornCtrl.saveItem()">... form inputs ...</form>
</div>
<div id="edit-items" ng-controller="MultipleItemsEdit as multiEditCtrl">
... table with some data ....
<!-- I need this -->
<a ng-click="itemCtrl.createItem()">Create new item</a>
<!-- -->
</div>
Basically there are 3 isolated scopes. But I need to break this isolation and call methods from one scope on another.
I'm currently using ugly "delegate" kind of hack.
Controllers and their methods are not so interesting, only interesting methods are ItemsController.createItem():
this.createItem = function(dataCollection) {
angular.element( $("#create-form) ).controller().createNewItem(dataCollection);
}
and ItemFormController.createNewItem(dataCollection):
this.createNewItem = function(dataCollection) {
... some initialization ....
$("#add-item").dialog( "open" );
}
I need to call createNewItem method on ItemFormController to show modal box. But I cannot do it directly, so I'm using method createItem which gets the create-form element and its controller and calls createNewItem method on it. It is kind of a delegate. But I don't like it, because I need to call createNewItem from many places of my code and I don't want to populate all my controllers with this kind of delegate methods.
Maybe I could make these delegates on some kind of root controller, but isn't there any better solution?
You can nest the edit controller scope in the list controller scope by simply nesting the divs (move the div with ng-controller="MultipleItemsEdit as multiEditCtrl" into the div with ng-controller="ItemsController as itemCtrl"). That way the you can call the method directly.
Related
my controller is loaded multiple times in my AngularJS 1.5 code:
<div ng-repeat="conditionForMultipleRows">
<div data-ng-if="$first">
<div co-my-component></div>
</div>
</div>
export function coMyComponent(): ng.IDirective {
return {
template: coMyComponentTemplateHtml,
controller: 'MyComponentController',
controllerAs:'$ctrl'
}
}
export class MyComponentController{
state: MyStateClass;
static $inject = [someServices]
constructor(someServices) {
document.getElementById("myComponent").addEventListener("myEvent", (ev: CustomEvent) => {
doStuff()
}
The HTML Part is only called once, so there should be no issue. Only my controller is loaded multiple times.
The angular.module only loads the controller once and the directive only once, so there is no issue. Also there is no other place where the controller or webcomponent is called in the code.
I'm not very familiar with AngularJS so you can also point out other parts if you see something wrong here. Please refer to a source if it was resolved there. I didnt find any helpful answer
Thanks in advance guys
Each time your directive is instantiated, it will received a brand new controller.
The ng-repeat directive instantiates a template once per item from a collection.
In your case, if conditionForMultipleRows is an array having four items inside, you will instantiate four times the template
<div data-ng-if="$first">
<div co-my-component></div>
</div>
Each template instance gets its own scope and own controller, thus calling the constructor four times.
The answer for my issue here is:
<div ng-repeat="conditionForMultipleRows track by $index ">
<div data-ng-if="$first">
<div co-my-component></div>
</div>
</div>
With adding track by $index, the controller of my component only loaded once.
Also learned that in a ng-repeat it should always be added a "track by". There are only a few egde cases where this isnt required. Correct me if I'm wrong
I have two controllers allocated to two views:
[ResultsView ng-controller="ResultsCtrl"]
[SearchView ng-controller="SearchCtrl"]
The Search View has many complex filters/options and is filled in by the user, then he/she can press "Search" on SearchView and Results should be populated into a Grid.
Now I can send information between two either by a Service or by using $rootScope.$broadcast.
Heres the problem I've run into:
[ResultsView ng-controller="ResultsCtrl"][SearchView ng-controller="SearchCtrl"]
[ResultsView ng-controller="ResultsCtrl"][SearchView ng-controller="SearchCtrl"]
[ResultsView ng-controller="ResultsCtrl"][SearchView ng-controller="SearchCtrl"]
If I were to have multiple Result-Search sections on the same page, how can I ensure they each act independently from each other? Using the Service approach, the ResultsCtrl and SearchCtrl both have the defined service
.controller("searchCtrl", ["$scope", "$searchHttp", function ($scope, $searchHttp) {
.controller("resultsCtrl", ["$scope", "$searchHttp", function ($scope, $searchHttp) {
So I can't change how each instance of the controller behaves regarding the service. Soon as one SearchCtrl calls the service, it will modify every ResultsCtrl instance.
Likewise using broadcasts $rootScope.$broadcast("searchResults"... will be picked up by every ResultsCtrl instance.
So whats the best way around this? I want to reuse the Results and Search View code since its basically the same. But I need to render each pair independently on the same page a few times.
I think the HTML structure you need is something like this.
<!--First-->
<div ng-controller="SearchCtrl">
<div ng-controller="ResultsCtrl">
</div>
</div>
<!--Second-->
<div ng-controller="SearchCtrl">
<div ng-controller="ResultsCtrl">
</div>
</div>
This HTML structure would help you to use independently the search results one's parent SearchCtrl created in ResultsCtrl.
jsfiddle is here.
I hope this would help you. :)
I try to follow an example from http://blog.novanet.no/creating-multilingual-support-using-angularjs/
to make multilingual support using AngularJS. This example works well.
I try another way to implement that. I use ng-include to manage module separation of the header, content and footer by using the AngularJS ng-include.
My HTML structure is like this:
<div ng-include = "'header.html'"> </ div>
<div ng-include = "'content.php'"> </ div>
<div ng-include = "'footer.html'"> </ div>
I save my work on Plunker
My question is, when I try to implement multi-language, and put the selector of language in header.html and the result on content.php it is not working.
When your templates create models, they do so on the current scope, which isn't shared with your controller scope (as mentioned in a previous answer, ng-include creates a new child scope). Because of the way inheritance works, items added to this child scope are not visible to the parent (or siblings).
However, If you add items to an item already on the parent scope (google "dot notation angular"), those additions will be visible to the parent and siblings.
A good way to do this is use the controller-as syntax. I've updated your Plunk
<body ng-app="myApp" ng-controller="myController as vm">
app.controller('myController',['$scope', 'translationService',
function ($scope, translationService){
//Run translation if selected language changes
$scope.translate = function(){
translationService.getTranslation($scope, $scope.vm.selectedLanguage);
};
//Init
$scope.vm.selectedLanguage = 'en';
$scope.translate();
}]);
I have an html file with ng-includes inside
<div ng-controller="MapMenuCtrl">
<div class="mapMenu row">
<ng-include src="'partials/mapMenu/filterDropdown.html'"></ng-include>
<ng-include src="'partials/mapMenu/alertDropdown.html'"></ng-include>
<ng-include src="'partials/mapMenu/investigationDropdown.html'"></ng-include>
</div>
</div>
The problem is that I need MapMenuCtrl for each of ng-include. But when it is set as in example, it works, but only a half. For example in one of this files I use ng-model for one of $scope variables of MapMenuCtrl and it doesn't bind.
I was trying to set controller for each of ng-include, but it loads 3 times, though I need only 1.
I hope you understood me. I know, that my english is quite bad
ng-include creates a new scope. Put an object on the controller scope and put all bindable stuf inside.
You can call it model for example. Then the bindings should work as expected.
Here is a link for a details of the problem you face i believe:
Does my ng-model really need to have a dot to avoid child $scope problems?
So, I can change a model value from a child controller, but when the child controller is in ng-switch then it doesn't work, why? I created an example to demonstrate it.
One way to avoid this is to use the . in the model name, like bunnies.kills. Is this a bug or this is a feature ?
Using Angular 1.0.6
Using your code structure, in your child controllers you would need to change:
$scope.$parent.kills++;
to
$scope.$parent.$parent.kills++;
Explanation: MainCtrl's scope is the parent scope of SimpleParentCtrl, but the grandparent of Step1Ctrl and Step2Ctrl. As some others pointed out, ng-switch creates its own scope, and then your Step1Ctrl and Step2Ctrl each created a child scope of the ng-switch.
Note: Each time the 1 or 2 button is clicked, both the ng-switch and it's currently matched child controller get a new scope.
Also: In case you happen to be looking in the Angular source and wondering how the ng-switch directive creates its own scope without a scope property, the answer is that it does so manually in its link method via scope.$new(). The directives ng-include, ng-switch, ng-repeat, and ng-view all create new scope this way, either in the link method or the compile method's returned link function.
Resources:
https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance
http://www.youtube.com/watch?v=ZhfUv0spHCY&feature=youtu.be&t=30m
ng-switch creates its own child scope, which is why #sh0ber's answer is one way to get it to work. In general, models should be referenced in controller scopes (hence reference objects), and not be not primitives. So using a . is a "best practice".
This is not a bug, but it is not a feature either. This is the way JavaScript prototypal inheritance works with primitives.
I would take a slightly different approach to this problem.
Rather than use $scope.$parent, I would recommend you move all of your bunny killing logic into a shared service/model.
Also, I would try to avoid referencing parent views/controllers. Referencing the parent can make it difficult to reuse your code and can be painful to debug as the project grows. It is okay for a parent to know about it's children but a child should know little to nothing about it's parent.
Here is an updated Plunk: http://plnkr.co/edit/PLDbfU8Fu7m59A42qdR6?p=preview
HTML
<body ng-controller="MainCtrl">
<p>
Dead bunnies: <strong>{{Elmer.deadWabbits}}</strong>
</p>
<div ng-controller="SimpleParentCtrl">
<button ng-click="Elmer.killTheWabbit()">Kill from simple parent gun</button>
</div>
<hr>
<div ng-switch="" on="step">
<div ng-switch-when="first" ng-controller="Step1Ctrl">
<button ng-click="Elmer.killTheWabbit()">Kill from 1 tab gun</button>
</div>
<div ng-switch-when="second">
<div ng-controller="Step2Ctrl">
<button ng-click="Elmer.killTheWabbit()">Kill from 2 tab gun</button>
</div>
</div>
</div>
<hr>
<p>
<button ng-click="changeStep('first')">1</button> <button ng-click="changeStep('second')">2</button>
</p>
</body>
JS
angular.module('plunker', []).
service("Elmer", [function() {
this.deadWabbits = 0;
this.killTheWabbit = function() {
this.deadWabbits++;
};
}]).
controller('MainCtrl', function($scope, Elmer) {
$scope.Elmer = Elmer;
$scope.step = 'first';
$scope.changeStep = function(name){
$scope.step = name;
};
}).
controller('SimpleParentCtrl', function() {}).
controller('Step1Ctrl', function() {}).
controller('Step2Ctrl', function() {});
One way to avoid this is to use the . in model name, like bunnies.kills. Is this a bug or this is a feature ?
This has been explained numberous times : https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance
and in mhevery's video