i have a problem regarding the angular dependency injection mechanism.
I have written a directive in a standalone module, that does some neat things on a datatable. I'm using TypeScript and my directive looks as follows:
export class MdListControls implements angular.IDirective {
public templateUrl = "/app/templates/mdListControls.html";
public static instance(): any {
return new MdListControls();
};
public restrict = 'E';
public transclude = true;
public replace = true;
public scope = {
grid: '=',
};
public controller = "mdListControlsController"
public controllerAs = "vm";
public link = function (scope: angular.IScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes) {
};
}
My Controller looks something like this:
export class MdListControlsController {
static $inject = ['scope']
constructor($scope: angular.IScope){
//Setup the constructor
}
//Here goes the controller Logic
}
I'm registering the directive and the controller at a new module as follows:
angular.module("base.directives.mdListControls", [])
.controller("mdListControlsController", MdListControlsController)
.directive("mdListControls", function () { return MdListControls.instance() });
However, once I run the app, I get the following error once the directive shall be loaded:
Argument 'mdListControlsController;' is not a function, got undefined
This doesn't quite look like a problem with dependency injection at first. But when I register the controller on the application itself (which gets injected the "base.directives.mdListControls" Module) like so
app.controller("mdListControlsController", MdListControlsController);
everything works fine and the controller gets injected properly into the directive.
Does anyone know what I'm doing wrong here?
Thanks for your help!
In Your MdListControls class try to provide controller class, not controller name.
...
public scope = {
grid: '=',
};
public controller = MdListControlsController
public controllerAs = "vm";
...
Related
I have this constructor function:
function State(url, templateUrl, controller, controllerAs, factory, resolveProp){
this.url = url
this.templateUrl = templateUrl
this.controller = controller
this.controllerAs = controllerAs
}
and in my router callback I have this:
$stateProvider
.state('archetypes', new State('/admin/archetypes', './resources/features/admin/archetypes/index.html', 'archetypes', 'aaVM'))
This works fine. I have another route, however, that contains a resolve to get some back end data.
I thought I might be able to do something like this for my constructor:
function State(url, templateUrl, controller, controllerAs, factory, resolveProp){
this.url = url
this.templateUrl = templateUrl
this.controller = controller
this.controllerAs = controllerAs
if(factory){
this.resolve = {}
this.resolve[resolveProp] = function(factory){
return factory.getAll()
}
}
}
and then instantiate a state in this way:
.state(
'archetypes',
new State(
'/admin/archetypes',
'./resources/features/admin/archetypes/index.html',
'archetypes',
'aaVM',
ArchetypesFactory,
'archetypes'
)
)
But I think the .state method calls the controller in order to process the resolve object, such that when I try the above state instantiation, it errors out because ArchetypesFactory is clearly undefined.
Note that when i write my state in this way:
.state('archetypes', {
url: '/admin/archetypes',
templateUrl: './resources/features/admin/archetypes/index.html',
controller: 'archetypes',
controllerAs: 'aaVM',
resolve: {
archetypes: function(ArchetypesFactory){
return archetypesFactory.getAll()
}
}
})
It works fine.
Is there any way I can abstract the state configuration object with a resolve into a constructor function or ES6 class?
The resolver function is invoked by the AngularJS injector. The injector needs an explicit form of annotation. One way is to use Inline Array Annotation:
function State(url, templateUrl, controller, controllerAs, factory, resolveProp){
this.url = url;
this.templateUrl = templateUrl;
this.controller = controller;
this.controllerAs = controllerAs;
if(factory){
this.resolve = {}
this.resolve[resolveProp] = [factory, function(factory){
return factory.getAll();
}];
};
}
Then specify a string for the factory parameter:
.state(
'archetypes',
new State(
'/admin/archetypes',
'./resources/features/admin/archetypes/index.html',
'archetypes',
'aaVM',
//ArchetypesFactory,
//USE string
'ArchetypesFactory',
'archetypes'
)
)
Inline Array Annotation is the preferred way to annotate application components. When using this type of annotation, take care to keep the annotation array in sync with the parameters in the function declaration.
I have the following problem. When I have a class in ionic with angular I can access angular services in the constructor after injecting them:
export class HomeController {
static $inject = [
'$scope',
'$state'
];
constructor(
public $scope : any,
public $state : any
) {
this.$state.go('test');
this.$scope.changeController = this.changeController;
}
changeController() {
console.log("change controller");
};
}
However when I change it to the change controller function, it doesn't work
export class HomeController {
static $inject = [
'$scope',
'$state'
];
constructor(
public $scope : any,
public $state : any
) {
this.$scope.changeController = this.changeController;
}
changeController() {
console.log("change controller");
this.$state.go('test'); // Fails here
};
}
This is the error:
Error: undefined is not an object (evaluating 'this.$state.go')
What can I do?
On top of that, is it correct add the changeController function to the scope or is there an easier method to make it available to a template?
Thx in advance
In your case the value of this is incorrect which is a very common issue due to misunderstanding of ES6 classes. Try using lambda to have lexical scoping:
changeController = () => {
console.log("change controller");
this.$state.go('test'); // Fails here
};
I'm trying to add an Angular function parameter to a directive written in TypeScript but I must not have the syntax right. Here is an example of my directive:
export class NewUserDirective implements ng.IDirective {
public restrict: string = "E";
public replace: boolean = true;
public templateUrl: string = '/views/_dialogs/newUserDialog.html';
public controller = Directives.NewUserController;
public controllerAs: string = 'Ctrl';
public bindToController: boolean = true;
public scope: any = {
showDialog: '=',
saveInProgress: '=',
onSuccessCallback: '&',
onClosingCallback: '&'
};
}
and here is a snippet of my controller:
export class NewUserController {
static $inject = ["$scope", "$q", "UserServices"];
public showDialog: boolean;
public saveInProgress: boolean;
public onSuccessCallback: () => void;
public onClosingCallback: () => void;
....
public save(): void {
//Flag as saving
this.saveInProgress = true;
//Save the plan
this.UserServices.createUser(this.newUser).then(x => {
//When finished, hide the modal
this.showDialog = false;
//Let parent know new user is created
this.onSuccessCallback();
});
}
}
Everything is working except for the function parameter in the parent controller. I've tried a few different syntaxes in the directive implementation like this:
<new-user-directive on-success-callback="loadData()" ...
and this:
<new-user-directive on-success-callback="loadData" ...
Are you using controllerAs for the outer controller that has the loadData method on it? If so, then you would need to create your directive like this (assuming you named our outer controller 'Ctrl'). Notice the Ctrl. in front of loadData()
<new-user-directive on-success-callback="Ctrl.loadData()" ...
You have controllerAs = 'Ctrl' so on-success-callback="Ctrl.loadData()"
The methods are passed through the $scope and not on the controller.
You should create a constructor:
constructor(private $scope, ....){
}
And call the method from the $scope like so:
this.$scope.onSuccessCallback();
I may just be attempting to combine too many "new-to-me" concepts at once, but I am trying to write a custom Angular directive using a TypeScript class. At the moment, I'm not trying to do anything terribly useful, just a POC.
I have a TypeScript file that looks like this:
module App {
'use strict';
export class appStepper {
public link:(scope:angular.IScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes) => void;
public template:string = '<div>0</div><button>-</button><button>+</button>';
public scope = {};
public restrict:string = 'EA';
constructor(){ }
public static Factory(){
var directive = () =>
{ return new appStepper(); };
return directive;
}
}
angular.module('app').directive('appStepper', App.appStepper.Factory());
}
It compiles to this in JavaScript:
(function(App) {
'use strict';
var appStepper = (function() {
function appStepper() {
this.template = '<div>0</div><button>-</button><button>+</button>';
this.scope = {};
this.restrict = 'EA';
}
appStepper.Factory = function() {
var directive = function() {
return new appStepper();
};
return directive;
};
return appStepper;
})();
App.appStepper = appStepper;
angular.module('app').directive('appStepper', App.appStepper.Factory());
})(App || (App = {}));
My angular module looks like (I don't even know if I need to do this):
angular.module('app',['appStepper'])
And I attempt to use it in my view:
<div app-stepper></div>
And get these errors:
Uncaught Error: [$injector:nomod]
Uncaught Error: [$injector:modulerr]
Why doesn't my app know about my directive?
Though it is not quite the same question, this answer included an example of what I'm attempting to do: How can I define my controller using TypeScript?
I followed the example in the Plnkr it referenced and found success: http://plnkr.co/edit/3XORgParE2v9d0OVg515?p=preview
My final TypeScript directive looks like:
module App {
'use strict';
export class appStepper implements angular.IDirective {
public link:(scope:angular.IScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes) => void;
public template:string = '<div>0</div><button>-</button><button>+</button>';
public scope = {};
public restrict:string = 'EA';
constructor(){ }
}
angular.module('app').directive('appStepper', [() => new App.appStepper()]);
}
I trie to implement a new directive in my app. Directive's code :
module Myapp.NV.Directives {
export interface placeHolderScope extends ng.IScope {
txt: string;
}
/**
* PlaceHolder
*
* #class
* #classdesc This directive is use to simulate placeholder HTML5 attributes
*/
export class PlaceHolder implements IDirective {
static $inject = ['$log','$timeout'];
constructor($log: ng.ILogService, $timeout: ng.ITimeoutService) {
var txt;
var directive: any = {
restrict: "A",
scope: { txt: "#ngPlaceholder" },
link: function (scope: placeHolderScope, elem: ng.IAugmentedJQuery, attrs: ng.IAttributes, $log: ng.ILogService, $timeout: ng.ITimeoutService) {
console.log($log);
console.log($timeout);
}
}
return directive;
}
}
}
Myapp.NV.registerDirective('PlaceHolder', ['$log', '$timeout']);
My probleme is log and timeout are always undefined...
static $inject = ['$log','$timeout'];
Won't work...
The code for registerDirective function :
export function registerDirective(className: string, services = []) {
var directive = className[0].toLowerCase() + className.slice(1);
services.push(() => new Myapp.NV.Directives[className]());
angular.module('Myapp.NV.Directives').directive(directive, services);
}
Thanks for help me :)
As boindill points out in the original answer, dependencies are injected through the TypeScript constructor, not through the link function.
This is my solution, myDirective depends on myService:
export class MyDirective implements ng.IDirective {
static $inject = ["myService"];
constructor(private myService: IMyService) {
}
restrict = "A";
scope = {};
link = (scope: IMyScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes) => {
// directive implementation
// Access myService through 'this.myService'.
};
}
Register the directive in Angular the following way. Here ng.IDirectiveFactory is implemented as an anonymous function:
angular.module("app", []).directive("myDirective", (myService: IMyService) => {
return new MyDirective(myService);
});
Here the Angular dependency injection works because it recognizes the constructor parameter names (myService).
To make sure dependency injection still recognizes the dependencies when the generated JavaScript is minified the static $inject property provides their names in a string array. Just as with plain JavaScript, make sure the constructor parameters and the $inject array members are in the same order.
You cannot inject dependencies in the link function of a directive. The link function has a fixed signature function link(scope, element, attrs) { ... } this is taken from the official angularjs documentation.
What you is to define a controller for your directive and inject the dependencies into the controller. Afterwards just use this controller function.