AngularJS ControllerAs syntax and controller injected variables - javascript

I'm writing a directive and trying to stick to the John Papa style guide. So I've also decided to jump on the ControllerAs syntax wagon and I've got a tiny directive like below:
(function() {
angular
.module('htApp')
.directive('newsletterSignup', newsletter_signup);
function newsletter_signup($http) {
var directive = {
templateUrl: '../whatever.tpl.html',
restrict: 'EA',
controller : controller,
controllerAs: 'vm',
bindToController: true
};
return directive;
controller.$inject = ['$http'];
function controller($http) {
var vm = this;
// $http is here ... all is good, but I don't need it
function doSubmit(form) {
// I need $http here, but it is null
debugger;
};
vm.doSubmit = doSubmit;
}
}
})();
This is a newsletter signup service. I'm going to have to do an HTTP request, therefore I'm injecting it into the controller. All is fine - but calling the vm.doSubmit(--formname-here--) function from the template results in me not being able to find the $http service.
So my question: how can I access the injected $http from the doSubmit() function?
EDIT
I'll include the view code - but no worries - the plumbing works:
<button class="btn btn-yellow" ng-click="vm.doSubmit(newsletterform)" translate>
footer.ok_button_text
</button>
EDIT 2
As it turns out, #Tek was right - the code works. I think the reason I didn't see it was because (I think) the JS runtime in Chrome optimizes the $http away when it knows it's not going to be called.
This code works fine. I think this is because the runtime aniticipated the usage of $http in the console.log() function call. However - if I remove that line I get this ( which was why I had this problem in the first place ):
Notice that I commented out the console.log - thus the doSubmit() call never uses $http. Now - when I call $http in the console - it's not defined.

The problem is here:
return directive;
controller.$inject = ['$http'];
function controller($http) {
...
controller function is defined when return statement is executed. But controller.$inject will never be defined. Also, newsletter_signup function misses the corresponding $inject.
This won't be minified properly. While this will be minified.

Your example works just fine: example.
But as for me John Papa has pretty strange vision of angular style, I prefer this style guide.

Related

Scope variable not visible within ng-click handler

I'm pretty new to Angular and am trying to figure out what's wrong here. There is a controller defined like this:
(function(){
function myController($scope, CommsFactory) {
$scope.doSomething = function() {
var x = $scope; // <- Doesn't work because $scope is not defined
}
}
angular
.module('aModule')
.controller('myController', myController);
})();
The doSomething() method is then called by a button click like:
<input type="button" ng-click="doSomething()" class="btn--link" value="do it"/>
This seems straightforward to me but the problem is that, when I break within the method, $scope is not defined. This is different from most of the examples I've seen, and I can't figure out why. Shouldn't it be visible here? Obviously a lot of code is missing - I've tried to show only the relevant bits - could I be missing something somewhere else?
You're declaring a module then you need to add [].
Something like this:
angular.module('aModule', [])
.controller('myController', myController);
Usage
angular.module(name, [requires], [configFn]);
Arguments
name.- The name of the module to create or retrieve.
requires (optional).- If specified then new module is being created. If unspecified then the module is being retrieved for further
configuration.
configFn (optional).- Optional configuration function for the module. Same as Module#config().
Please, I would to recommend to read this guide about Angular Module:
angular.module
(function() {
function myController($scope) {
$scope.doSomething = function() {
var x = $scope;
console.log(x);
}
}
angular
.module('aModule', [])
.controller('myController', myController);
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div data-ng-app="aModule">
<div data-ng-controller="myController">
<input type="button" ng-click="doSomething()" class="btn--link" value="do it" />
</div>
</div>
Your code is generally working fine as demonstrated in this fiddle.
Your main problem seems to be in the usage of $scope. $scope is an object containing all variables and methods which should be available in the corresponding template. For this reason, you would always reference a member of $scope, instead of the whole object.
Furthermore, John Papas AngularJS style guide recommends the usage of controllerAs in favor of $scope for multiple reasons as stated in Y030
By convention, you should also give your controllers uppercase names and use explicit Dependency Injection
A typical use case would rather look like:
(function(){
angular
.module('aModule', [])
.controller('myController', MyController);
MyController.$inject = ['$scope', 'CommsFactory'];
function MyController($scope, CommsFactory) {
var vm = this;
vm.doSomething = doSomething;
function doSomething() {
var $scope.x = "Did it!";
}
}
})();
SOLVED: It turns out that what I was experiencing had something to do with the way in which the Chrome debugger works. It appears to do some kind of lazy loading of variables defined outside of the function in which you break (or at least this as far as I've characterized it). What this means, at least in my case, is that if I break inside of the method, and $scope isn't actually used within that method (which, unfortunately, I was doing a lot because I was trying to verify that $scope was visible), then the debugger will report that $scope is unavailable.

AngularJS : directive function not called

The function that I'm providing to define my directive is never called. It used to work fine but suddenly stopped working and I have no idea why.
Here's my directive:
portalApp.directive('contactPanel', function () {
console.log("NEVER SHOWN!");
return {
restrict: 'AE',
replace: 'true',
templateUrl: 'partials/account/contactPanel.html',
scope: {
contact: '=contact',
primaryRoleName: '#',
roleName: '#',
primary: '=primary',
locations: '=locations'
},
controller: function (userService, $rootScope, $scope) {
...snip...
}
};
});
and here an example of its use:
<contact-panel contact="user.account.contacts.billing" role-name="billing"
locations="locations"></contact-panel>
Note that I'm using the correct casing, i.e. camel-case in JS and hyphenation in HTML.
The key clue is that the message that's logged in the second line (i.e. 'NEVER SHOWN!') never shows up in the console. If I log a message immediately before the directive declaration then that shows up, so this code is being executed by the interpreter, but the framework is just never using my declaration.
I'd love to have an answer obviously, but I'd also love to hear of some approaches to debug this kind of problem.
I can see only 2 possibilities that would exhibit the behavior you described. Either the HTML with the directive was not compiled or the directive is not registered.
The "not compiled" case could be because the directive is used outside of the Angular app, for example:
<div ng-app="portalApp">
...
</div>
...
<contact-panel></contact-panel>
Or, if you added the HTML dynamically, but did not $compile and link it.
The "not registered" case could be due to re-registration of the app's module. In other words, you might have the following case:
var portalApp = angular.module("portalApp", []);
portalApp.directive("contactPanel", function(){...});
// then elsewhere you use a setter:
angular.module("portalApp", []).controller("FooCtrl", function(){});
// instead of the getter:
// var app = angular.module("portalApp");
The second call to angular.module("portalApp", []) removes the previous registration of .directive("contactPanel", ....
I figured out the cause of this problem. At some point I must've accidentally moved the directive into a configuration block like this:
portalApp.config(function ($stateProvider, $urlRouterProvider) {
portalApp.directive('contactPanel', function () {
console.log("NEVER SHOWN!");
return {
...snip...
};
});
});
Once I moved it back out of the configuration block and into the global scope the directive immediately rendered as it should.
The reason why this doesn't work is that angular runs the configuration code after it's run the directives, like this:
runInvokeQueue(moduleFn._invokeQueue); // runs directives
runInvokeQueue(moduleFn._configBlocks); // runs config blocks
So things added to the _invokeQueue (which the directive() function does) from within a config block will never be executed.
Thank you to all those who tried to help.

How to inject services into a provider with angular 1.3

In every piece of information I can find (including the angular documentation), the way to inject a service into a provider is through the $get method:
var myApp = angular.module('myApp', []);
myApp.provider('helloWorld', function() {
this.$get = function() {
return {
sayHello: function() {
return "Hello, World!"
}
}
};
});
function MyCtrl($scope, helloWorld) {
$scope.hellos = [helloWorld.sayHello()];
}
This would work perfectly in angular 1.2 and below: http://jsfiddle.net/1kjL3w13/
Switch to angular 1.3 though, and the $get function completely breaks. It seems that whatever's returned from the $get function is no longer used to instantiate the provider, and thus is now useless for injecting f.e. services.
Same example as above, but using angular 1.3: http://jsfiddle.net/duefnz47/
This is exactly the behavior provided in the angular documentation. So either the documentation is wrong or I've completely misunderstood it. I don't really care if the $get method works as before or not though, I just need to be able to inject services reliably into my provider.
Problem is you are using global controller which is not valid according to angular 1.3
So use
angular.module('myApp').controller('MyCtrl',function ($scope, helloWorld) {
$scope.hellos = [helloWorld.sayHello()];
});
Here is updated fiddle
**
Migration Document official
**
Hope it help :)

Pass External Function into Angular Directive

I'm struggling to find an elegant way of passing an external/out-of-scope/non angular function into a directive. So far the only way I could see to do this was by passing the function name as a string into angular and then using eval which does not seem so nice. Here is an example of that on Plunker
http://plnkr.co/edit/L9GGDkxwh4IGNufXB8yg?p=preview
Using extFunc:'&' in the scope only work for functions in the scope so that does not work for me.
Is there a better way of doing this? I realise in my example one would just include the function inside the directive or controller but this is not always practical.
<script>
function nonAngularFunction( someText ) {
alert( someText );
}
<script>
<my-directive ext-func="nonAngularFunction('Hi there')" ></my-directive>
Sorry if this question has already been asked but I could not find a solution anywhere.
I'd import this function in Angular's world as a service:
/* global nonAngularFunction:false */
angular.module('aModuleName', [])
.value('nonAngularFunction', nonAngularFunction);
Then you can use injection and import the function to your scope:
// ... in your directive or controller
angular.module('maybeAnotherModule', ['aModuleName'])
.controller('YourController', ['$scope', 'nonAngularFunction',
function ($scope, nonAngularFunction) {
$scope.nonAngularFunction = nonAngularFunction;
}]);
Should work (I haven't tested it).
Edit: Alternative when there are dozens of external functions
eval is not a practical solution, since it doesn't have access to your scopes.
You could either import lots of your functions into Angular, e.g.
angular.module('aModuleName', [])
.value('myHorribleLib', {
'oneFunction': oneFunction,
'Constructor1': Constructor1,
...
});
Then you have to make them available by $scope.legacyLib = myHorribleLib and use it with a prefix: ng-click="legacyLib.oneFunction('hello')". A bit verbose, but you keep your eyes of what is legacy.
Or you just extend $rootScope (or a top-level scope) with the methods:
angular.module('aModuleName', [])
.run(['$rootScope', '$window', function ($rootScope, $window) {
'oneFunction Constructor1 anotherFunction ...'
.split(' ').forEach(function (f) {
$rootScope[f] = $window[f];
});
}]);
I'm not saying this is a super-clean solution, but this way you could expose the functions of your legacy library to Angular scope. They should now work in normal ng-click and the like and have access to your scope.

functionality of angular directive before return and compile chain

It is possible to run javascript in a directive before returning anything, as well as in a directive's compile step before returning anything:
angular.module('foo').directive('fooDirective', [function(){
console.debug('before return');
return {
restrict: 'E',
controller: function($scope){
console.debug('controller');
},
compile: function(scope, elem){
console.debug('compile');
return {
pre: function(scope,elem, attr){
console.debug('pre');
},
post: function(scope,elem,attr){
console.debug('post');
}
}
}
}
}]);
<body ng-app="foo">
<foo-directive></foo-directive>
<foo-directive></foo-directive>
</body>
This produces the following console log order:
before return
compile
compile
controller
pre
post
controller
pre
post
I have several questions about this:
1) Why would I ever want to run code before returning the actual directive object? What would be a usecase?
2) Why would I ever want to run code before returning the pre/post link functions? How is the prelink step different from the compile step? What is a use case?
3) Why does compile run twice in succession when there is two items, while everything else runs iteratively in the same order irrelevantly of number of elements?
Plunk: http://plnkr.co/edit/1JPYLcPlMerXlwr0GnND?p=preview
You would want to do this so you can have some private method definitions that only your object can use.
You would want to do this if you were utilizing some service precompile to get the data for the directive, and post compile to use the data and do something with it.
No idea
I think there is some weird bubbling going on.
because you only have two instances of your directive.
This is what I see in the console log:
before return
compile
compile
controller
pre
post
controller
pre
post

Categories

Resources