AngularJS nested scopes and altering $parent scope variables - javascript

Apologies if this has been asked before, I couldn't find anything on SO and I'm hoping for some clarification ( or a nice neat trick )
Given
<div ng-controller="Parent">
<div ng-controller="Child">
//child manipulation of parent scope object
</div>
</div>
Parent sets json object so it is available to multiple child scopes -
$scope.persistentData = getAJSONObject();
A child scope wants to do some calculations and update a key of the local json object it has inherited from the parent -
doCalculations( $scope.persistentData.keyIWantToAlter )
Do I need to explicitly assign the parent scope to the result of the calculation function in the child (shown below) or is there a way that I can propogate the changes to the parent scope by just using the child's inherited scope objects?
$parent.$scope.$persistentData.keyIWantToAlter =
doCalculations( $scope.persistentData.keyIWantToAlter)

I can't see any problems with the blurb you gave you will need to give us more. I can caution you about trying to "share" nested objects on scope.
This fiddle illustrates what happens if you are in the child and you "overwrite" the reference. The json2 shows that these start off the same, but I overwrite the reference in the child scope and now the variables are detached.
I think you are experiencing a similar issue but can't prove it until you provide more info.
<div ng-controller="ParentCtrl">
Hello, {{json2}}!
<div ng-controller="ChildCtrl">
Hello, {{json2}}!
</div>
</div>
function ParentCtrl($scope) {
$scope.json2 = {
child:{
name: 'parent'
}
}
}
function ChildCtrl($scope, $timeout) {
$scope.json2 = {
child:{
name: 'child'
}
}
$timeout(function(){
$scope.json2.child.name= 'nick';
},5000);
}

Related

$scope.feeds getting updated but the view won't update the view

$scope.feeds is getting updated within the controller. But the view won't update the variable. The feeds variable is in a ng-repeat.
Angular Code:
$scope.openfeeds = function(category){
$http({
method: 'GET',
url: '/category/feeds/'+category.id
}).then(function successCallback(response) {
$scope.feeds = response.data;
console.log($scope.feeds);
});
};
HTML code:
<div class="c-Subscribe">
<div class="c-Subscribe_feeds" ng-repeat="feed in feeds" ng-controller="LinkController">
#{{feed}}
</div>
</div>
Whereas, there is another variable called categories which right above it. It is getting updated with the same way I am doing it to update the feeds.
<div class="c-modal_content">
<div class="c-categoryTile_blocks">
<div class="c-categoryTile" ng-repeat="category in categories">
<div class="c-categoryTile_background" style="background-image: url('/images/biker-circuit-competition-63249.jpg');">
#{{ category.category }}
</div>
</div>
</div>
</div>
Any ideas why this isn't working?
Pretty much the same answer that I gave to this question How to preserve scope data when changing states with ui-router?
You need to under stand how prototypal inheritance works. When a parent puts a property value on the scope with
$scope.value = 'something';
In a child component if you access $scope.value the inheritance chain will find $scope.value.
If the child sets
$scope.otherValue = 'something';
If follows the inheritance chain, doesn't find a value of otherValue and creates a property on the child scope, not the inherited prototype so the parent component and any other children of the parent do not see it.
You can use what is called the dot rule of prototypal inheritance. If the parent creates an object on the scope called something like data
$scope.data = { value: 'something' };
Now if the child puts a property on the data object
$scope.data.otherValue = 'something';
It looks for the data object, finds it in the inheritence chain and because you are adding a property to an instance of an object it is visible to the parent and any children of the parent.
let parent = {
value: 'some value',
data: { value: 'some value' }
};
let child = Object.create(parent);
console.log(child.value); // Finds value on the prototype chain
child.newValue = 'new value'; // Does not affect the parent
console.log(parent.newValue);
child.data.newValue = 'new value'; // newValue is visible to the parent
console.log(parent.data.newValue);
Short answer is to just never inject $scope and use controllerAs syntax.
To share data between controllers you use a service that is injected to both controllers. You have the spots collection on the service and use a route param to identify which spot the other controller should use or have a place on the service called currentSpot set by the other controller.
Services are a singleton object that you create at the module level and then all controllers that ask for them in their dependency list get the same instance. They are the preferred way to share data between controllers, $scope hierarchies are bound to lead to confusion as the prototypal inheritance nature of them can be confusing. A child $scope is prototypally inherited from it's parent, this seems like you should be sharing data but when a child controller sets a property it is not visible to the parent.
You are learning an outdated way of Angular programming. Injecting $scope is no longer a recommended way. Look at using components. Components are a wrapper for a controller with an isolated scope and using contollerAs syntax. Isolated scopes make it much cleaner to know where data comes from.
Take a look at my answer on this question
Trying to activate a checkbox from a controller that lives in another controller

Unable to call $scope function in ng-repeat

I have this function showPopupSelectTopic(subject) to call in my ng-repeat html code. But it does not work at all.
<div style="width:100%;" ng-controller="manageStudyCtrl">
<div class="div-subject" ng-repeat="subject in dataSubject" ng-click="showPopupSelectTopic(subject)">
<div class="round-button-subject-2">
<div class="subject-name-2 subject-eng" style="color:{{subject.subject_code.colour_code}}">
{{subject.subject_code.short_name}}
<div>{{subject.avg_overall_coverage | number : 0}}%</div>
</div>
<circular-progress
value = "subject.avg_overall_coverage"
max="100"
orientation="1"
radius="36"
stroke="8"
base-color="#b9b9b9"
progress-color="{{subject.subject_code.colour_code}}"
iterations="100"
animation="easeInOutCubic"
></circular-progress>
</div>
</div>
</div>
I want to call my showPopupSelectTopic(subject)in my controller so that I can make popup and manipulate the data.
I have done outside from ng-repeatand its working perfectly. However if I used in ng-repeat then it would not execute as expected. How to solve this issue?
My controller:
angular.module('manageStudy', [])
.controller('manageStudyCtrl', function($scope,){
$scope.showPopupSelectTopic = function(subject) {
alert(subject.chapter_id);
};
});
That's not possible due to every ng-repeat creating its own child scope. That being said, ever functio invocation will lead the child to copy some variables into its own scope. You'd have to use their parentscope or refere to the origin $scope of your controller.
ng-click="$parent.showPopupSelectTopic(subject)"
This should solve the problem. However, it's kinda dirty. A better solution would be to return your parents scope and use it in every child scope just like that. So declare a function inside of your controller (e.g. $scope.getScope) and let it simply return its $scope. Afterwards you'll be able to access it properly.
$scope.getScope = function() {
return $scope;
}
ng-click = "getScope().showPopupSelectTopic(subject)"
ng-repeat works only with an iterate able object, like array or collection. Before you open the div where you intent to repeat, the iterate able object must be in ready in the scope. Try using ngInit instead of ngClick to initialize the array before attempting to ngRepeat

Angular scope binding

I'm following a book, and using this version of Angular: https://ajax.googleapis.com/ajax/libs/angularjs/1.2.6/angular.js[1]
This is my template:
<div ng-controller="ParentController">
<div ng-controller="ChildController">
<a ng-click="sayHello()">Say hello</a>
</div>
{{ person }}
</div>
This is my controller code:
app.controller('ParentController', function ($scope) {
$scope.person = { greeted: false };
});
app.controller('ChildController', function ($scope) {
$scope.sayHello = function () {
$scope.person = { name: "Ari Lerner", greeted: true };
}
});
I noticed my code doesn't doesn't update the template as expected unless I change my sayHello method to this:
$scope.sayHello = function () {
$scope.person.name = "Ari Lerner";
$scope.person.greeted = true;
};
Any insight as to why this might be? I was under the assumption that updating person would be reflected in the DOM.
Using 1.4.2 yields the same result.
Thinking that maybe the properties are somehow indexed differently, I tried the following:
$scope.person.name = { greeted: true, name: "Ari Lerner" };
(switched greeted and name)
Wild speculation: It seems to me that something in Angular is holding on to the original object that was assigned to $scope.person and setting $scope.person to a new object "loses" the data binding. Is this true?
In AngularJS, scopes use prototypical inheritance from their parents.
Prototypical inheritance basically means that JavaScript will look at the parent scope if it doesn't find a property on the child scope.
So, when you do $scope.person.name = 'Ari Lerner', JavaScript looks at $scope, sees that it doesn't have a person property, then goes to its parent (the parent scope), sees that it has a person property, and assigns the name property of that to 'Ari'.
On the other hand, when you do $scope.person = { ... }, JavaScript doesn't care if the property exists or not - it simply carries out the assignment, ensuring that your $scope now has a person property. The problem here is that your child scope's person property now shadows the person property of the parent scope, which still has its original value. So, in the parent controller, person never changed.
For further reading, check this answer here:
What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
You are creating a child scope that prototypically inherits from its parent scope. This is the case unless you are using an isolate scope (directive).
For a really great explanation, see here What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
As others stated, this is because of your scopes.
For example, if you want to create a new object in your childScope, you do $scope.someObject = {};.
Now, JavaScript can't know the difference between
$scope.someNewObject = {};
and
$scope.person = {};
because you are just assigning a new object to your childscope.
The other notation works, because by grabbing $scope.person.attribute = ... JavaScript knows, that the person object already exists. It starts looking for this object in your childscope, can't find it there, goes to parentscope and finds it there and sets the attribute.
In conclusion you either have to use $scope.person.attribute, or you use $scope.$parent.person = {};
To edit $scope.person from the childController (child scope) use $scope.$parent like so...
app.controller('ChildController', function ($scope) {
$scope.sayHello = function () {
$scope.$parent.person = { name: "Ari Lerner", greeted: true };
}
});

Parent id of scope Angularjs

I want to access or manipulate a scope's parent's parent without getting too too complicated. The controller as fashion allows for naming the parent controller as such: this.applicationCtrl.something given applicationCtrl > parent1Ctrl > child1Ctrl - siblingOfChild1Ctrl
To give you a better example, I have an applicationCtrl on the <body> tag, I have a side panel with sidePanelCtrl and the content with contentCtrl with a nested contentChildCtrl
With the controller as model, I can call or change things on the sidePanelCtrl by calling this.sidePanelCtrl, can I do the same if I just want to use $scope method?
This is specifically for the contentChildCtrl where I do not want to write $scope.$parent.$parent which still will only get me to the applicationCtrl and not the sidePanelCtrl
If you don't know the nesting level of the parent scope, or don't want to type $scope.$parent.$parent etc you can attach something like this to a service:
angular.module('app').service('inherit', function () {
var inherit = function(scope, item)
if (!scope) return;
if (scope[item]) return scope[item];
return inherit(scope.$parent, item);
}
return inherit;
}
If your namespacing isn't great then it might not help much, but if you're looking to modify, say, the sidebar contents from a grandchild scope, you could call var sidebarNav = inherit($scope, 'sidebarNav'); in the grandchild controller.
Edit - Better to put this in a service than on $rootScope as the comment below has mentioned
Edit: updated to use service

AngularJS, updating ng-repeat within a child controller view by reference

I have a child controller that references values from it's parent.
The problem is: ng-repeat within the child controller view doesn't get updated when the parent controller values are updated.
So my question is: How does one update the child controller ng-repeat when parent controller values are updated while child values are by reference?
Child Controller:
angular.module('angularApp').controller('PostController', function ($scope)
{
$scope.mainController = $scope.$parent.getMainController();
$scope.editController = $scope.$parent;
$scope.posts = $scope.mainController.currentStation.posts;
$scope.featuredAlbums = $scope.$parent.featuredAlbums;
$scope.updatePost = function(postId){
$scope.editController.updatePost(postId);
};
$scope.updateFeatured = function(featuredId){
$scope.editController.updateFeatured(featuredId);
};
});
ng-repeat under the child controller
<div ng-controller="PostController" class="posts">
<div ng-repeat="featuredAlbum in featuredAlbums">
Example that breaks:
http://plnkr.co/edit/GKjYAWEEWOrp84bwIIOt?p=info
** Answer **
Thanks for the fast response guys, I realise that everything created within the controller is passed by value and not reference, even values referenced from parent controllers are recreated as locally scoped controller values.
So the solution? Simple.
Just call the parent directly instead of recreating locally scoped vars
$scope.$parent.$someValue
Imagine the scenario:
app.controller('ParentController', function($scope) {
$scope.rawValue = 3;
$scope.hiddenValue = false;
$scope.objectValue = {
name: 'David',
age: 27
};
$scope.someFunction = function(input) {
return input;
}
});
app.controller('ChildController', function($scope) {
$scope.hiddenValue = true;
//Notice i don't need to wrap calls to parent functions or reassign parent data.
//this is because the $scope object will automatically inherit from it's parent.
});
<div ng-controller="ParentController">
{{ hiddenValue }} //false, remains false as setting only occured on child scope;
{{ rawValue }} //3, remains 3 as setting will only occur on child scope;
{{ objectValue.name }} // 'David' however will dynamically update with input below.
<div ng-controller="ChildController">
{{ hiddenValue }} //true because now it's scoped;
<input type="button" ng-click="someFunction('hello')" value="calls function on parent scope" />
<input type="text" ng-model="rawValue" /> //will initialise as 3 but as soon as updated it will be scoped on this scope.
<input type="text" ng-model="objectValue.name" /> //will initialise as David and will correctly update parent scope as edited.
</div>
</div>
So why does this work?
Anytime you are accessing a property or function it will automatically travel up the $scope hierarchy to find the value. No need to specify $parent expressly as this is how javascript inheritance works.
However whenever you are modifying/setting a value it will occur on the nearest $scope and be 'scoped'. that's what happens with hiddenValue and rawValue in example above. however notice that it works as expected on objectValue.name this is because in order to set the name property you must first 'get' objectValue. therefore javascript inheritance travels up the scope chain to get objectValue from the parent scope and then sets it's name property.
Two guidelines:
ng-model should usually use a '.' so that it forces this scope walking.
using $parent is usually a bad sign. If used correctly parent properties should already be available through the current $scope alone.
I am not sure I understand your question correctly. So I created a plunker that included two controllers. It seems to me that child values are always updated. Can you show us how your original questions are related or not?
`http://plnkr.co/edit/9aHqdbbIe5aGJSuHogPA?p=preview`
What's happening is that you are getting a new copy of the data in the child scope and the parent scope is not updated.
The simplest way to make it work is not to store any objects that need to be accessible by child controllers directly on the parent scope. You will have less code and far fewer complications.
In your case, in the parent have something like:
$scope.media = {
featuredAlbums : [ ... ],
currentStation : {
posts : {...}
}
}
$scope.functions = {
updatePost : function (pid) {
// your function
},
updateFeatured : function (pid) {
// your function
}
}
and in the child don't bother with all the inheritance and just call functions directly :
$scope.functions.updatedFeatured(featureID);
$scope.functions.updatePost(postId);
it doesn't matter which parent controller your functions and data are in, it will find them if you attach to a property of the controller but not the controller itself.
Take a look at this video which explains it better.
https://egghead.io/lessons/angularjs-the-dot
EDIT
As David Beech points, there is no need to store parent scope functions in a property because the function is not being modified. However, this approach is meant to be a simple rule that will work without any extra thinking about which data/functions are read-only and which are writable.

Categories

Resources