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

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.

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

Isolate Scope 2 way bindings doesn't update the value of parent scope

Hello I have directive foo in which controller I have
$scope.valid = false
I am passing this variable inside another directive through isolated scoping in my template
<bar valid="valid">
and using an ng-if inside my template
<span ng-if="valid">Validated<span>
Now when I update valid in my child directive. It shows validated in my template. But the variable did not update in my parent directive controller. Why this is happening?
Note: In my child controller I am attaching the variable to controller instead of scope. Is this the reason it is behaving like this.
Indeed, if, in your child directive controller code, you write
function MyController($scope) {
this.valid = $scope.valid;
}
then setting the controller object's valid property is not going to change the $scope.valid, because you performed a copy of valid.
Instead, keep using $scope to pass the information about the change back up to the parent.

Angular.js two way data binding between two nested controllers

Im curious, is there a proper way that one should handle/implement 2 way data binding between 2 nested controllers. Let me explain the scenario.
I have a formController in which has some form elements. One of the form elements is a multiselect widget, for which i created a partial html that i can use in other places, and this partial runs with a separate Controller, lets calle it multiSelectController (note, this controller/partial/view is nested within the form/formController).
What i want is to be able to have the formController (which has the data), to pass in a list of "selected" items, like [1, 3, 7, 10] to the multiselect partial, which will then be able to render the widget with the correct items selected. And at the same time, when an item gets deselected or selected from the multiselect partial, i want to be able to pass the new selected list to the formController/scope (so i can display saying 1, 3, 5 are now selected).
So to simplify the question, i want to know what is the best/corerct way to pass in a model/variable to a child view/controller while retaining the databinding, thus the child view/controller can make changes to the said variable within it while it updates the parent.
The best way according to me is :
Create a service, that will hold all the model variables.
angular.service("dataService", function() {
this.value1 = "";
this.value2 = "";
});
reference that service in your controllers, saving their reference in the scope.
angular.controller("myCntrl1", function($scope, dataService) {
$scope.dataService = dataService;
});
angular.controller("myCntrl2", function($scope, dataService) {
$scope.dataService = dataService;
});
Now in your html, you refer all your modal variables using the service reference :
// Controller 1 view
<div ng-controller="myCntrl1">
<input type="text" ng-model="dataService.value1" />
</div>
// Controller 2 view
<div ng-controller="myCntrl2">
The value entered by user is {{dataService.value1}}
</div>
Since formController is a parent controller, you need not worry about accessing its model/varaibales, just add $parent in child's scope to access any parent property
$scope.$parent.someProperty
So, if you change or update this variable, it will automatically updated in parent's scope also.

AngularJS nested scopes and altering $parent scope variables

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

AngularJS : Modify model of parent scope in a directive with '=xxx' isolate scope?

I have an Angular directive which permit to render an user, and create a link to view the user profile, declared as:
.directive('foafPerson', function() {
return {
restrict: 'E',
templateUrl: 'templates/person.html',
scope: {
personModel: '=',
onClickBindTo: '=',
onPersonClick: '&'
}
};
As you can see, I'm trying 2 solutions to be able to visit and load the full user profile: onClickBindTo and onPersonClick
I use it like that to render a list of persons + their friends:
// display the current user
<foaf-person person-model="person" on-person-click="changeCurrentProfileUri(person.uri)" on-click-bind-to="currentProfileUri"></foaf-person>
// display his friends
<div class="profileRelationship" ng-repeat="relationship in relationships">
<foaf-person person-model="relationship" on-person-click="changeCurrentProfileUri(relationship.uri)" on-click-bind-to="currentProfileUri"></foaf-person>
</div>
On the template, I have a link that is supposed to change an attribute of the controller (called currentProfileUri)
<a href="" ng-click="onClickBindTo = personModel.uri">
{{personModel.name}}
<a/>
I can see that the controller scope variable currentProfileUri is available in the personTemplate.html because I added a debug input: <input type="text" ng-model="onClickBindTo"/>
Unfortunately, when I modify the input value, or when I click on the link, the currentProfileUri of the controller is not updated. Is this normal or am I missing something?
With the other method it seems to work fine:
<a href="" ng-click="onPersonClick()">
{{personModel.name}}
<a/>
So to modify a model of the parent scope, do we need to use parent scope functions?
By the way, passing an expression with &, I tried another solution: not using a function declared in the controller scope:
<foaf-person person-model="relationship" on-person-click="currentProfileUri = relationship.uri"></foaf-person>
How comes it does not work?
My controller has nothing really fancy:
$scope.currentProfileUri = 'https://my-profile.eu/people/deiu/card#me';
$scope.$watch('currentProfileUri', function() {
console.debug("currentProfileUri changed to "+$scope.currentProfileUri);
loadFullProfile($scope.currentProfileUri);
})
$scope.changeCurrentProfileUri = function changeCurrentProfileUri(uri) {
console.debug("Change current profile uri called with " + uri);
$scope.currentProfileUri = uri;
}
I am new to Angular and read everywhere that using an isolate scope permits a two-way data binding with the parent scope, so I don't understand why my changes are not propagated to the parent scope and my debug statement doesn't fire unless I use the scope function changeCurrentProfileUri
Can someone explain me how it works?
In your example the scopes hierarchy is the following:
controller scope
ng-repeat scope
foaf-person scope
so when you declare two-way binding for 'currentProfileUri' it is actually bound to the scope created by ng-repeat, not by the controller, and when your code changes value of onClickBindTo then angularjs executes 'currentProfileUri = newValue' in the ng-repeat scope.
Solution is to use objects instead of primitive values for two-way bindings - in this case scopes inheritance always work in proper way. I mean something like that:
// display the current user
<foaf-person person-model="person" on-click-bind-to="currentProfile.uri"></foaf-person>
// display his friends
<div class="profileRelationship" ng-repeat="relationship in relationships">
<foaf-person person-model="relationship" on-click-bind-to="currentProfile.uri"></foaf-person>
</div>
I've prepared a js-fiddle which illustrates this behavior

Categories

Resources