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

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

Related

Ionic - AngularJS: calling methods via template outside of Controller

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.

AngularJS - update a directive outside the view

I want to update the scope inside a directive that is outside of my main view, here's my code:
index.html
<!-- the directive I want to update -->
<nav sitepicker></nav>
<section id="content-wrapper">
<!-- main content-->
<div ui-view></div>
</section>
sitepicker is essentialy just a dropdown menu that contains some html structure.
sitepicker.html
<span>{{currentWebsite}}</span> <-- this is the one I want to update
<ul>
<li ng-repeat="website in websites">{{website.name}}</li>
</ul>
and the JS:
.controller('sitepicker', function($scope, websiteService)
$scope.website = websiteService.currentWebsite; // not updating eventhough I update this in overview.js
});
overview.js
.controller('OverviewCtrl', function($scope, websiteService) {
websiteService.currentWebsite = website; // assume that this value is dynamic
});
but currentWebsite is not changing. How can I work around this? I want to avoid using $rootScope because I know it's bad.
Here's my service:
.factory('websiteService', function() {
var currentWebsite;
return {
currentWebsite: currentWebsite
};
});
Edit: Adding a watch like this works but i'm not sure if its good
.controller('sitepicker', function($scope, websiteService)
$scope.$watch(function() {
$scope.website = websiteService.currentWebsite;
});
});
Solution #1
We can add $watch to makes this possible
.controller('sitepicker', function($scope, websiteService)
$scope.$watch(function(){
return websiteService.currentWebsite;
}, function(newValue){
// Do something with the new value
});
});
Solution #2
We can also define websites in our main controller. Then, we can update it in our overview child controller like so:
.controller('sitepicker', function($scope, websiteService)
$scope.$parent.website = websiteService.currentWebsite;
});
it is important to use $parent
Since we update the controller that wraps up the whole app, we will be able to access it from anywhere, any directive, controller, view, etc.

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>

AngularJS access parent scope

I'm new to angularjs and I'm trying to do some things that I don't know if is a good practice.
Well, I have a main page where I want to show the messages of all other controller and views.
In this "main" view I have the following
<div class="row" data-ng-repeat="msg in messages">
<div class="col-lg-12">{{mgs.message}}
<a data-ng-click="close(msg)">×</a>
</div>
</div>
When I set a message in my MainController the message is shown, but when I navigate to another controller it isn't.
Google told me that it happens because I'm working with different scopes.
I would like to know:
How do I add messages into the "parent" scope? or
How do I call a method of MainController inside my Functionality2Controller or
What are the other better ways to show messages in the main page?
Different controller use different scopes and thats right and you should try to change that. First thing you could do is using the $rootScope, where all other scopes are derivated from. But that kind of a misuse, the imo better solution is creating a shared messageService:
.factory('messageService', [
function() {
var messages = [];
return {
getMessages: function() {
return messages;
},
addMessage: function(message) {
messages.push(message)
}
}
}
])
and inject it to your controller:
.controller('ctrl', ['$scope', 'messageService',
function($scope, messageService) {
$scope.messages = messageService.getMessages();
}
])
Plunker: http://plnkr.co/edit/6cnbx9okRA03tut7JzuF?p=preview

Tabs directive not exposing an api in my scope

So I'm trying the Tabs directive and having some problems.
the structure is something like:
//routes
$routeProvider..when('/course/:id', {
controller: 'CourseCtrl',
templateUrl: '/app/views/course.html'
});
//course.html
<div ng-controller="CourseTabsCtrl">
<tabset>
<tab>
<tab-heading>Title</tab-heading>
<div ng-include="'/view.html'"></div>
</tab>
....
</tabset>
</div>
Problem is i can't access the api to enable or disable tabs, select a tab, in none of the controllers CourseTabsCtrl or CourseCtrl.
Is this because the directive is working on an isolated scope? and if so, is there a way to get around that? How can i fix it?
Thanks
Looking at the source and the documentation, you should be able to pass in an expression to the <tab> directive, that dictates whether it is enabled or disabled.
app.controller('CourseTabsCtrl', function ($scope) {
$scope.expr = true;
});
<div ng-controller="CourseTabsCtrl">
<tabset>
<tab disabled="expr">
<tab-heading>Title</tab-heading>
<div ng-include="'/view.html'"></div>
</tab>
....
</tabset>
</div>
If however, this is not enough for you. You could 'hack' the tabset directive and use another controller than the one currently specified. Now, you would have to replicate the old behaviour of the default TabSetController, but you could add functionality on top of it to cater to your needs.
The best way (I've found) to do this is to decorate the directive itself.
Like so:
app.config(function ($provide) {
$provide.decorator('tabSetDirective', function ($delegate) {
// $delegate in a directive decorator returns an array. The first index is the directive itself.
var dir = $delegate[0];
dir.controller = 'CourseTabsController';
return $delegate;
});
});
You could build further on this and pass in the controller to the directive itself.
<tabset ctrl="someCustomCtrl"></tabset>
The config block would then look like this:
app.config(function ($provide) {
$provide.decorator('tabSetDirective', function ($delegate) {
// $delegate in a directive decorator returns an array. The first index is the directive itself.
var dir = $delegate[0];
dir.controller = function ($scope, $element, $attrs, $controller) {
return $controller($attrs.ctrl, {
$scope: $scope
});
};
return $delegate;
});
});
Note: If you go with the decorator way, you may have to do it in the config block for the angular-ui module. In that case, you will probably want to have a look here, to be able to configure third party modules without touching their core code.
A second gotcha to the passed-in controller way, is that you need to make use of $injector in order to get dependencies (apart from $scope, $element and $attrs) into the controller. This can either be done in the config block, or in the controller itself by adding $injector as a dependency, like so:
app.controller('CourseTabsController', function ($scope, $injector) {
var $timeout = $injector.get('$timeout');
// etc...
});
What the f* am I on about?
Given that I don't personally work with the AngularUI Bootstrap components, and the fact that there is no plunker/jsBin available I'm throwing out some tips n tricks on how to add custom behaviour to third party components, without polluting their core code.
To address the questions at the end of your post:
Is this because the directive is working on an isolated scope?
It very well might be, the idea of isolated scopes is to not pollute the outside world with their inner properties. As such, it's highly likely that the only live 'endpoint' connected to the <tab> directive API is the default AngularUI TabSetController.
... and if so, is there a way to get around that? How can i fix it?
You can either do what I've suggested and roll your own controller (bare in mind that you should duplicate the code from the TabSetController first), that way you should have full access to the endpoint of the <tab> directive. Or, work with the options that are available to the directive as of this writing and wait for some more functionality to be introduced.
I'll try to fire up a jsBin soon enough to further illustrate what I mean.
Edit: We can do the whole decorator dance and duplication of the old controller behaviour, without the need to pass in a new controller. This is how we would achieve that:
app.config(function ($provide) {
$provide.decorator('tabSetDirective', function ($delegate, $controller) {
// $delegate in a directive decorator returns an array. The first index is the directive itself.
var dir = $delegate[0];
var origController = dir.controller;
dir.controller = function ($scope) {
var ctrl = $controller(origController, { $scope: $scope });
$scope.someNewCustomFunction = function () {
console.log('I\'m a new function on the ' + origController);
};
$scope.someNewCustomFunction();
return ctrl;
};
return $delegate;
});
});
Here's a jsBin illustrating the last example: http://jsbin.com/hayulore/1/edit

Resources