I'm trying to create directives on the fly, actually I achived that, but seams pretty hacky.
This was my first approach:
function create(myDir) {
angular.module("app").directive(myDir.name, function() {
return {
template:myDir.template
};
});
}
It didn't work because you can't register directives after application started.
based on this post: http://weblogs.thinktecture.com/pawel/2014/07/angularjs-dynamic-directives.html
I found out that I could use compileProvider to do the work, but since compileProvider isn't available outside config block, you need to put it out, so I did:
var provider = {};
angular.module("app",[]);
angular.module('app')
.config(function ($compileProvider) {
//It feels hacky to me too.
angular.copy($compileProvider, provider);
});
....
function create(myDir) {
provider.directive.apply(null, [myDir.name, function () {
return { template: myDir.template } }]);
render(myDir); //This render a new instance of my new directive
}
Surprisingly it worked. But I can't feel like being hacking the compileProvider, because I'm using it not in the way it was suppose to be, I would really like to know if is it possible to use the compileProvider properly after the application has started.
There is a list of dependencies that can be injected to config blocks (these are built-in $provide, $injector and all service providers) and a list of dependencies that can be injected to everywhere else (service instances and good old $injector). As you can see all that constant does is adding the dependency to both lists.
A common recipe for using providers outside config is
app.config(function ($provide, $compileProvider) {
$provide.constant('$compileProvider', $compileProvider);
});
Related
I am building a dialog service. A dialog can have a controller, very similar to $mdDialog, like this:
myDialogService.show({
templateUrl: `<div ng-click="$ctrl.log()">Hello dialog</div>`,
controller: function() {
this.log = function() {
console.log("logged from myDialogController");
}
}
});
which works great. I invoke the controller that way:
locals.$scope = scope;
const invokeController = $controller(options.controller, locals, true);
const controller = invokeController();
if (options.controllerAs) {
scope[options.controllerAs] = controller;
} else {
const controllerAs = "$ctrl";
scope[controllerAs] = controller;
}
In angular-mock is the $componentController service, which can invoke component controllers. With my code, I only can invoke registered controllers, or given controller functions. This is not very helpful, as I only have components registered, not single controllers.
My question
Is it possible/recommended to use the $componentController in production? Or is there any AngularJS build in variant I have overseen?
$componentController belongs to ngMock module because it is useful for testing but is considered a hack in production. Since ngMock is big enough and isn't supposed to be available in production, it should be pasted in order to become available.
The proper way to solve this is to either have registered controllers that are reused as component controllers or import/export controller functions/classes with JS modules.
Since MdDialogController belongs to third-party module, isn't registered or exported and is small, it can be just pasted.
Why am I forced to declare the provider before the config function that will uses it?
In others words, this code works:
angular.module('app')
.provider('testService', function() {
// ...
})
.config(function(testServiceProvider) {
// ...
});
but not this one (got an [$injector:unpr] Unknown provider: testServiceProvider):
angular.module('app')
.config(function(testServiceProvider) {
// ...
})
.provider('testService', function() {
// ...
});
(in my real code, these 2 blocks are defined in separate files, and thus the order of loading these files really matters)
My understanding was that when I call module('app').config(...) and module('app').provider(...), the code is not executed immediately, but when the Angular application is bootstraped, and thus, that's the role of Angular to execute the differents code in correct order (i.e. the provider code and then the config code).
Thanks
ps1: I already saw this question, which is quite the same but the answer was more about suggestion or guessing...
ps2: I'm still working with Angular 1.2, maybe things changed with Angular 1.3 - 1.4?
I've tested on several Angular versions, and it appears to be a "bug" in 1.2.x version. Starting from 1.3.0 version, the order of the definition of the provider and config is not important.
in Angular 1.2.29: http://jsfiddle.net/c4Lg3da4/ --> KO
in Angular 1.3.0: http://jsfiddle.net/c4Lg3da4/1/ --> OK
in Angular 1.4.8: http://jsfiddle.net/40arcz2p/ --> OK
test code:
angular.module('myApp',[])
.provider('testService', function() {
console.log('.. service');
this.$get = function () {
return {};
};
})
.config(function(testServiceProvider) {
console.log('.. config');
});
To keep my code clean I'm in the habit of defining url's as constants in my AngularJs applications, looking like:
var myApp = angular.module('myApp', ['myModule']);
myApp.constant('URL', {
google: 'http://www.google.com',
facebook: 'http://www.facebook.com'
});
Now, that works fine but I get into problems when I also define (and use) this constant in my module:
var myModule= angular.module('myModule', []);
myModule.constant('URL', {
twitter: 'http://www.twitter.com'
});
Because, now when I want to use the URL.twitter constant in a directive (inside my module) it is no longer available because the definition of the URL constant in 'myApp' has overriden the value. For example when I create a directive:
myModule.directive('myDirective', function(){
return {
template: '<div></div>',
controller: function(URL){
console.log(URL.twitter); // logs undefined when used within 'myApp'
}
};
});
See a live example: http://plnkr.co/edit/xiOGVWfJ4GupSvkTs0Ic?p=preview
Think of 'myModule' being a third-party module which I include in my project. If I accidentally use the same names for my constants, the whole thing is broken.
How to overcome this use case without having to use different names for the same purpose?
I think the question boils down to:
A third party module (that I didn't write) defines 'URL' as a constant.
I include this module as dependency in my app, and define URL too (without knowing that URL was already defined):
var app = angular.module('myApp', ['thirdPartyModule']);
app.constant('URL', { twitter: 'www.twitter.com'});
All of a sudden, the app breaks and stops working.
How does one avoid this pitfall?
Answer:
You can use angular's injector to determine if the constant is already defined:
angular.module('myApp', ['thirdPartyModule']);
var injector = angular.injector(),
url = injector.has('URL') ? injector.get('URL') : {};
The above code will guarantee that an object will be returned - either the URL defined in a third party module, or an empty object literal, if it is not defined.
Then, you can extend the 'URL' as follows:
angular.extend(url, { twitter: 'www.twitter.com' });
Word of Caution:
This will work if URL was previously undefined or defined in a third-party module as an object literal. This will not work if URL was previously defined as a primitive (ie. string).
Unfortunately, I can't think of a way to guard against this scenario in a clean way. I would recommend not overriding previously defined constants at all, and relying on naming convention to make your injectables unique. For example, for your constants, services, providers and factories, use a unique prefix that reduces the likelihood of naming collisions:
var app = angular.module('tgApp', ['thirdPartyModule']);
app.constant('tgURL', { ... });
I Realize this is an old question but I didn't see this answer provided. I just spent a few hours banging my head against the desk on the very same issue the OP described. I have quite a few jQuery widgets I am converting to angular. These are essentially third-party modules that will be used across several sites. I solved this issue by using a provider for each module.
var myModule = angular.module('myModule', []);
myModule.provider('myModuleProvider', function() {
this.$get = [function() {
return {
twitter: 'http://www.twitter.com',
};
}]
})
myModule.directive('myDirective', function() {
return {
template: '<div></div>',
controller: function(myModuleProvider) {
var URL = myModuleProvider;
console.log(URL.twitter);
}
}
})
My example does make full use of providers but the nice thing is they can be configurable. So if I have "options" I want to expose they can be configured via the angular.config() function on the module that myModule is being injected in to.
You're creating an angular module named myApp, which depends on the module myModule.
Then you add some costants to the module myApp
var myApp = angular.module('myApp', ['myModule']);
myApp.constant('URL', {
google: 'http://www.google.com',
facebook: 'http://www.facebook.com'
});
Then you are creating myModule and adding the twitter constant to it. That is:
- google and facebook are bound to myApp
- twitter is bound to myModule
Then, you're injecting URL into a directive, trying to recover the twitter URL.
This heavily depends on what is before the myModule.directive line.
Using: var myModule = angular.module('myModule') you should find the twitter constant
Using: var myModule = angular.module('myApp') you should find the google and facebook constant
I usually use a clearer (in my opinion) approach:
var myApp = angular.module('myApp')
myApp.factory('appConstants', function() {
return {
facebook: "www.facebook.com",
google: "www.google.com",
twitter: "www.twitter.com"
}
})
And then you just inject it whenever you need it and use it like: appConstants.facebook
What do you think about it?
Is it possible to work with Restangular with 2 different APIs?
I would like to have setBaseUrl() for both.
just create two or more Restangular service and config them as you want and inject your module which one you want to use...
UPDATE
this code from restangular github page
// Global configuration
app.config(function(RestangularProvider) {
RestangularProvider.setBaseUrl('http://www.global.com');
RestangularProvider.setRequestSuffix('.json');
});
//First Restangular Service
app.factory('FirstRestangular', function(Restangular) {
return Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.setBaseUrl('http://www.first.com');
});
});
//Second Restangular Service
app.factory('SecondRestangular', function(Restangular) {
return Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.setBaseUrl('http://www.second.com');
});
});
instead of global config (although you can still set global config for shared properties) create Restangular factories like these and inject them your controller...
// Let's use them from a controller
app.controller('MainCtrl', function(Restangular, FirstRestangular, SecondRestangular) {
// GET to http://www.google.com/users.json
// Uses global configuration
Restangular.all('users').getList()
// GET to http://www.first.com/users.json
// Uses First configuration which is based on Global one, therefore .json is added.
FirstRestangular.all('users').getList()
// GET to http://www.second.com/users.json
// Uses Second configuration which is based on Global one, therefore .json is added.
SecondRestangular.all('users').getList()
});
#wickY26 has the right answer, but I wanted to add something important.
If you are going to minify your code, you need to use inline annotation like so:
app.factory('FirstRestangular', [ 'Restangular', function(Restangular) {
return Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.setBaseUrl('http://www.first.com');
});
}]);
Note the square brackets.
EDIT: I have managed to get my unit tests running - I moved the code containing the services to a different file and a different module, made this new module a requirement for fooBar module, and then before each "it" block is called, introduced the code beforeEach(module(<new_service_module_name)). However, my application still won't run. No errors in console either. This is the only issue that remains - that when I use global scope for controllers definition, the application works, but when I use angular.module.controller - it does not.
I have a file app.js that contains the following:
'use strict';
var app = angular.module('fooBar', []);
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/', {
templateUrl: 'partials/form-view.html',
controller: FormViewCtrl
}).
when('/resultDisplay', {
templateUrl: 'partials/table-view.html',
controller: TableViewCtrl
}).
otherwise({redirectTo: '/'});
}]);
app.service('searchResults', function() {
var results = {};
return {
getResults: function() {
return results;
},
setResults: function(resultData) {
results = resultData;
}
};
});
I have another file controllers.js that contains the following:
'use strict';
var app = angular.module('fooBar', []);
app.controller('FormViewCtrl', ['$scope', '$location', '$http', 'searchResults',
function ($scope, $location, $http, searchResults) {
//Controller code
}]);
searchResults is a service that I created that simply has getter and setter methods. The controller above uses the setter method, hence the service is injected into it.
As a result, my application just does not run! If I change the controller code to be global like this:
function ($scope, $location, $http, searchResults) {
//Controller code
}
then the application works!
Also, if I use the global scope, then the following unit test case works:
'use strict';
/*jasmine specs for controllers go here*/
describe('Foo Bar', function() {
describe('FormViewCtrl', function() {
var scope, ctrl;
beforeEach(module('fooBar'));
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('FormViewCtrl', {$scope: scope});
}));
}
//"it" blocks
}
If I revert to the module scope, I get the error -
Error: Unknown provider: searchResultsProvider <- searchResults
Thus, by using global scope my application and unit tests run but by using app.controller, they seem to break.
Another point that I have noted is that if I include the controller code in app.js instead of controllers.js, then the application and unit tests start working again. But I cannot include them in the same file - how do I get this to run in the angular scope without breaking the application and unit tests?
You don't need to go that route. You can use the modular approach, but the issue is with your second parameter.
In your app.js you have this:
var app = angular.module('fooBar', []);
Then in your controller, you have this:
var app = angular.module('fooBar', []);
What you're doing there is defining the module twice. If you're simply trying to attach to the app module, you cannot pass in the second parameter (the empty array: []), as this creates a brand new module, overwriting the first.
Here is how I do it (based on this article for architecting large AngularJS apps.
app.js:
angular.module('fooBar',['fooBar.controllers', 'fooBar.services']);
angular.module('fooBar.controllers',[]);
angular.module('fooBar.services', []);
...etc
controllers.js
angular.module('foobar.controllers') // notice the lack of second parameter
.controller('FormViewCtrl', function($scope) {
//controller stuffs
});
Or, for very large projects, the recommendation is NOT to group your top-level modules by type (directives, filters, services, controllers), but instead by features (including all of your partials... the reason for this is total modularity - you can create a new module, with the same name, new partials & code, drop it in to your project as a replacement, and it will simiply work), e.g.
app.js
angular.module('fooBar',['fooBar.formView', 'fooBar.otherView']);
angular.module('fooBar.formView',[]);
angular.module('fooBar.otherView', []);
...etc
and then in a formView folder hanging off web root, you THEN separate out your files based on type, such as:
formView.directives
formView.controllers
formView.services
formView.filters
And then, in each of those files, you open with:
angular.module('formView')
.controller('formViewCtrl', function($scope) {
angular.module('formView')
.factory('Service', function() {
etc etc
HTH
Ok - I finally figured it out. Basically, if you wish to use the module scope and not the global scope, then we need to do the following (if you have a setup like app.js and controllers.js):
In app.js, define the module scope:
var myApp = angular.module(<module_name>, [<dependencies>]);
In controllers.js, do not define myApp again - instead, use it directly like:
myApp.controller(..);
That did the trick - my application and unit tests are now working correctly!
It is best practice to have only one global variable, your app and attach all the needed module functionality to that so your app is initiated with
var app = angular.module('app',[ /* Dependencies */ ]);
in your controller.js you have initiated it again into a new variable, losing all the services and config you had attached to it before, only initiate your app variable once, doing it again is making you lose the service you attached to it
and then to add a service (Factory version)
app.factory('NewLogic',[ /* Dependencies */ , function( /* Dependencies */ ) {
return {
function1: function(){
/* function1 code */
}
}
}]);
for a controller
app.controller('NewController',[ '$scope' /* Dependencies */ , function( $scope /* Dependencies */ ) {
$scope.function1 = function(){
/* function1 code */
};
}
}]);
and for directives and config is similar too where you create your one app module and attach all the needed controllers, directives and services to it but all contained within the parent app module variable.
I have read time and time again that for javascript it is best practice to only ever have one global variable so angularjs architecture really fills that requirement nicely,
Oh and the array wrapper for dependencies is not actually needed but will create a mess of global variables and break app completely if you want to minify your JS so good idea to always stick to the best practice and not do work arounds to get thing to work
In my case, I've defined a new provider, say, xyz
angular.module('test')
.provider('xyz', function () {
....
});
When you were to config the above provider, you've inject it with 'Provider' string appended.
Ex:
angular.module('App', ['test'])
.config(function (xyzProvider) {
// do something with xyzProvider....
});
If you inject the above provider without the 'Provider' string, you'll get the similar error in OP.