Ionic - AngularJS: calling methods via template outside of Controller - angularjs

So, here's a sample code:
<div ng-controller="MyControllerOne">
<span ng-click="foobar()">Click Me!</span>
</div>
Can I, from that template, without changing controller, call the function foobar() in MyControllerTwo:
.controller('MyControllerOne', function($scope) {
//some code
})
.controller('MyControllerTwo', function($scope) {
// method I wanna call
function foobar(){
}
})

While not the prettiest solution, it is technically possible...ish.
If you update your HTML to:
<div ng-controller="MyControllerOne">
<span ng-controller="MyControllerTwo as mct" ng-click="mct.foobar()">Click Me!</span>
</div>
Then you should get your expected results.

You can call method which is in another controller from the template by injecting '$controller' service in the controller. Below is the demo and code.
You can see demo here: http://plnkr.co/edit/oBEKxamgJv0uDVsJJwth?p=preview
HTML:
<body ng-controller="MainCtrl">
<div ng-click="fooBar()">Click Me!</div>
</body>
JS:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $controller) {
$controller('SubCtrl', {$scope: $scope});
});
app.controller('SubCtrl', function($scope) {
$scope.fooBar = function() {
alert('second controller');
};
});

Pretty old question, but if any one is still looking for an alternative answer ...
It should be possible to use $emit or $broadcast.
Like from ControllerOne :
$rootScope.$broadcast('callToFoobat',{});
And then from ControllerTwo :
$scope.$on('callToFoobat', function(){
// whatever you want, so why not a call to foobar
})
Just a rough solution. Might be more elegant or lighter than just $rootScope.$broadcast. And maybe think about stoping propagation whenever needed.

Related

View is not updated after the $http async call when using controllerAs syntax

I am learning angularjs and I am trying to use the controllerAs syntax as I am from Java background and this would make more sense to me but I am having trouble understanding the digest loop.
I am trying to do a http call and update the variable in the controller.When I am using $scope in controller the view is updated after the data is received but when I am using the controllerAs syntax the view is not updated.
Codepen with $scope Syntax
http://codepen.io/eternal15/pen/BzANEw?editors=1111
<html>
<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.2/angular.min.js"></script>
</head>
<body ng-app="Test" ng-controller="testCtrl">
{{output}}
<button ng-click="onClick()">Test</button>
</body>
</html>
//JS FILE
angular.module("Test", []).controller('testCtrl', ['$scope','$http', function($scope, $http){
$scope.output = "Loading";
$scope.onClick = function(){
console.log('clicked');
$http.get('http://jsonplaceholder.typicode.com/posts').then(function(data){
$scope.output = "worked!!";
console.log($scope.output);
})
}
}]);
Codepen with controllerAs Syntax (View not updated)
http://codepen.io/eternal15/pen/yJKoaZ?editors=1011
<html>
<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.2/angular.min.js"></script>
</head>
<body ng-app="Test" ng-controller="testCtrl as test">
{{test.output}}
<button ng-click="test.onClick()">Test</button>
</body>
</html>
//JS File
angular.module("Test", []).controller('testCtrl', ['$http', function($http){
this.output = "Loading";
this.onClick = function(){
console.log('clicked');
$http.get('http://jsonplaceholder.typicode.com/posts').then(function(data){
this.output = "worked!!";
console.log(this.output);
})
}
}]);
I have read about the controllerAs syntax and I think it would add the object (test in the example above) under scope and thus the variables are accessible using (test) object.
So the digest loop runs after $http call because the view is updated in the first example using $scope. Since the digest loop is executed the object test in the second example should also be updated right?
Also i tried to inject $scope and do $scope.$apply() and that also didn't work and it gave me this error
Error: [$rootScope:inprog] http://errors.angularjs.org/1.5.2/$rootScope/inprog?p0=%24digest
I would like to know what I am doing wrong. Although i could go back to using the $scope format, I would like to know if I am doing something wrong or should I add other statements to watch the variables and update the values manually.
Thanks in advance
this has a different meaning inside function. Assign this to a variable and use it. Try:
angular.module("Test", []).controller('testCtrl', ['$http', function($http){
var vm = this;
vm.output = "Loading";
vm.onClick = function(){
console.log('clicked');
$http.get('http://jsonplaceholder.typicode.com/posts').then(function(data){
vm.output = "worked!!";
console.log(vm.output);
})
}
}]);
This is because of javascripts closures. When defining a new function you're creating a new scope, hence the keyword this has a new meaning for each new scope.
To solve this, define the controllers scope at the top of your controller. Common names used are either vmor $ctrl.
Your controller would then look somehting like this:
angular.module("Test", []).controller('testCtrl', ['$http', function( $http){
var $ctrl = this;
$ctrl.output = "Loading";
$ctrl.onClick = function(){
console.log('clicked');
$http.get('http://jsonplaceholder.typicode.com/posts').then(function(data){
$ctrl.output = "worked!!";
//$scope.$apply();
})
}
}]);

Change ng-show in another controller?

I want to change ng-show in another controller than ng-show is.
myApp.controller('popupCtrl', function() {});
myApp.controller('changePopup', function($rootScope){
// now i wanna show my Ppopup
$rootScope.popup = true;
});
<div ng-controller="popupCtrl">
<div ng-show="popup">
Popuptext
</div>
</div>
But this doesn't work... How can I fix it?
Thanks!
So first thing, you should never add to the $rootScope or change it in anyway. It has been optimised by the angular team.
Second thing, there is no need to involve the $rootScope.
Here is a demo showing how to communicate across two controllers.
The key is the event aggregator pattern:
Communicator.register(function (newValue) {
vm.value = Communicator.value;
});
I created a function in the Communicator to register a callback function. The aim is that when a value gets changed the callback function is fired off. I.e. an event is triggered (change event).
The second key part is fire that change event off:
Communicator.change(!Communicator.value);
Here we pass through to the change function a new value which will do two things:
Update the internal value so we can keep track of it
Loop through all the registered callbacks and execute them passing in the new value.
By implementing this pattern, we can minimise the extent to which we communicate around our application ($rootScope can have a tendency to traverse the scope heirarchy when you $broadcast).
Now we can follow more closely the principle of single responsibility. Our class is aptly named in its current scope, when we look at this factory we can tell it is supposed to "communicate".
Finally, with a global event aggregator pattern ($rootScope) it is far more difficult to keep track of where these events are being broadcast from, and where they'll end up. Here we don't have that issue
One way to solve this is to use $rootScope.$broadcast
Here is an example: http://plnkr.co/edit/EmJnZvXFRWv6vjKF7QCd
var myApp = angular.module('myApp', []);
myApp.controller('popupCtrl', ['$rootScope', '$scope', function($rootScope,$scope) {
$scope.popup = false;
$rootScope.$on('changePopup', function(event, data) {
$scope.popup = !$scope.popup;
});
}]);
myApp.controller('changePopup', ['$rootScope', '$scope', function($rootScope, $scope) {
$scope.changePopup = function() {
$rootScope.$broadcast('changePopup', 'data could be sent here');
}
}]);
View:
<div ng-controller="popupCtrl">
<div ng-show="popup">
Popuptext
</div>
<div ng-controller="changePopup">
<button ng-click="changePopup()">Change the popup</button>
</div>
Using a service/factory is a better solution for cross controller communication if you are working on a large application, but for a smaller app I would say using $broadcast, $emit and $on is sufficient.
Here is a working demo for you - sorry I changed the controller names, but I am sure you will be able to build on this. Good luck
angular.module('myApp', [])
.controller('c1', function($scope) {
// now i wanna show my Ppopup
$scope.popup = false;
$scope.$on('popup', function() {
$scope.popup = true;
});
})
.controller('changepopup', function($rootScope, $scope) {
// now i wanna show my Ppopup
$scope.clicker = function() {
$rootScope.$broadcast('popup')
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="c1">
<div ng-show="popup">
Popuptext
</div>
</div>
<button ng-controller="changepopup" ng-click="clicker()">Click me</button>
</div>

Angular Controller as NewController 2 way binding works with this.function

I'm trying to understand why I must use the as in order that the two-way binding will work with this inside a controller.
working example:
<div ng-controller="MyController as TestController">
{{TestController.test()}}
</div>
<script>
var app = angular.module('myApp', []);
app.controller('MyController', function(){
this.test = function test(){
return "test";
};
});
</script>
not working example:
<div ng-controller="MyController">
{{MyController.test()}}
</div>
<script>
var app = angular.module('myApp', []);
app.controller('MyController', function(){
this.test = function test(){
return "test";
};
});
</script>
If you want to use this in your controllers you need to use the controller as syntax otherwise you have to use $scope in your controllers. If you didn't use controller as the controller would need to be:
app.controller('MyController', function($scope){
$scope.test = function test(){
return "test";
};
});
and the view would need to be:
<div ng-controller="MyController">
{{test()}}
</div>
One of the benefits of the controller as syntax is it helps to promote the use "dotted" object in the View which helps to avoid any reference issues that may occur without "dotting". For more info on scope reference issues take a look at this post
Not really an answer to your question, but normally you'd define functions you want to invoke from the DOM on the Controller's $scope.
Example:
<div ng-controller="MyController">
{{test()}}
</div>
<script>
var app = angular.module('myApp', []);
app.controller('MyController', function($scope){
$scope.test = function test(){
return "test";
};
});
</script>
http://plnkr.co/edit/lbgG9MCJ1kNBhArLpEpc?p=preview
Edit: Sorry, forgot to update the code in my post. The plnkr should've been right all along though.
Thanks to Wayne Ellery:
It's because Angular added the controller as syntax in 1.2 which enables you to work with this. ng-controller="MyController as myController". Think of it as var myController = new MyController();. It's essentially scoping an instance of MyController to myController.

Angular.js "Controller as ..." + $scope.$on

If i'd like to use the "Controller as ..." syntax in Angular, how should I approach things like $scope.$on(...) that i need to put inside the controller?
I get an impression i could do it some other way than the one shown below.
Here, to get $scope.$on working i bind "this" to the callback function. I tried to invoke $on on "this" inside the controller but it didn't work.
Could you give me a hint here or if i'm completely messing up, could you point me to some right way to do it? Thanks.
main.js:
angular.module('ramaApp')
.controller('MainCtrl', ['$scope', '$location', function ($scope, $location) {
this.whereAmINow = 'INDEX';
$scope.$on('$locationChangeStart', function(event) {
this.whereAmINow = $location.path();
}.bind(this));
this.jumpTo = function(where) { $location.path(where); }
}]);
index.html:
<div ng-controller="MainCtrl as main">
<p>I am seeing the slide named: {{ main.whereAmINow }}</p>
<div ng-click="main.jumpTo('/slide1')">Slide 1</div>
<div ng-click="main.jumpTo('/slide2')">Slide 2</div>
<div ng-click="main.jumpTo('/slide3')">Slide 3</div>
</div>
As far as I know, you need to inject $scope if you want $scope watchers/methods. ControllerAs is just syntactic sugar to enable to see more clearly the structure of your nested controllers.
Three ideas though which may simplify your code.
Use var vm = this, in order to get rid of the bind(this).
var vm = this;
vm.whereAmINow = "/";
$scope.$on('$locationChangeStart', function(event) {
vm.whereAmINow = $location.path();
});
vm.jumpTo = function(where) {
$location.path(where);
}
The whole whereamINow variable makes sense putting it into the initialization of app aka .run() (before config) since I assume it's a global variable and you don't need to use a $scope watcher/method for it.
Another option is to use a factory to make the changes persist, so you simply create a location factory which holds the current active path.
Inject $scope and your controller is accessible by whatever you named it
EG:
$stateProvider
.state('my-state', {
...
controller: 'MyCtrl',
controllerAs: 'ctrl',
...
});
.controller('MyCtrl', function($scope) {
var $this = this;
$scope.$on('ctrl.data', function(new, old) {
// whatevs
});
$timeout(function() {
$this.data = 'changed';
}, 1000);
});
Ok, i think people just do the same, just as in this question:
Replace $scope with "'controller' as" syntax

AngularJS: ng-app inside ng-include

I have a template like this.
<body ng-app="demo" ng-controller="demo">
<div ng-include="/main.html">
</div>
</body>
And the main.html is.
<div ng-app="main" ng-controller="main>
""
</div>
here is the js.
JS-1
var myapp = angular.module('demo', []);
myapp.controller('demo', function($scope,$routeParams, $route,$http) {
$scope.variable="444"
})
JS-2
var mainapp = angular.module('mainapp', []);
myapp.controller('main', function($scope,$routeParams, $route,$http) {
})
Is it possible to access the scope of JS-1 inside JS-2?, if yes how, if no is there any solution to this.Thanks.
It depend what you want to do.
If you want read $scope.variable variable from JS-1, you should see it in JS-2 $scope.
If you want modify $scope.variable form JS-1, you should create method in JS-1:
$scope.changes = function(data){
$scope.variable = data;
}
This method also should be available in JS-2 $scope.
This isn't nice solution but should work.
The best solution is to create service which will provide operations on JS-1 fields.

Resources