When I display my controller (class) in console, I see that all dependencies are visible and all the dependencies of my dependencies are visible by the scope ...
But I know that everthing should be private if I use functions.
My question is : What is the best way to create controllers in angularjs (1.6) and What is the difference between classes (ES6) and functions.
You can try the following pattern to keep those dependencies private and not have to assign them all to controller object.
function yourControllerFactory(yourDep1, yourDep2, ...) {
class YourController {
constructor() {
// no longer have to assign every dependency to this object
}
...
doSomething() {
yourDep1.doSomethingWithDep();
}
}
return new YourController();
}
module('your-module').controller('yourController', yourControllerFactory);
This way, instances of YourController will NOT have access to your injected dependencies. This way is similar to how controllers were defined in the past.
Related
I'm using Angular 1.5+ with Typescript, preparing my code to be compatible with Angular 2. I've got a situation where many of my components need to use an application-wide repository location for the views that are mapped to their templateUrl properties, but sometimes a view needs a specific, local implementation.
So, normally the views are hosted on a fast-served CDN, they're re-used between multiple sites that all belong to the same general code base, api, etc. This is done to prevent duplicating them and to centralize what doesn't need to be repeated.
Rarely, I'll need to override this behavior and use a more specific, fine-tuned view. So my approach to this was to add a binding to the components called viewLocation, with the intent to use it like this;
<component-name></component-name> is the default. In this situation, the default CDN path is used.
<component-name view-location="local"></component-name> is a unique situation. If this happens, the templateUrl should be able to respond to that and switch to a path relative to the specific application and not from the CDN.
I thought it would be pretty simple until I realized that I wouldn't have access to the binding properties within the actual constructor or templateUrl function, as is demonstrated here;
export class SidebarComponent extends Component {
constructor() {
super();
this.bindings = { viewLocation: '=' };
// other properties
this.templateUrl = ($someService: IServiceInterface): string => {
// help! I don't have access to the viewLocation property!
}
}
}
So is there anything else I can do to get access to that property?
This is not done in TS, but the AngularJS 1.5 component generally provides the $element and $attrs to the $injector when you are using an injectable template.
This is an example in AngularJS using Javascript where the template URL is picked based on an attribute set on the component:
angular.module('example', []);
angular.module('example').component('myComponent', {
templateUrl: function($element, $attrs, $log) {
$log.info('determining template to be used');
if($attrs.useTemplate) {
return $attrs.useTemplate;
}
return 'default.html';
}
});
Template snippet:
<my-component use-template="hui.html"></my-component>
<my-component use-template="bu.html"></my-component>
<p></p>
<my-component></my-component>
Working example:
template injection in an angular 1.5 component
I need to modify a constant service. This service has no tests attached, but I wanted to, at least partially, cover the file and my modifications with unit tests that I will write with Jasmine.
The service looks like:
(function () {
angular
.module('app')
.constant('myService', myService);
function myService() {
dependencyOne: dependencyOneImpl,
someFunction: someFunctionImpl
...
}
dependencyOneImpl.$inject = ['someDependency'];
function dependencyOneImpl(someDependency) {
...
}
someFunctionImpl.$inject = ['dependencyOne', 'dependencyTwo'];
function someFunctionImpl(dependencyOne, dependencyTwo) {
...
}
})();
It is used in the resolution process of routes (we use ui-router). When a particular route is activated, a function of the service is invoked:
someRoute = {
url: '...',
name: '...'
views: { ... },
resolve: { resolver.someFunction }
}
There are no dependencies required in the construction of the service, and I'm trying to test one of the public functions that the service exposes.
Typically I would write the test like:
prepareSomeSetup();
theService.someFunction();
assertSomething();
I could manually resolve the dependencies required by the function, but I would like to know if I can get the same behavior that ui-router is getting when resolving the function. The dependencies seem to be resolved and the function is executed.
It crossed my mind that I probably needed to inject $injector service in the test file and manually call invoke somehow to resolve the function, but it does not seem to work.
How can I call the function with the two dependencies resolved?
When you define an injectable function you have to call it using $injector.invoke(fn, scope, otherArgs);
Where
fn the injectable function, or array for inline injection
scope : the "this" of the function
otherArgs : others Arguments that are not defined as service,... in angular that you inject.
I'm building an application with angular and recently switched to TypeScript. The core of my application consists of multiple actual classes that interact with eachother (so not just global services). The way I did this is as follows:
class Person {
constructor(private $http, public name: string) {
// ... use this.$http for some task
}
}
class PersonFactory {
static $inject = ['$http'];
constructor(private $http) {}
create(name: string) {
return new Person(this.$http, name);
}
}
angular.module('app').service('personFactory', PersonFactory);
So if some other class needs to create a Person object, it needs to get injected with PersonFactory and call its create method. This works ok, even though it requires some boilerplate.
My main problem arises when I want to subclass Person:
class Student extends Person {
constructor(private $http) {
super(this.$http, 'Some actual name')
}
}
I could follow the same pattern as above, but then I need to, again, create a StudentFactory that passes the required services like $http to the class constructor, which in turn passes it to the constructor of the super method. If I now had multiple such extending classes and would change the base class Person, I had to change every extending class and their factories. Is there a better way to approach this problem? Might it be better to just remove all dependencies on angular services in my core application and just use angular for the UI code?
Solution
As suggested in the answers, I added the $injector service to the window, like this:
module('app').run(['$injector', function($injector) {
window.$injector = $injector;
}]);
The service can now be used in any class to get other services like $http.
This didn't work for unit tests though, so I had to put the injector on the global scope again:
// MyClass.test.js
var $injector;
describe('My class', function() {
beforeEach(module('app'));
beforeEach(inject(function(_$injector_) {
$injector = _$injector_;
}));
...
});
I had to change every extending class and their factories. Is there a better way to approach this problem
Just inject $injector and then the base class can get what it wants using $injector.get().
or even more crazy ... put $injector (its a singleton) on the global scope / or some module ... and just use that in your classes to get angular specific stuff.
I'm working with angular, jspm, and es6. I'm working with a base class to inject dependencies onto the constructor and automatically register themselves on 'this'.
This is actually a pattern that exists in React when you extend the base component class. I found this guy's little shortcut method here: http://www.newmediacampaigns.com/blog/refactoring-react-components-to-es6-classes
I am looking for a way to do this with Angular, using es6 Classes to bind the injected dependencies to the constructor's "this".
class baseClass {
constructor(...injections) {
this._bind(injections)
}
_bind(injections) {
injections.forEach( (injection) => {
this[injection.name] = injection;
});
}
}
class DiClass extends baseClass {
constructor($q, SomeAngularFactory) {
super($q, SomeAngularFactory);
}
}
This obviously doesn't work (injection.name is not a thing, i know)... but it almost does. My question is how do i get the "name" of the injected function or object. In this example, the _bind function just gives you the raw object or function... i don't know how to get "$q" or "SomeAngularFactory" as a string.
You can kind of get that by using "Object.getOwnPropertyNames(...injections)", but not inside the _bind function.
Thanks for any feedback or ideas you have.
You could do something like this:
class baseClass {
constructor(injections) {
Object.assign(this, injections);
}
}
class DiClass extends baseClass {
constructor($q, SomeAngularFactory) {
super({ $q, SomeAngularFactory });
}
}
Obs.: Like classes, Object.assign is an ES2015 feature and it merges the second (parameter) object into the first one.
In AngularJS this injectable arguments works only on development mode. If you go to production and minify your scripts, all injectables should be saved into special property. You can learn more about it in the angular docs. Here how it works with angular controllers
function MyController($q, SomeAngularFactory) {
}
MyController.$inject = ['$q', 'SomeAngularFactory']
Angular 2.0 will be powered by TypeScript and will use annotations to describe injectable variables. For now in anguar 1.3 you still can add static property $inject
class DiClass extends baseClass {
constructor($q, SomeAngularFactory) {
super($q, SomeAngularFactory);
}
}
DiClass.$inject = ['$q', 'SomeAngularFactory'];
Angular has a built in method, $injector.annotate(fn), which it uses for dependency injection, and which allows you to get arguments of the function you pass to it. You could utilize it to get the names of all constructor() parameters, and then use $injector.get() to retrieve them (which should be fine performance-wise, since they are cached).
Here's a link to answer in another thread, and a direct link to a fiddle with a demo.
In this the repo
In the controller, I'm trying to inject the UserService:
class UserDetailController implements IUserDetailScope {
static $inject = ['app.core.services.UserService']; // static injection
constructor(userService: app.core.services.IUserService) {
But it fails on the browser' console with:
Unknown provider: app.core.services.UserServiceProvider <-
app.core.services.UserService <- UserDetailController
Could you say why?
Try upper casing the "U" in UserService in the constructor. When angular goes to inject, it just does a match on the name of the service, and it matches case.
EDIT
Just realized you're using $inject, I'm leaving the first response up in case some others find it.
You didn't show us the code that registers the service, but that's the next culprit, usually. I'd guess you're registering it as 'UserService', not 'app.core.services.UserService', or else you're doing an App.controller() instead of App.service() registration. Those are the other two major culprits that come to mind.
you are writing the whole namespace to inject a service but there is neat way in which i do
module portal {
var app =angular.module('fooModule',[]);
app.service(services);
app.controller(controllers);
}
module portal.services {
//all your services will go under this namespace
export class fooService{
//service body here
}
}
module portal.controller{
export class UserDetailController implements IUserDetailScope {
static $inject = ['UserService'];
constructor(userService: potal.services.IUserService) {
}
}