I have the following code as my controller...
module scrumtool.app.Controllers {
export class ConfigController implements Directives.IConfigScope{
static $inject = [
"$scope"
];
public message: string = "Testing";
constructor() { }
clickMe = () => {
return this.message;
}
}
}
The IConfigScope interface is defined as such under the directives module for the time being...
export interface IConfigScope {
message: string;
clickMe(): string;
}
The directive is then like so...
module scrumtool.app.Directives {
export interface IConfigScope {
message: string;
clickMe(): string;
}
export class Cog implements ng.IDirective {
restrict: string = "A";
controller = Controllers.ConfigController;
constructor($scope: Directives.IConfigScope) {
}
link = function($scope: Directives.IConfigScope, element: ng.IAugmentedJQuery,
attr: ng.IAttributes, ctrl: Controllers.ConfigController) {
element.on("click", (e) => {
alert(ctrl.clickMe());
});
}
static factory(): ng.IDirectiveFactory {
var directive: ng.IDirectiveFactory =
($scope: Directives.IConfigScope) => new Cog($scope);
directive.$inject = [];
return directive;
}
}
}
Essentially I was able to make this work. What I don't understand is that through the $scope variable I can access the message variable and have it alert the message "Testing".
What I can't quite understand is why I can't simply say:
alert($scope.clickMe());
and have it return the message. When I attempt the above I get undefined.
Can someone please explain this to me?
Dependencies defined inside of the:
static $inject = [
"$scope"
];
must be somehow delivered, injected. The way with angular is via constructor. So we need this:
// there is no param in constructor, to be used
// constructor() { }
// but we need one - $scope
constructor(protected $scope) { }
we also should provide a type of passed $scope
constructor(protected $scope:IConfigScope) { }
which will mean, that the passed $scope would have these messages and clickMe implemented.
Other words - controller won't implement IConfigScope, it will be provided with that
But if we want to let our controller do that job, we should use controllerAs syntax
export class Cog implements ng.IDirective {
restrict: string = "A";
controller = Controllers.ConfigController;
controllerAs = "Ctrl";
...
and later anywhere inside of view template we can use
{{Ctrl.messages}}
...
ng-click="Ctrl.clickMe()"
Related
I am trying to reuse some working code from AngularJS 1 services written in plain JavaScript in an Angular 2 environment.
The services look, for instance, like the following example:
(function () {
angular.module('myapp.mysubmodule').factory('myappMysubmoduleNormalService', ['someOtherService',
function (someOtherService) {
var internalState = {
someNumber: 0
};
var service = {};
service.someFunction = function () {
internalState.someNumber++;
};
someOtherService.getValues().forEach(function (v) {
service[v] = function () {
console.log(v + internalState.someNumber);
};
});
return service;
}]);
})();
I have found various examples of how to convert AngularJS 1 services to Angular 2 services (such as this one), all of which have in common that instead of the service factory, I have to export a class.
This should look roughly as follows:
import { Injectable } from '#angular/core';
#Injectable()
export class myappMysubmoduleNormalService {
someFunction: function () {
// ?
}
}
Now, the question is how to incorporate the internal state and the dynamically added properties.
Is it really the way to go to do all that in the constructor, i.e. fill each instance of the class upon initialization, like so:
import { Injectable } from '#angular/core';
#Injectable()
export class myappMysubmoduleNormalService {
constructor() {
var internalState = {
someNumber: 0
};
var service = {};
this.someFunction = function () {
internalState.someNumber++;
};
this.getValues().forEach(function (v) {
service[v] = function () {
console.log(v + internalState.someNumber);
};
});
}
}
Or is there any other way? The above probably works (save for the missing dependency injection, that I still have to find out about how to do in Angular 2). However, i am wondering whether it is a good way because I have not come across any samples that did much of a member initialization in their constructor.
You can use just the same approach in Angular with factory providers:
export function someServiceFactory(someOtherService) {
var internalState = {
someNumber: 0
};
var service = {};
service.someFunction = function () {
internalState.someNumber++;
};
someOtherService.getValues().forEach(function (v) {
service[v] = function () {
console.log(v + internalState.someNumber);
};
});
return service;
};
#NgModule({
providers: [
{
token: 'myappMysubmoduleNormalService',
useFactory: someServiceFactory,
deps: ['someOtherService']
}
]
})
Both in Angular and AngularJS the value returned by the factory function is cached.
A service is just a class that you can inject into components. It will create a singleton in the scope where it is named a provider.
import { Injectable. OnInit } from '#angular/core';
#Injectable()
export class myappMysubmoduleNormalService implements OnInit {
internalState: number;
constructor() {}
ngOnInit(){
this.internalState = 0;
}
incrementSomeNumber() {
this.internalState++;
console.log(this.internalState};
}
}
I realize this is not logging a distinct internal state for multiple functions but you get the idea.
Register this as a provider in the app.module (if you want a singleton for app scope)
When you import into a component and then inject in the constructor
constructor(private _myservice : myappMysubmoduleNormalService) {}
you can now use the _myservice methods
myNumber : number = 0 ;
componentFunction() {
_myservice.incrementSomeNumber();
this.myNumber = _myservice.internalState;
}
Of course you could have the service method return the incremented number (or data or a promise of data)
This is rough but gives you the idea. Very little code belongs in the constructor. A service should be injected. what is shown in component constructor is shorthand to a get private variable referencing the service. The service will be a singleton for the scope in which it is provided. (can be overridden within the scope but that seems a code smell to me)
To pass back a value :
In service
incrementSomeNumber(): number {
this._internalState++;
console.log(this._internalState};
return this._internalState;
}
In component:
mynumber: number;
componentFunction() {
this.mynumber = _myservice.incrementSomeNumber();
}
Not sure what you're trying to accomplish but just wanted to show example of getting information from services. Most common use of services for me is a dataservice, so the code would be a little more complex as it is asynch.
I have an model class with a default constructor and a constructor with parameters. I also have a service with some methods I would like to use in the model class. I have the include for the service, but when I attempt to inject the service with the constructor for the service, I get
"Multiple constructor implementations are not allowed."
Here is an example of what I have attempted:
import { MyService } from '../utilities/utils.service';
export class MyData {
private __var1: string;
get var1(): string { return this.__var1; }
set var1(val: string) { this.__var1 = val; }
private __var2: string;
get var2(): string { return this.__var2; }
set var2(val: string) { this.__var2 = val; }
// etc.
constructor()
constructor(
var1: string,
var2?: string
) {
this.__var1 = var1;
this.__var2 = var2;
}
constructor(private myService: MyService) { }; // causes error.
}
I thought this was the correct approach, obviously not.
I am trying to implement a Role-Based-Access-Control system where the allowed resources will be loaded from server after login. I could manage to check it using raw JavaScript code.
angular.module('app').directive('accessControl',
[
'AuthService', function (authService) {
return {
restrict: 'A',
scope: "=",
link: function (scope, element, attrs) {
scope.canShow = function(resource) {
var allowedResources = authService.accountInfo.resources;
return allowedResources.indexOf(resource) !== -1;
}
}
}
}
]);
But since my whole application is in TypeScript, I have been trying to make the directive in pure TypeScript, but unfortunately I am unable to do so. Here is my TS code.
export class AccessControl implements ng.IDirective {
public authService: App.AuthService;
public link: (scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes) => void;
constructor(authService: App.AuthService) {
this.authService = authService;
console.log('authservice: ', authService);
AccessControl.prototype.link = (scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes) => {
scope["canShow"] = function (resource: string) {
// some logic
console.log('can show' + resource);
return true;
};
};
}
public static factory(): ng.IDirectiveFactory {
var directive = (authService: App.AuthService) => {
return new AccessControl(authService);
};
directive['$inject'] = ['AuthService'];
return directive;
}
restrict = "A";
scope: "=";
}
angular.module('app').directive('accessControl', AccessControl.factory());
The link function never gets called.
Any help or pointer will be highly appreciated.
We have always declared the link as just a public function on the class. You don't need a public scope variable on your directive class unless you are using isolate scope (in which case it would be an object with each scope variable or method specifically passed). Also, you can set $inject directly on the directive without the bracket property notation you're using. Here's how we have created directives with TypeScript:
namespace app.directives {
export class AccessControl implements ng.IDirective {
public restrict = "A";
constructor(private authService: App.AuthService) {
console.log("authservice: ", this.authService);
}
public link(scope: ng.IScope,
element: ng.IAugmentedJQuery,
attrs: ng.IAttributes) {
console.log("in link function of directive", scope);
}
public static factory(): ng.IDirectiveFactory {
var directive = (authService: App.AuthService) => {
return new AccessControl(authService);
};
directive.$inject = ["AuthService"];
return directive;
}
}
angular.module("app")
.directive("accessControl", AccessControl.factory());
}
I am trying to create directive in Typescript which will keep watch on pending $resource requests. I want only one directive as an attribute which will be used with div in index.html to show loading progress. Below is my code for directive.
module app.common.directives {
interface IProgressbarScope extends ng.IScope {
value: number;
isLoading: any;
showEl: any;
}
class Progressbar implements ng.IDirective {
static $inject = ['$http'];
static instance(): ng.IDirective {
return new Progressbar;
}
//transclude = true;
restrict = 'A';
replace = true;
link = function (scope: IProgressbarScope, elements: ng.IAugmentedJQuery, attrs: ng.IAttributes, $http: ng.IHttpService) {
debugger;
scope.isLoading = function () {
return $http.pendingRequests.length > 0;
};
scope.$watch(scope.isLoading, function (v) {
debugger
if (v) {
elements.addClass("hidediv")
} else {
elements.removeClass("hidediv");
}
});
}
}
angular.module('app')
.directive('progressbar', Progressbar.instance);
}
in Index.html, it is used as below:
<div progressbar id="myProcess" name="myProcess">
// loading image
</div>
But in directive, $http is always undefined. Note that I am not using $http directly. I a using $resource service for making server side api requests.
The reason $http undefined is, you are trying to get $http dependency from link function of directive. Basically 4th parameter of link function stands for require controller.
You should Ideally get that injected dependency instance from Progressbar constructor function.
class Progressbar implements ng.IDirective {
_http: ng.IHttpService; //defined _http variable
static $inject = ['$http'];
//asking for dependency here
static instance($http: ng.IHttpService): ng.IDirective {
this._http = $http; //get `$http` object assigned to `_http`
return new Progressbar;
}
//transclude = true;
restrict = 'A';
replace = true;
//removed dependency from here
link = function (scope: IProgressbarScope, elements: ng.IAugmentedJQuery, attrs: ng.IAttributes) {
//use arrow function here
scope.isLoading = ()=> {
return this._http.pendingRequests.length > 0;
};
//use arrow function here
scope.$watch(scope.isLoading, (v)=> {
if (v) {
elements.addClass("hidediv")
} else {
elements.removeClass("hidediv");
}
});
}
}
define $scope.isLoading inside directiveController and make $http call from service layer.
basic controller.ts
export class sampleController {
// inject service here
constructor() {
}
public isLoading() {
callServiceFunction();
}
}
sampleController.$inject['service'];
Import this controller inside custom directive.
SampleService.ts
export class sampleService {
constructor() {
}
}
sampleService.$inject = ['$http'];
Register this service inside app module.
For more info refer sample Importing and exporting example and large scale app architecture
How do you initialize isolate scope for an AngularJS directive when using a strongly-typed interface? If you want to use a model interface as well as the "#" "=" and "&" binding symbols, it seems you confuse the compiler because you can't assign a string variable to properties with incompatible types.
For instance:
module widgets {
'use strict';
export interface IWidget extends ng.IScope {
removed: boolean;
onChanged: ()=>void;
description: string;
toggle:()=>void;
}
export class Widget implements ng.IDirective {
public templateUrl = 'app/widgets/widget.html';
public restrict = 'E';
public scope: IWidget = {
removed: "#",
onChanged: "&",
description: "="
};
public link: (scope: IWidget, element: ng.IAugmentedJQuery,
attrs: ng.IAttributes) => void;
static factory(): any {
/* #ngInject */
var directive = () => {
return new Widget();
};
return directive;
}
constructor() {
this.link = (scope: IWidget, element: ng.IAugmentedJQuery,
attrs: ng.IAttributes) => {
var init = () => {
scope.toggle = this._toggle.bind(this);
scope.$on('$destroy', this.destruct);
scope.$apply();
};
element.ready(init);
};
}
private _toggle() {
// updated: this throws the type error
this.scope.removed = !this.scope.removed;
}
private destruct() {
}
}
}
Given the above code, notice that onChanged will produce compiler errors, because you can't assign a string "&" to a function.
You may get the following error:
2349 Cannot invoke an expression whose type lacks a call signature.
Or this error on the removed property:
2322 Type 'boolean' is not assignable to type 'string'.
In fact, even if you use any for the model the compiler still wont let you change the underlying type of a property after it is defined.
Is there any way to deal with this?
You may get the following error: 2349 Cannot invoke an expression whose type lacks a call signature.
The main cause is that scope members need to be stringly linked (and one of the reason I am no longer an angular advocate).
The member onChanged should be callable just fine e.g. :
public scope: IWidget = {
removed: "#",
onChanged: "&",
description: "="
};
public link: (scope: IWidget, element: ng.IAugmentedJQuery, attrs: ng.IAttributes) => void {
scope.onChanged(); // WILL WORK FINE
}
You are probably misusing the annotation IWidget in your code (not provided with the question).
The issue here is the directive definition.
It expects scope to be kind of IDictionary<string, string>, but type any would work as well:
export class Widget implements ng.IDirective {
public templateUrl = 'app/widgets/widget.html';
public restrict = 'E';
//public scope: IWidget = {
public scope: any = {
removed: "#",
onChanged: "&",
description: "="
};
The scope here, in the directive definition, is about describing how to handle html attributes (passed as a value, or as function ...) . It does not have type IWidget. That type IWidget will come into play when the scope instance is passed to controller or link
As said above, the IDictionary will work as well:
public scope: {[key: string] : string} = {
//public scope: any = {
"removed": "#",
"onChanged": "&",
"description": "="
};
But the any : {...} and simplified notation will do the same.