ES6 AngularJS directive <> service communication - angularjs

I think i have a scope problem with js. Please take a look to my code below.
This is my AngularJS example in es6. I compile the code to es5 with grunt browserify.
If i call my example i got the error:
TypeError: this.gatewayServiceGet is not a function
at ChainsDirective.loadChains [as chainsServiceLoadChains]
I check it and find out that this in loadChains is not the same this than in the constructor.
What can i do?
This is my app.js
'use strict';
import AppController from './appController.js';
import ChainsDirective from './components/chains/chains.directive.js';
import ChainsService from './components/chains/chains.service.js';
import GatewayService from './components/common/gateway/gateway.service.js';
angular
.module('SalesCockpit', ['ui.router', 'ui.grid'])
.config($stateProvider => {
$stateProvider
.state('chains', {
url: '/chains',
templateUrl: 'components/chains/chains.html'
})
.state('chainDetail', {
url: '/chain/{chainId:int}/detail',
templateUrl: 'components/chain-detail/chain-detail.html'
})
;
})
.controller('AppController', AppController)
.service('chainsService', ChainsService)
.service('gatewayService', GatewayService)
.directive('chains', ChainsDirective);
This is my chain directive
export default function ChainsDirective() {
class ChainsDirective {
/*#ngInject*/
constructor(chainsService, $state) {
this.chainsServiceLoadChains = chainsService.loadChains;
this.gridOptions = {
enableColumnMenus: false,
columnDefs: [
{
name: 'id',
visible: false
},
{
name: 'name',
displayName: 'Kette',
cellTemplate: '<div class="ui-grid-cell-contents"><a ng-click="grid.appScope.openDetail(row.entity.id)">{{row.entity.name}}</a></div>'
}
]
};
this.$stateGo = $state.go;
this.fetch();
}
/**
* #param int chainId
*/
openDetail(chainId) {
this.$stateGo('chainDetail', {chainId})
}
fetch() {
return this.chainsServiceLoadChains().then(data => {
this.gridOptions.data = data
})
}
}
return {
restrict: 'E',
template: '<div id="chains" ui-grid="gridOptions" external-scopes="$scope" class="grid"></div>',
controller: ChainsDirective,
controllerAs: 'chains'
}
}
This is my chain servie
export default class ChainsService {
/*#ngInject*/
constructor(gatewayService) {
this.gatewayServiceGet = gatewayService.get;
}
/**
* #returns Promise
*/
loadChains() {
return this.gatewayServiceGet('loadChains');
}
}

FWIW, this has nothing to do with ECMAScript 2015. JavaScript always worked that way.
The value of this depends on how the function is called. So if you call it as
this.chainsServiceLoadChains()
this inside chainsServiceLoadChains will refer to what is before the ., which is this that refers to the ChainsDirective instance.
One solution would be to bind the this value of the function to a specific value:
this.chainsServiceLoadChains = chainsService.loadChains.bind(chainsService);
Now it doesn't matter anymore how the function is called, this will always refer to chainsService.
Learn more about this:
MDN - this
How to access the correct `this` context inside a callback?

Related

Unable to transition to state, getting error that state is not defined

I am currently on the main page after invoking
http://localhost:8199/#/iceberg-ui
I have a state called iceberg.reconcreate as defined below.
But when I try to invoke $state.go('iceberg.reconcreate') on the click of a button, I get the error that state is not defined.
angular-ui-router.js:982 Uncaught Error: No such state undefined at
Object.transitionTo (angular-ui-router.js:982) at Object.go
(angular-ui-router.js:973) at HTMLAnchorElement.
(angular-ui-router.js:1383) at HTMLAnchorElement.dispatch
(jquery.js:4641) at HTMLAnchorElement.elemData.handle (jquery.js:4309)
ROUTING
routingSetup.$inject = ['$stateProvider', '$urlRouterProvider'];
function routingSetup($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/iceberg-ui");
$stateProvider
.state('iceberg', {
url: "/iceberg-ui",
templateUrl: "app/iceberg/iceberg.view.html",
controller: 'IcebergController as icebergCtrl'
})
.state('iceberg.reconlist', {
templateUrl: "app/iceberg/recon/list/recon.list.view.html",
controller: 'ReconListController as reconListCtrl'
})
.state('iceberg.reconcreate', {
templateUrl: "app/iceberg/recon/create/recon.create.view.html",
controller: 'ReconCreateController as reconCreateCtrl'
})
.state('iceberg.recondetails', {
templateUrl: "app/iceberg/recon/details/recon.details.view.html",
controller: 'ReconDetailsController as reconDetailsCtrl'
})
}
Controller
(function() {
'use strict';
var myApp = angular.module('iceberg.recon');
myApp.controller('ReconListController', ReconListController);
ReconListController.$inject = ['ReconListService', '$state'];
function ReconListController(ReconListService, $state) {
var vm = this;
function getReconciliationList() {
return {
load: function(loadOptions) {
var reconList = ReconListService.getReconciliationList();
return {
data: reconList,
totalCount: reconList.length
};
},
update: function(key, values) {
console.log("calling the UPDATE GRID");
//Do Nothing, this method is required else the framework throws an error
}
};
}
vm.createNewRecon = {
text: "Create New Reconciliation",
icon: "plus",
type: "success",
height: "45px",
onClick: function(e) {
$state.go('iceberg.reconcreate');
}
};
}
}());
UPDATE - Updated with controller details
Sorry folks, that was an oversight from my end. I had a ui-sref defined with the incorrect name and all this while I was thinking it was getting called as a onclick event. My bad, apologies for the same.

AngularJS UI-Router v1.0: getting state name during resolving

I am migrating from UI-Router v0.4.x to v1.0 and I'm having an issue. Consider the following piece of code.
myApp
.component('myComponent', {
bindings: {
resolveBinding: '<'
},
controller: class myComponentController {
constructor() {
// ...
}
},
templateUrl: someTemplateUrl
})
.config(($stateProvider) => {
$stateProvider
.state('some-state', {
url: '/some-state',
component: 'myComponent',
resolve: {
resolveBinding: function(DependencyResolver) {
let targetStateName = this.self.name
return DependencyResolver.doSomeThingsFor(targetStateName)
}
}
})
})
In the new versionlet targetStateName = this.self.name will fail because this is now null, whereas before it contained information on the state it was transitioning to.
How can I get the state name in this block of code?
I was thinking about using a $transitions.onBefore hook to put the name on rootScope, and doing something like:
resolveBinding: function($rootScope, DependencyResolver) {
let targetStateName = $rootScope.hackyStateName
return DependencyResolver.doSomeThingsFor(targetStateName)
}
But I feel this is ugly and I'm missing out on something easier and more elegant.
You can inject $transition$ into a resolve function:
resolveBinding: function($transition$) {
console.log($transition$.to());
}
See the $transition$ documentation.
Does that help you?

Angular directive receiving object as attribute using Typescript

I am developing using Angular 1.5.8 and Typescript
I have a directive which is used under the scope of another directive (and another controller of course). Let's say Directive1, Controller1 and Directive2, Controller2.
Given the Controller1 already has the user information, I would like to pass this user information to the Controller2 through the Directive2, to prevent from fetching the information again from the backend.
I am not sure if this can be done, but it would be nice if that's the case :)
Below is the code to help my explanation:
Directive1 HTML:
<div>
...
<directive2 user="{{ctrl.loggedUser}}"></directive2>
...
</div>
loggedUser is loaded in Controller1 constructor through a call to the backend.
Directive2 and Directive2Ctrl Typescript code:
class Directive2 implements ng.IDirective {
controller = "Directive2Ctrl";
controllerAs = "d2Ctrl";
bindToController = {
user: "#"
};
restrict = "E";
templateUrl = "directive2.html";
static factory(): ng.IDirectiveFactory {
const directive = () => new Directive2();
return directive;
}
}
angular
.module("app")
.controller("Directive2Ctrl", Directive2Ctrl)
.directive("directive2", Directive2.factory());
class Directive2Ctrl implements IDirective2Ctrl {
public user: User;
constructor(user: User) {
// user is undefined
}
$onInit(user: User): void {
// user is undefined
}
}
I couldn't find a way of passing the user object to the Directive2Ctrl (not even sure if it is possible).
Use "scope" property instead of "bindToController" property, and replace your "#" with "=".
Then I use an interface for my specific scope to get autocompletion.
export interface IMyDirectiveScope extends ng.IScope {
user: any;
}
export class Myirective {
public restrict: string = 'E';
public templateUrl = "/mytemplate.html";
public link: (scope: IMyDirectiveScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ngModel: ng.INgModelController) => void;
public scope = {
user: "="
};
constructor() {
var context = this;
context.link = (scope: IMyDirectiveScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ngModel: ng.INgModelController) => {
//INSERT YOUR CODE
//console.log(scope.user);
};
}
public static Factory() {
var directive = () => {
return new MyDirective();
};
return directive;
}
}
In your html, remove your curly braces.
<div>
...
<directive2 user="ctrl.loggedUser"></directive2>
...
</div>
If you want to share data between different locations in your application, just put it in a service and use DI wherever you need the data.
That is, fetch the data, store it in a service and use DI to make the data available in different locations. There is no need to pass data through bindings over several layers, much easier to use a service.
var mod = angular.module('testApp', ['ngRoute']);
mod.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/intern', {
template: '<div class="outer" ng-controller="InternController">{{User.firstName}} <div class="nested" ng-controller="NestedController">{{NestedUser.lastName}}<test-dir></test-dir></div></div>',
resolve: {
userResolve: function($q, $timeout, User) {
var deferred = $q.defer();
// mock server call, which returns server data
$timeout(function() {
var mockUserResp = {
firstName: 'John',
lastName: 'Rambo'
};
User.setUser(mockUserResp);
deferred.resolve();
}, 1000);
return deferred.promise;
}
}
}).
otherwise({
redirectTo: '/intern'
});
}]);
mod.factory('User', function() {
var _user = null;
return {
setUser: function(user) {
_user = user;
},
getUser: function() {
return _user;
}
}
});
mod.controller('InternController', function($scope, User) {
$scope.User = User.getUser();
});
mod.controller('NestedController', function($scope, User) {
$scope.NestedUser = User.getUser();
});
mod.directive('testDir', function(User) {
return {
restrict: 'EA',
scope: {},
template: '<div class="dir">{{firstName}} is a cool guy.</div>',
link: function(scope) {
scope.firstName = User.getUser().firstName;
}
};
});
.outer {
border: 1px solid green;
}
.nested {
border: 1px solid blue;
}
.dir {
border: 1px solid orange;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.12/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.12/angular-route.min.js"></script>
<div ng-app="testApp">
<div ng-view></div>
</div>
It should be like this. But if it's still not working for you, you can create a simple plunker and I will fix it there. Cheers!
<div>
...
<directive2 user="ctrl.loggedUser"></directive2>
...
</div>
`
class Directive2 implements ng.IDirective {
controller = "Directive2Ctrl";
controllerAs = "d2Ctrl";
scope = {},
bindToController = {
user: "="
};
restrict = "E";
templateUrl = "directive2.html";
static factory(): ng.IDirectiveFactory {
return () => new Directive2();
}
}
angular
.module("app")
.controller("Directive2Ctrl", Directive2Ctrl)
.directive("directive2", Directive2.factory());
class Directive2Ctrl implements IDirective2Ctrl {
public user: User;
$onInit(user: User): void {
// user should be available
console.log(this.user);
}
}
Sorry, I wonder if you still need to set scope = {}
To do what I asked in the first place, the scope needs to be correctly used.
Here is another question which is nicely explained which uses the scope correctly:
How can I define my controller using TypeScript?

Can there be an AngularJS directive to manage an array of conditions?

I'm new to AngularJS and my project at the moment has a menu that only needs to be displayed sometimes.
I therefore have:
<div class="iframe-hide"
ng-show="$state.includes('deposit.card.start')||
$state.includes('deposit.card.3ds')||
$state.includes('deposit.card.waiting')||
$state.includes('deposit.bank')||
$state.includes('deposit.x')||
$state.is('deposit.x.start')||
$state.is('deposit.y.start')||
$state.is('deposit.y.frame')">
As you can imagine, as the project grows this becomes unmanageable, so I want to look into tidying it up and creating perhaps a custom directive that will handle these conditions better.
I've been thinking of adding a custom data parameter like this:
.state("deposit.card.waiting", {
url: "/waiting",
templateUrl: "app/deposit/templates/card/waiting.html",
data: { includeMenu: true }
})
The Html instead would be:
<div class="iframe-hide" show-if-true="includeMenu">
And then a directive that will check whether includeMenu is true. I wrote it here:
export class showIfTrueDirective {
static $inject = ["$", "$rootScope"];
static $rootScope: any;
public static build($, $rootScope) {
var directive: ng.IDirective = {
link: (scope, element, attributes: any) => {
var itemToShow = attributes["showIfTrue"];
// this correctly prints "includeMenu"
// grab the data from current state?. If includeMenu == true then show element, otherwise hide element
}
};
return directive;
}
}
if I hook that up:
.directive("showIfTrue", ["$", "$rootScope", (r, s) => { return ShowIfTrueDirective.build(r,s); }])
If I manage to grab the scope data then this might work but this is my first week using Anglular and not entirely sure what I'm doing. Is there a better solution for this scenario?
I managed to solve it:
export class NgHideDirective {
static $inject = ["$rootScope"];
static $rootScope: any;
public static build($rootScope) {
var directive: ng.IDirective = {
link: (scope, element, attributes: any) => {
var itemToHide = attributes["ngHide"];
$rootScope.$on('$stateChangeStart',
(event, toState) => {
if (toState.data.hasOwnProperty(itemToHide)) {
element.hide();
} else {
element.show();
}
});
}
};
return directive;
}
}
So if we now do this on an element:
<div class="iframe" ng-hide="hideMenu">
And this on the state:
.state("deposit.x.rejected", {
url: "/rejected",
templateUrl: "app/deposit/templates/x/rejected.html",
data: { hideDepositMenu: null }
Then the div will be hidden.
However this doesn't work when page is refreshed for some reason.

Adding a new data model to Malhar-Angular-Dashboard

Im' working on the Malhar Angular Dashboard, based on this github project https://github.com/DataTorrent/malhar-angular-dashboard.
As per the documentation in the link post just above, under the 'dataModelType' heading 1/2 way down:
`The best way to provide data to a widget is to specify a dataModelType in the Widget Definition Object (above). This function is used as a constructor whenever a new widget is instantiated on the page.`
And when setting up the Widget Definition Objects, there are various options to choose from :
templateUrl - URL of template to use for widget content
template - String template (ignored if templateUrl is present)
directive - HTML-injectable directive name (eg. "ng-show")
So when I add my own widget definition column chart, I attempt to use the 'template' option; however it does NOT inject the {{value}} scope variable I'm setting.
Using the original datamodel sample widget def, it works fine using the 'directive' option. If I mimic this method on my column chart definition then it works ! But it doesn't work using the template option.
Here's the 'widgetDefinitions' factory code :
(function () {
'use strict';
angular.module('rage')
.factory('widgetDefinitions', ['RandomDataModel','GadgetDataModel', widgetDefinitions])
function widgetDefinitions(RandomDataModel, GadgetDataModel) {
return [
{
name: 'datamodel',
directive: 'wt-scope-watch',
dataAttrName: 'value',
dataModelType: RandomDataModel // GOTTA FIGURE THIS OUT !! -BM:
},
{
name: 'column chart',
title: 'Column Chart',
template: '<div>Chart Gadget Here {{value}}</div>',
dataAttrName: 'value',
size: {width: '40%',height: '200px'},
dataModelType: ColumnChartDataModel
},
];
}
})();
and here are the factories:
'use strict';
angular.module('rage')
.factory('TreeGridDataModel', function (WidgetDataModel, gadgetInitService) {
function TreeGridDataModel() {
}
TreeGridDataModel.prototype = Object.create(WidgetDataModel.prototype);
TreeGridDataModel.prototype.constructor = WidgetDataModel;
angular.extend(TreeGridDataModel.prototype, {
init: function () {
var dataModelOptions = this.dataModelOptions;
this.limit = (dataModelOptions && dataModelOptions.limit) ? dataModelOptions.limit : 100;
this.treeGridActive = true;
//this.treeGridOptions = {};
this.updateScope('THIS IS A TreeGridDataModel...'); // see WidgetDataModel factory
},
updateLimit: function (limit) {
this.dataModelOptions = this.dataModelOptions ? this.dataModelOptions : {};
this.dataModelOptions.limit = limit;
this.limit = limit;
},
destroy: function () {
WidgetDataModel.prototype.destroy.call(this);
}
});
return TreeGridDataModel;
});
'use strict';
angular.module('rage')
.factory('ColumnChartDataModel', function (WidgetDataModel) {
function ColumnChartDataModel() {
}
ColumnChartDataModel.prototype = Object.create(WidgetDataModel.prototype);
ColumnChartDataModel.prototype.constructor = WidgetDataModel;
angular.extend(ColumnChartDataModel.prototype, {
init: function () {
var dataModelOptions = this.dataModelOptions;
this.limit = (dataModelOptions && dataModelOptions.limit) ? dataModelOptions.limit : 100;
this.treeGridActive = true;
var value = 'THIS IS A ColChartDataModel...';
//$scope.value = value;
this.updateScope(value); // see WidgetDataModel factory
},
updateLimit: function (limit) {
this.dataModelOptions = this.dataModelOptions ? this.dataModelOptions : {};
this.dataModelOptions.limit = limit;
this.limit = limit;
},
destroy: function () {
WidgetDataModel.prototype.destroy.call(this);
}
});
return ColumnChartDataModel;
});
and finally the directives:
'use strict';
angular.module('rage')
.directive('wtTime', function ($interval) {
return {
restrict: 'A',
scope: true,
replace: true,
template: '<div>Time<div class="alert alert-success">{{time}}</div></div>',
link: function (scope) {
function update() {
scope.time = new Date().toLocaleTimeString();
}
update();
var promise = $interval(update, 500);
scope.$on('$destroy', function () {
$interval.cancel(promise);
});
}
};
})
.directive('wtScopeWatch', function () {
return {
restrict: 'A',
replace: true,
template: '<div>Value<div class="alert alert-info">{{value}}</div></div>',
scope: {
value: '=value'
}
};
})
.directive('wtFluid', function () {
return {
restrict: 'A',
replace: true,
templateUrl: 'app/views/template2/fluid.html',
scope: true,
controller: function ($scope) {
$scope.$on('widgetResized', function (event, size) {
$scope.width = size.width || $scope.width;
$scope.height = size.height || $scope.height;
});
}
};
});
I'd like to know why ONLY the directive option will update the wigdet's data and not the template option.
thank you,
Bob
I believe I see the problem. The dataAttrName setting and updateScope method are actually doing something other than what you're expecting.
Look at the makeTemplateString function here. This is what ultimately builds your widget's template. You should notice that if you supply a template, the dataAttrName does not even get used.
Next, take a look at what updateScope does, and keep in mind that you can override this function in your own data model to do what you really want, a la:
angular.extend(TreeGridDataModel.prototype, {
init: function() {...},
destroy: function() {...},
updateScope: function(data) {
// I don't see this "main" object defined anywhere, I'm just going
// off your treegrid.html template, which has jqx-settings="main.treeGridOptions"
this.widgetScope.main = { treeGridOptions: data };
// Doing it without main, you could just do:
// this.widgetScope.treeGridOptions = data;
// And then update your treegrid.html file to be:
// <div id="treeGrid" jqx-tree-grid jqx-settings="treeGridOptions"></div>
}
});

Resources