Understanding Angularjs scope - angularjs

I am working angularjs scope, I countered an interesting problem. I have the following html structure:
<body ng-app='myApp'>
<div ng-controller='myCtrl'>
<select ng-model='selectedOption'
ng-options='option.size for option in options'
ng-change='update(selectedOption)'></select>
</div>
<div ng-controller='myCtrl'>
<ul>
<li ng-repeat='asset in assets'>
{{asset}}
</li>
</ul>
</div>
</body>
Notice that i have 2 controllers that have the same name 'myCtrl'.
And the following is my javascript code:
var myApp = angular.module('myApp', []);
myApp.factory('Assets', function() {
return {
assets: [{test:1}]
};
});
myApp.controller('myCtrl', function($scope, Assets, $rootScope) {
$scope.assets = Assets.assets;
var temp = [
{test:5},
{test:6},
{test:7}
];
$scope.options = [
{size: 1},
{size: 2},
{size: 3},
{size: 4}
];
$scope.selectedOption = $scope.options[0];
$scope.update = function(value) {
console.log($scope.$id);
/*for(var i = 1; i < value.size; i=i+value.size) {
$scope.assets.push({
test: value.size
});
}*/
$scope.assets = temp;
};
});
When I run the code and select a option from the dropdown list, console will log '003' which is the first controller's $id. Why inside the 'update' function I can't get the second controller's scope? Is there a way to get it?
Here is a link to JSbin: http://jsbin.com/gecizuwaku/2/edit?html,js,console,output

You could, but you would need to invoke the update() function inside the second ng-controller="myCtrl" block.
The way ng-controller roughly works is that it creates a child scope of the scope of the element it is on. Then it instantiates the controller function, resolving any dependencies and passing them in, along with this new child scope as $scope. That means each ng-controller instance will create a new instance of the controller function with a new child scope supplied.
In your specific case, the two scopes created by ng-controller will be siblings.
You could see the second scope's $id by running the following in the developer console:
angular.element('ul').scope().$id
Or by invoking the update() method:
angular.element('ul').scope().update()
which would log the $id to the console. It will likely be the next sequential id, which in your case will be "004".

Related

Share $scope between DOM elements with same ng-controller

I'm stuck with something (I've been reading similar questions but none of them get me to the solution).
I have 2 DOM elements (let's say 2 div's) with different id's but the same ng-controller (this is for basic example, in my real app I have 2 differente pages but works the same).
<div id="layer1" ng-controller="appCtrl">
<select ng-model="selectedType" ng-options="type.label for type in ptype track by type.value" ng-change="changeType(selectedType.value)"></select>
</div>
<div id="layer2" ng-controller="appCtrl">
<select ng-model="selectedType" ng-options="type.label for type in ptype track by type.value" ng-change="changeType(selectedType.value)"></select>
</div>
And in JS
var myAppModule = angular.module('myApp', [])
.factory('selectedType', function(){
return{}
})
.controller('appCtrl', ['$scope',function($scope){
$scope.ptype = [
{
value: 1,
label:'Kg'
},
{
value: 2,
label:'Pza'
}];
selectedType = $scope.ptype[0];
$scope.changeType = function(value){
if(value==1){selectedType = $scope.ptype[0];}
else{selectedType=$scope.ptype[1];}
};
}])
As you can see I have the options for the SELECT and the ng-model, what I need is when change the selected value in any SELECT (doesn't matter which DIV) the other gets updated too.
Here a Plunker with code SEE HERE.
Thanks!
Using a Shared service like your attempt:
you are in the good way but you just forgot that :
1 - you use the scope in ng-model,
2 - to have 2 way data binding for factory,
bind the factory itself to a scope,
and use an item inside the factory not the factory itself (eg: data).
code:
.factory('selectedType', function(){
return {
data: {} // <---- We use 'data' here for example
}
})
and in the controller now:
.controller('appCtrl', ['$scope', 'selectedType',function($scope, selectedType) {
$scope.selectedType = selectedType; // <-- Not the selectedType.data (Important)
/* but the factory object */
$scope.ptype = [
{
value: 1,
label:'Kg'
},
{
value: 2,
label:'Pza'
}];
$scope.selectedType.data = $scope.ptype[0];
})
Now we dont need the ng-change at all:
<div id="layer1" ng-controller="appCtrl">
<select ng-model="selectedType" ng-options="type.label for type in ptype track by type.value"></select>
</div>
<div id="layer2" ng-controller="appCtrl">
<select ng-model="selectedType" ng-options="type.label for type in ptype track by type.value"></select>
</div>
plunker: https://plnkr.co/edit/5qg45F?p=preview
NB: Instead of using shared service, you can also use $rootScope or $scope.$parent for that.

ng-repeat is not refreshed when ng-click method called from directive

I am working on creating reusable directive which will be showing composite hierarchical data .
On first page load, Categories like "Server" / "Software"/ "Motherboard" (items array bound to ng-repeat) would be displayed . If user clicks on "Server" then it would show available servers like "Ser1"/"Ser2"/"Ser3".
html :
<div ng-app="myApp" ng-controller="myCtrl" ng-init="init()">
<ul>
<li ng-repeat="item in items">
<div my-dir paramitem="item"></div>
</li>
</ul>
</div>
Now first time Items are loading, but clicking on any item is not refreshing ng-repeat. I have checked ng-click, "subItemClick" in below controller, method and it is being fired. However the items collection is not getting refreshed.
http://plnkr.co/edit/rZk9cbEJU90oupVgcSQt
Controller:
var myApp = angular.module('myApp', []);
myApp.controller('myCtrl', ['$scope', function($scope) {
$scope.init = function() {
$scope.items = [{iname: 'server',subItems: ['ser1', 'ser2','ser3']}
];
};
$scope.subItemClick = function(sb) {
if (sb.subItems.length > 0) {
var zdupitems = [];
for (var i = 0; i < sb.subItems.length; i++) {
zdupitems.push({
iname: sb.subItems[i],
subItems: []
});
}
$scope.items = zdupitems;
}
};
}])
.directive('myDir', function() {
return {
controller: 'myCtrl',
template: "<div><a href=# ng-click='subItemClick(paramitem)'>{{paramitem.iname}}</a></div>",
scope: {
paramitem: '='
}
}
});
I am expecting items like ser1/ser2 to be bound to ng-repeat on clicking "Server" but it is not happening .
Any help?
I think that onClick is screwing up the method's definition of $scope. In that context, the $scope that renders the ngRepeat is actually $scope.$parent (do not use $scope.$parent), and you're creating a new items array on the wrong $scope.
I realize the jsfiddle is probably a dumbed down example of what you're dealing with, but it's the wrong approach either way. If you need to use a global value, you should be getting it from an injected Service so that if one component resets a value that new value is reflected everywhere. Or you could just not put that onClick element in a separate Directive. What's the value in that?

How can I allow multiple controllers to use one template with controllerAs syntax?

Say I have the following controllers:
function MainController() {
this.hello = function() {
// does stuff
}
}
function AnotherController() {
this.hello = function() {
// does other stuff
}
}
where controllerAs for MainController is set as mainController and controllerAs for AnotherController is anotherController. Now say I have the following template:
<div ng-click="mainController.hello()"></div>
Is it possible to allow AnotherController to use this template as well? How can I do that?
EDIT** To clarify, I have two different views calling this template. Whenever the main view calls the template I want the template to be
<div ng-click="mainController.hello()"></div>
but when the other template calls it I want it to be
<div ng-click="anotherController.hello()"></div>
I don't want to have two templates because almost everything in the template remains the same except for ng-click.
It is absolutely!!! controllerAs Syntax is awesome because it presents more clarity and control when it comes to choosing which variables you are pulling from certain controllers in your app.
In your Controllers Definitions:
var app = angular.module('AngularApplication', []);
app.controller('MainAppController', [MainAppControllerFn])
.controller('AnotherController', [AnotherControllerFn])
function MainAppControllerFn($scope) {
this.array = [1, 2, 3, 4, 5, 6];
this.hello = "World";
}
function AnotherControllerFn() {
this.hello = function() {
return "Hello";
}
}
In your Template:
<body ng-app="AngularApplication">
<div class="container" ng-controller="MainAppController as main" bindToController="true">
{{main.hello}}
<div class="container" ng-controller="AnotherController as another" bindToController="true">
{{another.hello()}}
{{main.hello}}
</div>
</div>
</body>
I have a codepen here that you can reference.
One thing to keep in mind is that if you want to access a parent controller from your child controller, you will need to inject scope into your controller so that you can access the "controller" itself.
So main.hello in your controller would look like $scope.main.hello.

Accessing inherited scope with Controller As approach

With the original way to define controllers, accessing the parent's scope was fairly trivial, since the child scope prototypically inherits from its parent.
app.controller("parentCtrl", function($scope){
$scope.name = "Parent";
})
.controller("childCtrl", function($scope){
$scope.childName = "child of " + $scope.name;
});
<div ng-controller="parentCtrl">
{{name}}
<div ng-controller="childCtrl">
{{childName}}
</div>
</div>
The Controller-As approach seems to be the recommended way to declare a controller. But with Controller-As, the above approach no longer works.
Sure, I can access the parent scope with pc.name from the View:
<div ng-controller="parentCtrl as pc">
{{pc.name}}
<div ng-controller="childCtrl as cc">
{{cc.childName}}
</div>
</div>
I do have some issues with this (potential for spaghetti code), but this question is about accessing the parent scope from the child controller.
The only way I can see this working is:
app.controller("parentCtrl", function(){
this.name = "parent";
})
.controller("childCtrl", function($scope){
$scope.pc.name = "child of " + $scope.name;
// or
$scope.$parent.pc.name = "child of " + $scope.name;
// there's no $scope.name
// and no $scope.$parent.name
});
So now, the child controller needs to know about "pc" - except, this should (in my mind) be restricted to the view. I don't think a child controller should know about the fact that a view decided to declare a ng-controller="parentCtrl as pc".
Q: What's the right approach then?
EDIT:
Clarification: I'm not looking to inherit a parent controller. I am looking to inherit/change the shared scope. So, if I was to amend the first example, I should be able to do the following:
app.controller("parentCtrl", function($scope){
$scope.someObj = {prop: "not set"};
})
.controller("childCtrl", function($scope){
$scope.someObj.prop = "changed";
});
After researching, I came to the following realization:
Controller-As approach is NOT a substitute for using $scope. Both have their place, and can/should be used together judiciously.
$scope does exactly what the name implies: i.e. it defines ViewModel properties on the $scope. This works best for sharing scope with nested controllers that can use the $scope to drive their own logic or to change it.
Controler-As defines the entire controller object as a ViewModel with a named scope (via the controller's alias). This works best only in the View (but not other controllers), if the View decides if it wants to reference a specific controller ViewModel.
Here's an example:
var app = angular.module('myApp', []);
// Then the controllers could choose whether they want to modify the inherited scope or not:
app.controller("ParentCtrl", function($scope) {
this.prop1 = {
v: "prop1 from ParentCtrl"
};
$scope.prop1 = {
v: "defined on the scope by ParentCtrl"
};
})
.controller("Child1Ctrl", function($scope) {})
.controller("Child2Ctrl", function($scope) {
// here, I don't know about the "pc" alias
this.myProp = $scope.prop1.v + ", and changed by Child2Ctrl";
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.min.js"></script>
<body ng-app="myApp">
<div ng-controller="ParentCtrl as pc">
<div ng-controller="Child1Ctrl">
<div>I know about the "pc" alias: {{pc.prop1.v}}</div>
</div>
<div ng-controller="Child2Ctrl as ch2">
<div>I only care about my own ViewModel: {{ch2.myProp}}</div>
</div>
</div>
You should do like :
html
<div ng-controller="ChildController as child">
<button type="button" ng-click="child.sayMe()">Say me!</button>
</div>
js
var app = angular.module('myApp', [])
app.controller('BaseController',function() {
this.me = 'Base';
this.sayMe= function() {
alert(this.me);
}
});
app.controller('ChildController', function($scope, $controller) {
var controller = $controller('BaseController as base', {$scope: $scope});
angular.extend(this, controller);
this.me = 'Child';
});
take a look at https://docs.angularjs.org/guide/controller
For anyone looking to simply access the parent scope programmatically, use the $scope service, find the parent scope, and access the name used for the parent scope's controllerAs, e.g.:
$scope.$parent.someName.doSomething();

angular scope variable has no data

I'm pretty new to angular js but it seems like my simple code should work. Here is the html:
<body ng-app="MyHomepage">
<div ng-controller="RedditLoad">
{{a}}
<ul>
<li ng-repeat="article in a">
{{article.data.title}}
</li....
and here is my angualr_app.js:
var App = angular.module('MyHomepage', [])
function RedditLoad($scope){
$.getJSON("http://www.reddit.com/.json?jsonp=?", function(data) {
var data_array = [];
$.each(data.data.children, function(i,item){
data_array.push(item);
});
console.log(data_array);
$scope.a = data_array;
});
}
What am I doing wrong? console.log(data_array); is showing the correct values but the data wont seem to get passed to the template.
The getJSON callback isn't executed in the angular context so angular doesn't know about your changes and won't refresh the bindings. When code is called from an external source (like a jQuery event ), you have to encapsulate your scope changes in an $apply call:
$scope.$apply(function{
$scope.a = data_array;
}

Resources