Hi I need a AngularJS Wiz to point me in the right direction been trying to get my head around AngularJS Scope and Inheritance.
I have a child Scope which I add to a Parent Scope then I want to add a new object to the Parent scope via array.push(); but I'm not sure why the Child scope then inherits this new value. See the fiddle here http://jsfiddle.net/sjmcpherso/EFxuZ/ The first example using ng-repeat and objects causes the child to update:
$scope.childArr = [{'name':'peter'},{'name':'paul'},{'name':'perry'}];
$scope.parentArr = $scope.childArr;
$scope.parentArr.push({'name':'Why am I in now in the Child Array?'})
Whereas the second example using just a variable does not:
$scope.childVar = "Confused Muchly";
$scope.test.parentVar = $scope.childVar;
$scope.test.parentVar = "This wont change the child variable";
Ideally I would like to make changes to the child scope which would update the parent scope but not the other way around.
I have read of https://github.com/angular/angular.js/wiki/Understanding-Scopes while not fully understanding everything this issue seems a mystery to me.
Firstly, both of your models $scope.childArr and $scope.test.parentArr are in $scope of the controller. None of them is in parent scope.
If you want to have parentArr in the parent scope, then you should have a parent-child controller design or move your model inside the rootScope:
$rootScope.test = {};
$rootScope.test.parentArr = [ /* some items here */ ];
Secondly, $scope.childArr and $scope.test.parentArr both point to the same array. Changing either of them would mean changing both of them.
It is almost same as doing:
$scope.test = {};
$scope.childArr = $scope.test.parentArr = [
{'name':'peter'},
{'name':'paul'},
{'name':'perry'}
];
If you want to create separate copies so that changing one of them would not affect the other, then you can use angular.copy():
$scope.test.parentArr = angular.copy($scope.childArr);
Related
Let's say my grandparent component/directive's scope variable is declared like this: $scope.g = { // methods here };
In each of my nested component's/directive's controllers, I'm referring to that variable like $scope.$parent.$parent.g and then calling some function off of g such as $scope.fromNgClick = function() { $scope.$parent.$parent.g.run(); };. That works great (though I would like a better way of referring to ancestors such as an alias name? instead of a $parent chain).
When I natively drag a component from it's grandparent component into another grandparent sibling component (got that?), the same $scope.fromNgClick = function() { $scope.$parent.$parent.g.run(); }; still points to the original scope, not the new one like I need it to. So clicking the same ng-clickable element still triggers the run() method on the previous grandparent's scope.
That all makes sense; but, is there a way to get the scope to point to the new dropped locations grandparent scope instead?
Thanks!
EDIT
The markup would be something like the following where <g-directive> is treated as a grandparent because it uses transclude on its template, ultimately wrapping the child directives:
<g-directive>
<child-directive></child-directive>
<another-child-directive></another-child-directive>
<yet-another-child-directive></yet-another-child-directive>
</g-directive>
<g-directive>
<c-child-directive></c-child-directive>
<c-another-child-directive></c-another-child-directive>
<c-yet-another-child-directive></c-yet-another-child-directive>
</g-directive>
That's the reason for the $scope.$parent.$parent.g on the child directives/components.
A child component can be dragged and then dropped into another <g-directive> but it's controller still points to its original grandparent (original <g-directive>'s controller scope variable). I want it to point to the new grandparent is was dropped into, essentially resetting it's scope to the newly placed scope.
<g-directive some-attr=1>
<child-directive></child-directive some-attr=1>
<another-child-directive></another-child-directive some-attr=1>
<yet-another-child-directive></yet-another-child-directive some-attr=1>
</g-directive>
<g-directive some-attr=2>
<child-directive></child-directive some-attr=2>
<another-child-directive></another-child-directive some-attr=2>
<yet-another-child-directive></yet-another-child-directive some-attr=2>
</g-directive>
Each can have different listener for broadcast and emit.
if the directives are repeated through ng-repeat then the $index can be that attribute.
Hope this helps.
I know you may have seen this question before, but is there really an answer for it? I started to doubt it.
Simply, I am using controlleras syntax as recommended, I have no problem accessing parent controller members form within the view, but I cannot do it from the constructor function of my child scope. And here is some code from what I am having right now:
myapp.controller("ParentController", function() {
this.selectedItem = {
Id: 1,
name: 'item1'
};
this.setSelectedItem = function(item) {
this.selectedItem = item;
//do other stuff
}
});
myapp.controller("ChildController", function() {
this.onItemChanged = function(newItem) {
//How can I call the parent controller instance from here
}
});
Also please notice that I want to call the 'updateSelectedItem' function from my child controller in away that the 'this' keyword will refer to the parent controller instance not the child, because I want to change the parent controller instance, so how should I do this?
To answer your question as clearly as possible, you first must have a bit of background on how the Controller-As syntax actually works.
Using Controller-As does not mean that you are not using $scope. In reality, $scope still exists. Controller-As is shorthand which creates an object on $scope and attaches the properties assigned via this to that object. On the view side, this object is explicitly bound to all the controls. you could still reference $scope.vm.property. However, since $scope is implicit in this scenario, it is not necessary to create a dependency to it.
Accessing the properties of the vm object of the parent controller in a nested scenario is still possible, but only if each controller is referenced by a different name. If your objects are outerScope and innerScope, then inside the HTML template of innerScope, you can still refer to outerScope.someProperty.
If, however, all controllers are named the same (i.e. vm), then the only way to access the parent controller would be through a property of the child scope which is aliased to a $scope property, introducing the $scope dependency.
In practice, whenever you have a controller within another controller, it's much cleaner for the innermost item to be a directive which wraps its own content, and explicitly defines which variables it needs through an Isolate Scope. However, whenever this is not necessary, the fallback should be for inner controllers to be named uniquely from outer controllers.
I'm following the conventions made by John Papa, but unfortunately I'm not really figuring out how to bind values from children controllers to parent controllers, using ui-router, ControllerAs and vm variables instead of $scope.
I made two examples, first example illustrates the working environment, so without ControllerAs and the normal $scope variable, this works, but when I change $scope to
var vm = this;
it doesn't process any changes made to children controllers to the parent. I really want to make it work without working with some kind of PUBSUB pattern, because this is one of the most important features of working with two-way-data binding, but I don't know if this is possible within this situation?
I've created two Plunker examples to illustrate the problem:
Plunker example 1:
Controller + $scope
http://embed.plnkr.co/oNsCF2YnfKP3wzGNhFUi/preview
Plunker Example 2:
With vm variable and ControllerAs syntax
http://embed.plnkr.co/s68o08dtBafHKflD8ZLZ/preview
The first Example works, when changing some input fields within the template 'register-identification.html', this will directly change {{formData}} from the parent controller (RegisterBaseController).
The second Example doesn't process any changes.
I hope my explanation makes any sense? Hopefully someone can help me out!
Many thanks in advance!
Ken
This scenario is behaving as expected, but not as you want.
$scope Inheritance - pass model by reference
The reason is, that any child state, is provided with prototypically inherited version of a $scope:
$childScope = $scope.$new();
And that means, that any reference created on parent is available on child
$scope.Reference = {};
$scope.Reference.A = 1;
$childScope = $scope.$new();
// here both contain 1 as Reference.A
$childScope.Reference.A = 2;
// both contain 2
$childScope.Reference.A === $childScope.Reference.A
hidding reference with another
But what you do (what controllerAs does) is:
$scope.vm = controllerParent;
$childScope = $scope.$new();
$childScope.vm = controllerChild;
The reference to vm itself is changed.
solution with a dot - Model {}
So, what could be working is combination:
$scope.Model = {}
$scope.Model.vm = controllerParent;
$childScope = $scope.$new();
$childScope.Model.childVm = controllerChild;
$childScope.Model.vm ... // this is parent controller
Check the doc:
What Do Child States Inherit From Parent States?
and mostly:
Scope Inheritance by View Hierarchy Only
Keep in mind that scope properties only inherit down the state chain if the views of your states are nested. Inheritance of scope properties has nothing to do with the nesting of your states and everything to do with the nesting of your views (templates).
It is entirely possible that you have nested states whose templates populate ui-views at various non-nested locations within your site. In this scenario you cannot expect to access the scope variables of parent state views within the views of children states.
Check the dot in the model:
Model in a modal window in angularjs is empty
I am trying to persist the whole scope to a service so that it is available and already set up after user navigates away and then returns to the screen.
I have created a service and am storing the current scope there. Later I set the $scope variable passed into the controller with the one stored in my service, but the after inspecting the DOM, I see that it's bound scope is still the scope object that existed before replacing.
How can I replace the scope so that it will also be used for the DOM elements?
Thanks for any help!
the below code tries to see if the local scope variable is initialized and if so it sets the $scope to it, otherwise it continues and wires it all up normally. this.scope is a member variable defined and set in the controller's super class (not shown).
function xyzController($scope, stateService) {
_super.call(this, $scope, stateService);
if (this.scope.hasBeenInitialized) {
$scope = this.scope; // $scope is updated but the DOM's scope never changed
return;
}
$scope.hasBeenInitialized = true;
...
}
You could try:
if (this.scope.hasBeenInitialized) {
angular.extend($scope, this.scope);
return;
}
This would merge the values from this.scope onto your $scope without replacing the variable.
This won't work. Scope is wired up deep inside Angular. To give you an idea, on any element, you can call:
angular.element(someDomElement).scope();
And get its scope. It's really not do-able to replace scopes like you're trying to do. But the more immediate problem is you're just overwriting that particular variable. It's an object passed in. Imagine you have this code:
var myObject = { a: 1 };
function f(obj) {
obj = { a: 2 };
}
f(myObject);
Clearly this doesn't change myObject. It'll replace obj within your function, but the thing about scopes is they're set up for you for all the expression in your views (it's the this in any scope functions for example). You'd need to change it through and through, and I don't see a way to do that.
In an attempt to clean up my partials I recently moved some of my global menus into seperate templates which I now include in the views which need them. As the menu (including a search bar) is global I have created a service which keeps track of the state of the menu and so on.
Problem is something funny happened after I started including.
HTML (Jade) of the View
div(ng-controller='HeroUnitCtrl', ng-include src='heroTemplate')
div(ng-controller='MainSearchBarCtrl', ng-include src='searchBarTemplate')
div.row-fluid
div.span12
table.table.table-striped.table-bordered
tr
th
a(ng-click='setOrder("id")') ID#
th
a(ng-click='setOrder("client.name")') Kunde
th
a(ng-click='setOrder("technician.name")') Tekniker
th
a(ng-click='setOrder("createdAt")') Opprettet
th
a(ng-click='setOrder("statusString")') Status
tr(ng-repeat='record in records | orderBy:orderProp | filter:searchBar')
td
a(ng-href='#/records/show/{{record.id}}') {{record.id}}
td {{record.client.name}}
td {{record.technician.name}}
td {{record.createdAt}}
td {{record.statusString}}
HTML (Jade) searchBarTemplate
input#searchField.input-xxlarge(type='text', placeholder='placeholder', ng-change='searchBarUpdated()', ng-model='searchBar')
Now to the bit I really don't understand,
MainSearchBarCtrl
function MainSearchBarCtrl(MainSearchBarService, $scope, $location) {
$scope.searchBarTemplate = 'partials/main-searchbar';
$scope.searchBar = 'Hello World!';
$scope.searchBarUpdated = function() {
console.log('Search bar update: ' + $scope.searchBar);
MainSearchBarService.searchBarUpdated($scope.searchBar);
}
}
Initially the value of searchBar is as expected "Hello World". However, if I append any text it still only prints "Hello World". Or, if I replace the text it prints undefined. So it seems the binding is broken, but I don't really see why this is happening. Worth mentioning is that this wasn't case before I moved my search bar into a separate template.
Any help is greatly appreciated.
As discussed in the comments above, ng-include creates a new child scope. So in your searchBarTemplate, using ng-model="searchBar" results in a new searchBar property being created on the child scope, which hides/shadows the parent searchBar property of the same name.
In the controller, define an object:
$scope.obj = {searchBar: 'Hello World!};
And then use
ng-model="obj.searchBar"
in your template. When objects are used (instead of primitives), the child scope does not create a new property. Rather, due to the way JavaScript prototypal inheritance works, the child scope will find the property on the parent scope.
See also https://stackoverflow.com/a/14146317/215945 which has a picture showing the child and parent scopes and how the child property hides/shadows the parent property if a primitive is used.
Note that using $parent is another option, but it is not "best practice".
Try using $parent instead of $scope in your included template