When passing an object to a ng-click function, the object seems to lose its reference. What is the reason?
CodePen example
<div ng-app="app" ng-controller="controller">
<p>
<u>Object</u> : {{ obj | json }}
</p>
<p>
<button ng-click="obj = {}">obj = {}</button> <!-- works -->
<button ng-click="voidIt(obj)">voidIt(obj)</button> <!-- doesn't work -->
</p>
<p>
<button ng-click="reset()">Reset obj</button>
</p>
</div>
angular.module('app', []).controller('controller',
function($scope) {
$scope.voidIt = function(object) {
object = {}
}
$scope.reset = function() {
$scope.obj = { prop: "value" }
}
$scope.reset();
});
What a nested $scope refers to can become confusing, so it's much better to use the controllerAs syntax. For a good explanation, read AngularJS 'controllerAs' vs. '$scope'.
The Codepen with ControllerAs referencing.
But if you're looking for a clear reasoning why it doesn't work, I'm lost as well. I just find it's just better to take the safer path that avoids such pitfalls.
Related
I have a constant file which looks like this
demo.constant.js
// Add detail constans here
(function () {
angular.module('myApp').constant('TYPE', {
DYNAMIC: 'dynamic',
STATIC: 'static'
});
}());
Now I have a controller file that looks similar to this.
demo.controller.js
(function() {
var DemoController = function(DEP1,DEP2, .... , TYPE)
console.log(TYPE.DYNAMIC); // works perfectly
var self = this;
self.type = '';
...
...
angular.module('myApp.controllers').controller('DemoController', DemoController);
})();
I am trying to access these constants in the HTML file like this:-
<div ng-controller="DemoController as self">
<span ng-if="(self.type === 'dynamic')"> <!--instead of 'dynamic' I want to use TYPE.DYNAMIC-->
...
...
...
</span>
</div>
Note:- {{self.type}} works but {{TYPE.DYNAMIC}} doesn't.
Another problem is that I want to use this constant as the value of radio buttons.
somewhat like this:-
<input type="radio" name="type" ng-model="self.inputType" value="dynamic"> <!-- Here I want to use TYPE.DYNAMIC -->
<input type="radio" name="type" ng-model="self.inputType" value="static"> <!-- Same as above -->
I have searched everywhere but nothing seems to work. Please Help!!
One approach is to assign the constant to a controller property:
function DemoController(DEP1,DEP2, /*.... ,*/ TYPE) {
console.log(TYPE.DYNAMIC); // works perfectly
this.TYPE = TYPE;
}
angular.module('myApp.controllers').controller('DemoController', DemoController)
Then use it in the template:
<div ng-controller="DemoController as $ctrl">
<span ng-if="$ctrl.type === $ctrl.TYPE.DYNAMIC">
...
</span>
</div>
Note: The ng-if directive uses creates a child scope. Consider instead using the ng-show directive which uses CSS and less resources.
You can use $rootScope and initilize it in run phase:
angular.module('app')
.run(function ($rootScope, TYPE) {
$rootScope.TYPE = TYPE
});
then you can use it directly in your HTML
Say I have several forms each with their own controller that look like this
<div ng-controller="MyController1 as ctrl">
<label>Some label </label>
</div>
<div ng-controller="MyController2 as ctrl">
<label>Some label </label>
</div>
And I have a global controller, that gets the information about the form names. Now I want to find the controllers for each form. For instance, if in my global controller function, I get the name of the first form, how can I find out that its controller is MyController1? Is that even possible?
Calling a controller from another controller is possible. But, I believe the problem you're trying to solve is somewhere else: the architecture of your app.
Ideally, your app should 'react' to changes on the state. The state should be kept in a single place (also called 'single source of truth'), ie. a service. Then you share that service state with as many controllers as you need.
You can either update the service state directly from the controller, or by calling a method on the service itself.
Look at the example below. I hope that sheds some light.
Cheers!
angular.module('app', [])
.service('MyService', function(){
var self = this;
self.state = {
name: 'John'
};
self.changeNameFromService = function() {
self.state.name = 'Peter';
}
})
.controller('Ctrl1', function($scope, MyService){
$scope.state = MyService.state;
$scope.changeName = function(){
// update the state of the scope, which is shared with other controllers by storing the state in a service
$scope.state.name = 'Mary';
}
})
.controller('Ctrl2', function($scope, MyService){
$scope.state = MyService.state;
// call a method defined in service
$scope.changeName = MyService.changeNameFromService;
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="Ctrl1">
Ctrl1: {{state.name}}
<button ng-click="changeName()">Change name!</button>
</div>
<div ng-controller="Ctrl2">
Ctrl2: {{state.name}}
<button ng-click="changeName()">Change name from service!</button>
</div>
</div>
I've gone through what must be 20 similar questions asked on SO but have yet to find a solution for my situation, so I hope you guys can help me out.
The goal is to sort the list of names by the property that's provided in the "sort-type" attribute of the directive, but only for the list within each controller (not all lists at the same time).
HTML
<div ng-controller="TestController as testOne">
<b>By:</b> {{testOne.sortType}}<br>
<b>Reverse:</b> {{testOne.sortReverse}}<br>
<div ng-repeat="item in testOne.list">
<p table-sort sort-type="name" sort-reverse="false">Sort by name</p>
<ul>
<li ng-repeat="childItem in testOne.childList | orderBy:testOne.sortType">{{childItem.name}}</li>
</ul>
</div>
</div>
<br><br>
<div ng-controller="TestController as testTwo">
<b>By:</b> {{testTwo.sortType}}<br>
<b>Reverse:</b> {{testTwo.sortReverse}}<br>
<div ng-repeat="item in testTwo.list">
<p table-sort sort-type="name" sort-reverse="false">Sort by name</p>
<ul>
<li ng-repeat="childItem in testTwo.childList | orderBy:testTwo.sortType">{{childItem.name}}</li>
</ul>
</div>
</div>
Javascript (Angular)
var app = angular.module('demo', []);
app.controller('TestController', TestController);
function TestController() {
var vm = this;
vm.sortType = 'oldOrder';
vm.sortReverse = false;
vm.list = [1];
vm.childList = [{ name: 'Jimmy' },
{ name: 'Danny' },
{ name: 'Bobby' }];
}
/////////////////////////////////////
app.directive('tableSort', tableSort);
function tableSort() {
var directive = {
restrict: 'A',
link: linkFunc,
};
return directive;
function linkFunc(scope, element, attr) {
element.on('click', function() {
if(scope.sortType === attr.sortType) {
scope.sortReverse = !scope.sortReverse;
} else {
scope.sortType = attr.sortType;
}
});
}
}
JSFiddle here
My actual application is a bit more complex but I've tried to abstract it as much as possible.
Thanks for looking :)
Ok Several things going on here:
you are using the controllerAs syntax on your templates but
you are changing scope variables in your directive. hence your
controller variables are never changed.
your directive is inside of the ng-repeat which means that
you are actuating actually on a child scope so if you are setting
variables directive on the scope your ng-repeat won't be able to
reach them because they are being set after the child scope are
created.
you are using element.on which executes outside of angular
digest which means you would have to call scope.$apply to let
angular know that something happened.
Take a look at this
https://jsfiddle.net/rez8ey12/
i hope it helps
Here i want to update controller scope value as per change in directive scope but its only working outside the ng-repeat and its not working inside ng-repeat..
HTML
<div ng-app="app">
<div ng-controller="MainCtrl">
<div>
<h3>
outside repeat
</h3>
<br> Name <strong>{{name}}</strong>
<div class="directive" my-directive name="name"></div>
<button ng-click="run()">See changes</button>
<br>
<br>
<h3>
Inside repeat
</h3>
<br>
<div ng-repeat="(k,v) in rawdata">
{{k}} {{v.key}} Name <strong>{{name}}</strong>
<div class="directive" my-directive name="name"></div>
<button ng-click="run()">See changes</button>
<br>
</div>
</div>
</div>
JS
var app = angular.module("app", []);
app.controller("MainCtrl", function($scope) {
$scope.name = "HappyNewYear2016";
$scope.run = function() {
alert($scope.name);
}
$scope.rawdata = [{
key: "event1",
}, {
key: "event2",
}];
});
app.directive("myDirective", function() {
return {
restrict: "EA",
scope: {
name: "="
},
template: [
"<div>",
"Name : <strong>{{name}}</strong>; Change name:<input type='text' ng-model='name' /><br/>",
].join("")
};
});
JSFiddle Link
Please help me in updating controller value from directive inside this ngrepeat..
Basically the problem is the $scope.name inside the controller is not the same as you are passing to directive inside the ng-repeat element, because ng-repeat does create a child scope which is prototypically inherited from the parent scope while looping through rawdata object.
There are several ways to solve this problem.
If you wanted to solved this child and parent scope related issue just by using $parent annotation before name which will refers to parent scope.
Plunkr With $parent in directive
Cons:-
But after certain point $parent will make you crazy. Like suppose if you have two or three hierarchy of child scope it will become like $parent.$parent.$parent.name which looks very wiered.
In your case name is of primitive datatype, so while creating child scope the primitive datatypes values of parent scope aren't accessible inside the child scope. That is the reason why you were using $parent to indicates that name belongs to parent scope. You could overcome this problem just by following do annotation while declaring object. Which will help you to make parent scope property available inside the child by following prototypal inheritance.
HTML
<div class="directive" my-directive name="model.name"></div>
Code
$scope.model = {
name: 'HappyNewYear2016'
};
Plunkr with Dot Notation
You could solve this problem just by passing name value from the run function on ng-click="run(name)"
Html
<div ng-repeat="(k,v) in rawdata">
{{k}} {{v.key}} Name <strong>{{name}}</strong>
<div class="directive" my-directive name="name"></div>
<button ng-click="run(name)">See changes</button>
<br>
</div>
Code
$scope.run = function(name) {
alert(name);
}
You could use controllerAs syntax while declaring controller with its alias and the pass the controller name property by its alias.
Working Plunkr
It's all because of how prototypical inheritance works as both scopes directive and controller have same name for model. Child directive shadows the controller model.
Instead of storing controller model directly as variable use it with object.
$scope.data = {
name : 'HappyNewYear2016'
}
Then use it asdata.name to setup in ng-repeat. It will reflect it in parent as well.
Apologies for the ambiguity of the question :-P
I have a single JavaScript object which contains all my data. And I have a controller which I will use multiple times throughout the application. So the controllers are all working on the same data, the data is added to the application using a service.
To support a read-only/edit mode interaction, I make two copies of the original data source in the service. When the user manipulates data, they are manipulating the edit mode data source. They can then press a button to save the data to the edit mode data source to the read-only mode data source (using angular.copy).
I would also like to have the instances of the controller work on just part of the data source rather than the whole thing.
The behavior I am seeing is angularjs is able to update the parts, keeping them both in sync; but when I press the button to perform the angular.copy, it seems to reassign the variable rather than adjust the value of where it was pointing.
Code below and here's a jsfiddle http://jsfiddle.net/q5ca5quq/1/
<html ng-app='app'>
<body>
<div ng-controller='a_controller as ctrl_1'>
read_mode_inner = {{ ctrl_1.read_mode_inner }}<br>
edit_mode_inner = {{ ctrl_1.edit_mode_inner }}<br>
<br>
<input ng-model='ctrl_1.edit_mode_inner[0].a'>
</div>
<br><br><br>
<div ng-controller='a_controller as ctrl_2'>
read_mode_inner = {{ ctrl_2.read_mode_inner }}<br>
edit_mode_inner = {{ ctrl_2.edit_mode_inner }}<br>
<br>
Change this and press button below <input ng-model='ctrl_2.edit_mode_inner[0].a'> <br>
<button ng-click='ctrl_2.change()'>Copy edit_mode_inner into read_mode_inner</button>
</div>
<script src='https://ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular.min.js'></script>
<script type='text/javascript'>
angular.module('app',[])
.factory('DataService', [function() {
data = {
xx : [{'a':1}, {'b':2}, {'c':3}],
yy : [{'a':1}, {'b':2}, {'c':3}]
}
return {
read_mode : data,
edit_mode : angular.copy(data)
}
}])
.controller('a_controller', ['DataService', function(DataService) {
var self = this;
window.s = self; // For debugging
self.read_mode = DataService.read_mode;
self.edit_mode = DataService.edit_mode;
self.read_mode_inner = self.read_mode.xx;
self.edit_mode_inner = self.edit_mode.xx;
self.change = function(){
self.read_mode_inner = angular.copy(self.edit_mode_inner);
}
}]);
</script>
</body>
</html>
http://jsfiddle.net/q5ca5quq/2/
You can share data between controllers using a service, but if you want all controller instances to update every time the value in service is changed, you'll need something like $watch inside your controller:
$scope.change = function(){
DataService.read_mode.xx = angular.copy($scope.edit_mode_inner);
}
$scope.$watch(function(){return DataService}, function(dataService){
$scope.read_mode_inner = dataService.read_mode.xx;
}, true);
For this to work you need to use angular $scope instead of this/self reference. Notice how using $scope also simplified angular notation in HTML. You don't need to name controllers separately:
instead of:
<div ng-controller='a_controller as ctrl_1'>
read_mode_inner = {{ ctrl_1.read_mode_inner }}<br>
edit_mode_inner = {{ ctrl_1.edit_mode_inner }}<br>
</div>
You only have to:
<div ng-controller='a_controller'>
read_mode_inner = {{ read_mode_inner }}<br>
edit_mode_inner = {{ edit_mode_inner }}<br>
</div>
because $scope takes care of the rest.