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 };
}
});
Related
$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
I'm new to controllerAs syntax of angular and just trying to understand how it works with directive. I've created one directive for password validation. I want to make some flag true based on conditions and those will be used in parent template for displaying error messages. I'm not getting how can I achieve this!
JSFiddle
VIEW
<div ng-app="myapp">
<fieldset ng-controller="PersonCtrl as person">
<input name="emailID" type="text" ng-model="person.first" >
<input name="pass" type="password" ng-model="person.pass" password-validator>
<p ng-show="person.showMsg">Password validation message here.</p>
</fieldset>
</div>
Directive
myapp.directive('passwordValidator',function() {
return {
controller : PasswordCtrl,
controllerAs : 'dvm',
bindToController : true,
require : ['ngModel','passwordValidator'],
link : function(scope,ele,attrs,ctrls) {
var person = ctrls[1];
var ngModelCtrl = ctrls[0];
scope.$watch(function() {
return ngModelCtrl.$modelValue;
},function(newVal) {
if(newVal!='') {
person.showMsg = true;
} else {
person.showMsg = false;
}
console.log(person.showMsg);
});
}
}
function PasswordCtrl() {
}
});
Specially I want to understand why and how below watch is working fine!
// Why this below is also working, can anyone explain what's going behind!!
scope.$watch('person.pass',function(newVal) {
console.log("Watch fires");
});
This is just for learning purpose so please explain how controllerAs and bindToController works!
I know this was not part of your question, I will get to it, but using directive 'ng-controller' is an anti-pattern. If if are interested why I can explain in a separate post but in short it makes code much harder to follow.
Now, to get to the heart of your question.
From reading the Angular documentation for bindToController it would appear that if you are not also creating an isolated scope, i.e. scope: true or scope: {} it does not do anything.
Personally I have never used it before and does not seem particularly useful.
Using ng-controller is in essence adding a property to the current scope with that controller object.
So:
<fieldset ng-controller="PersonCtrl as person">
Is effectively saying, (in a contrived way):
$scope.person = new PersonCtrl();
Your directive passwordValidator which is using the controllerAs syntax within it is basically doing:
$scope.dvm= new PasswordCtrl();
In this case you effectively have a scope object that looks like:
$scope = {
person = new PersonCtrl(),
dvm: new PasswordCtrl()
}
Your person controller and dvm controller are sibling objects.
Within your passwordValidator directive you are requiring in its controller, which is the dvm object. Using that dvm object are you setting person.showMsg which is the same as doing:
$scope.dvm.person.showMsg = <value>
The dvm object does not have a way to access the person object, as they are siblings, on the $scope. So you need to use the $scope itself to access the person object. You would need to do:
$scope.person.showMsg = <value>
Although this assumes that person exists on the scope, which is a dangerous assumption.
your example is kinda messy but ill try to answer your questions.
// Why this below is also working, can anyone explain what's going behind!!
scope.$watch('person.pass',function(newVal) {
console.log("Watch fires");
});
this works because your directive is using SAME scope and variables on it as its parent controller.
this.checkDirCtrl = function() {
console.log($scope.dvm);
}
this is undefined because you are using controllerAs on SAME scope so new variable called dvm is not created and all your dvm controller variables are initialized on parent scope.
That means that you dont need to 'inject' person controller into directive because you already have controller instance on directive scope. So just set your variable 'showMsg' like this and it will work like a magic!
if(newVal!='') {
scope.person.showMsg = true;
} else {
scope.person.showMsg = false;
}
console.log(scope.person.showMsg);
I made a fiddle for you: JSFiddle
A few questions regarding structuring Angular code and the behavior of JavaScript when using variable bound vs private method naming function conventions. Is there a performance or stylistic reason for using variable bound functions / first class functions in AngularJS over private method naming? How is hoisting affected in each method? Would the second method below reduce the amount of hoisting performed and would this have a noticeable affect on application performance?
An example of private method naming. Is this a recommended way to structure Angular code?
(function () {
'use strict'
function modalController(dataItemsService, $scope) {
var vm = this;
function _getSelectedItems() {
return dataItemsService.SelectedItems();
}
function _deleteSelectedItems() {
dataItemService.DeleteItem();
$("#existConfirmDialog").modal('hide');
}
vm.SelectedItems = _getSelectedItems;
vm.deleteItemRecord = _deleteItemRecord;
}
angular.module('app').controller('modalController', ['dataItemService', '$scope', modalController]
})();
An example of variable bound functions. What about this method of structuring angular code within the controller - is there any disadvantage or advantage in terms of performance/style?
angular.module('appname').controller("NameCtrl", ["$scope", "$log", "$window", "$http", "$timeout", "SomeService",
function ($scope, $log, $window, $http, $timeout, TabService) {
//your controller code
$scope.tab = 0;
$scope.changeTab = function(newTab){
$scope.tab = newTab;
};
$scope.isActiveTab = function(tab){
return $scope.tab === tab;
};
}
]);
The first method, using "private" methods and exposing them via public aliases, is referred to as the Revealing Module Pattern, although in the example the methods aren't actually private.
The latter is a pretty standard Constructor Pattern, using $scope as context.
Is there a performance or stylistic reason for using variable bound functions / first class functions in AngularJS over private method naming?
Is [there] a recommended way to structure Angular code?
TL;DR
Fundamentally, there isn't much difference between the two styles
above. One uses $scope, the other this. One Constructor function is defined in a closure, one is defined inline.
There are scenarios where you may want a private method or value.
There are also stylistic and (probably insignificant) performance
reasons for using the variable this/vm over $scope. These are not mutually exclusive.
You'll probably want to use a
basic, bare bones, old school Constructor Pattern, and a lot of
people are exposing state and behavior via this instead of $scope.
You can allow yourself data privacy in the Controller, but most of
the time this should be leveraged by a Service/Factory. The main exception is data representative of the state of the View.
Don't use jQuery in your Controller, please.
References:
AngularJS Style Guide by Todd Motto.
AngularJS Up & Running
To answer your question thoroughly, I think it important to understand the responsibility of the Controller. Every controller's job is to expose a strict set of state and behavior to a View. Put simply, only assign to this or $scope the things you don't mind your user seeing or playing with in your View.
The variable in question (vm, $scope) is the context (this) of the instance being created by the Controller function.
$scope is Angular's "special" context; it has some behaviors already defined on it for you to use (e.g. $scope.$watch). $scopes also follow an inheritance chain, i.e. a $scope inherits the state and behaviors assigned to its parent $scope.
Take these two controllers:
angular.module("Module")
.controller("Controller", ["$scope", function($scope) {
$scope.tab = 0;
$scope.incrementTab = function() {
$scope.tab++;
};
}])
.controller("OtherController", ["$scope", function($scope) {
// nothing
}]);
And a view
<div ng-controller="Controller">
<p>{{ tab }}</p>
<button ng-click="incrementTab();">Increment</button>
<div ng-controller="OtherController">
<p>{{ tab }}</p>
<button ng-click="incrementTab();">Increment</button>
</div>
</div>
Example here
What you'll notice is that even though we didn't define $scope.tab in OtherController, it inherits it from Controller because Controller is it's parent in the DOM. In both places where tab is displayed, you should see "0". This may be the "hoisting" you're referring to, although that is an entirely different concept.
What's going to happen when you click on the first button? In both places we've exposed "tab", they will now display "1". Both will also update and increment when you press the second button.
Of course, I may very well not want my child tab to be the same tab value as the parent. If you change OtherController to this:
.controller("OtherController", ["$scope", function($scope) {
$scope.tab = 42;
}]);
You'll notice that this behavior has changed - the values for tab are no longer in sync.
But now it's confusing: I have two things called "tab" that aren't the same. Someone else may write some code later down the line using "tab" and break my code inadvertently.
We used to resolve this by using a namespace on the $scope, e.g. $scope.vm and assign everything to the namespace: $scope.vm.tab = 0;
<div ng-controller="OtherController">
<p>{{ vm.tab }}</p>
<button ng-click="vm.incrementTab();">Increment</button>
</div>
Another approach is to use the simplicity and brevity of this and take advantage of the controllerAs syntax.
.controller("OtherController", function() {
this.tab = 0;
});
<div ng-controller="OtherController as oc">
<p>{{ oc.tab }}</p>
</div>
This may be more comfortable for people who are used to using plain JS, and it's also easier to avoid conflicts with other Angular sources this way. You can always change the namespace on the fly. It's also a bit "lighter" on performance since you're not creating a new $scope instance, but I'm not sure there's much gain.
In order to achieve privacy, I would recommend encapsulating your data in a Service or Factory. Remember, Controllers aren't always singletons; there is a 1:1 relationship between a View and a Controller and you may instantiate the same controller more than once! Factories and Service objects are, however, singletons. They're really good at storing shared data.
Let all Controllers get a copy of the state from the singleton, and make sure all Controllers are modifying the singleton state using behaviors defined on the Service/Factory.
function modalController(dataItemsService) {
var vm = this;
vm.selectedItems = dataItemsService.SelectedItems(); // get a copy of my data
vm.updateItem = dataItemService.UpdateItem; // update the source
}
But wait, how do I know when another part of my app has changed my private data? How do I know when to get a new copy of SelectedItems? This is where $scope.$watch comes into play:
function modalController(dataItemsService, $scope) {
var vm = this;
vm.updateItem = dataItemService.UpdateItem; // update the source
// periodically check the selectedItems and get a fresh copy.
$scope.$watch(dataItemsService.SelectedItems, function(items) {
vm.items = items;
});
// thanks $scope!
}
If your data is not shared, or if your private data is representative of the View layer and not the Model layer, then it's totally OK to keep that in the controller.
function Controller() {
var buttonClicked = false;
this.click = function() {
buttonClicked = true; // User can not lie and say they didn't.
};
}
Lastly, DO NOT USE JQUERY IN YOUR CONTROLLER, as your reference did!
$("#existConfirmDialog").modal('hide');
This example might not be purely evil, but avoid accessing and modifying the DOM outside a Directive, you don't want to break other parts of your app by modifying the DOM underneath it.
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.
I have a directive that takes in a collection and builds out a dropdown.
.directive("lookupdropdown", function () {
return {
restrict: 'E',
scope: {
collectionset: '=',
collectionchoice: '='
},
replace: true,
template: '<select class="input-large" ui-select2 ng-model="collectionchoice" data-placeholder="">' +
' <option ng-repeat="collection in repeatedCollection" value="{{collection.id}}">{{collection.description}}</option>' +
'</select>',
controller: ["$scope", function ($scope) {
$scope.repeatedCollection = new Array(); //declare our ng-repeat for the template
$scope.$watch('collectionset', function () {
if ($scope.collectionset.length > 0) {
angular.forEach($scope.collectionset, function (value, key) { //need to 'copy' these objects to our repeated collection array so we can template it out
$scope.repeatedCollection.push({ id: value[Object.keys(value)[0]], description: value[Object.keys(value)[1]] });
});
}
});
$scope.$watch('collectionchoice', function (newValue, oldValue) {
debugger;
$scope.collectionchoice;
});
} ]
}
});
This works fine. It builds out the drop down no problem. When I change the dropdown value, the second watch function gets called and I can see that it sets the value of collection choice to what I want. However, the collectionchoice that I have put into the directive doesn't bind to the new choice.
<lookupDropdown collectionset="SecurityLevels" collectionchoice="AddedSecurityLevel"></lookupDropdown>
That is the HTML markup.
This is the javascript:
$scope.SecurityLevels = new Array();
$scope.GetSecurityLevelData = function () {
genericResource.setupResource('/SecurityLevel/:action/:id', { action: "#action", id: "#id" });
genericResource.getResourecsList({ action: "GetAllSecurityLevels" }).then(function (data) {
$scope.AddedSecurityLevel = data[0].SCRTY_LVL_CD;
$scope.SecurityLevels = data;
//have to get security levels first, then we can manipulate the rest of the page
genericResource.setupResource('/UserRole/:action/:id', { action: "#action", id: "#id" });
$scope.GetUserRoles(1, "");
});
}
$scope.GetSecurityLevelData();
Then when I go to post my new user role, I set the user role field like this:
NewUserRole.SCRTY_LVL_CD = $scope.AddedSecurityLevel;
but this remains to be the first item EVEN though I have updated the dropdown, which according the watch function, it has changed to the correct value. What am I missing here?
You faced this issue because of the prototypical nature inheritance in Javascript. Let me try and explain. Everything is an object in Javascript and once you create an object, it inherits all the Object.Prototype(s), which eventually leads to the ultimate object i.e. Object. That is why we are able to .toString() every object in javascript (even functions) because they are all inherited from Object.
This particular issue on directives arises due to the misunderstanding of the $scope in Angular JS. $scope is not the model but it is a container of the models. See below for the correct and incorrect way of defining models on the $scope:
...
$scope.Username = "khan#gmail.com"; //Incorrect approach
$scope.Password = "thisisapassword";//Incorrect approach
...
$scope.Credentials = {
Username: "khan#gmail.com", //Correct approach
Password: "thisisapassword" //Correct approach
}
...
The two declarations make a lot of difference. When your directive updated its scope (isolated scope of directive), it actually over-rid the reference completely with the new value rather then updating the actual reference to the parent scope hence it disconnected the scope of the directive and the controller.
Your approach is as follows:
<lookupDropdown collectionset="SecurityLevels" collectionchoice="$parent.AddedSecurityLevel"></lookupDropdown>
The problem with this approach is that although it works, but it not the recommended solution and here is why. What if your directive is placed inside another directive with another isolated scope between scope of your directive and the actual controller, in that case you would have to do $parent.$parent.AddedSecurityLevel and this could go on forever. Hence NOT a recommended solution.
Conclusion:
Always make sure there is a object which defines the model on the scope and whenever you make use of isolate scopes or use ng directives which make use of isolate scopes i.e. ng-model just see if there is a dot(.) somewhere, if it is missing, you are probably doing things wrong.
The issue here was that my directive was being transcluded into another directive. Making the scope im passing in a child of the directive it was in. So something like $parent -> $child -> $child. This of course was making changes to the third layer and second layer. But the first layer had no idea what was going on. This fixed it:
<lookupDropdown collectionset="SecurityLevels" collectionchoice="$parent.AddedSecurityLevel"></lookupDropdown>