Where am I doing wrong in converting angularjs component into es6 class? - angularjs

The options for chart are not coming. $onChanges method is not getting invoked. There is no compilation error though. I have executed the same without es6 convention. When I have converted this into es6 classes, I am facing this issue. Please help...
class chartController{
constructor(dashboardService){
this.dashboardService = dashboardService;
}
$onChanges(changes) {
console.log(changes);
if (changes && changes.options.currentValue) {
this.lineChartOptions = dashboardService.options.getLineChartOptions();
console.log("calling updateLineChartOptions");
updateLineChartOptions();
}
}
updateLineChartOptions() {
angular.extend(this.lineChartOptions, this.options);
this.lineChartOptions.bindingOptions = {
'dataSource': '$ctrl.options.dataSource',
};
this.lineChartOptions.valueAxis = {
valueType: 'numeric'
};
}
}
class ChartComponent{
bindings = {
$transition$: '<', options: '<'
};
controller = chartController;
templateUrl = 'views/chart.html';
}
app.component('chart', ChartComponent);

const ChartComponent = {
bindings: {
$transition$: '<',
options: '<'
},
controller: chartController,
templateUrl: 'views/chart.html',
}
app.component('chart', ChartComponent);
The .component method of an angular.module instance requires an object for its second argument, not a class (or function).
For more information, see
AngularJS angular.module Type API Reference - component method

Related

Unit Test Resolve Block in Angular component

Does anyone know how to unit test resolve block items in a routed component? It would be great if anyone can test courseDetails in the resolve block as an example.
(function() {
'use strict';
angular
.module('writingsolutionsComponents')
.component('courseSettings', {
bindings: {
newCourseForm: '<',
courseDetails: '<',
timezones: '<',
citations: '<',
disciplines: '<'
},
templateUrl: 'course-settings/course-settings.html',
controller: 'CourseSettingsController'
})
.config(stateConfig);
stateConfig.$inject = ['$stateProvider'];
function stateConfig($stateProvider, $urlRouterProvider) {
$stateProvider.state('course-settings', {
parent: 'app',
url: '/:courseId/course-settings',
data: {
pageTitle: "Hello World"
},
views: {
'content#': {
component: 'courseSettings'
}
},
resolve: {
courseDetails: function(CourseService, $stateParams) {
return CourseService.get($stateParams.courseId);
},
timezones: function(TimezoneService) {
return TimezoneService.getTimeZones();
},
citations: function(CitationService) {
return CitationService.getCitations();
},
disciplines: function(DisciplineService) {
return DisciplineService.getAllDisciplines();
}
}
}
});
}
})();
I tried following and it didn't workout for me.
CourseServiceMock = {
get: function () {
return $q.resolve({courseId: "32432535", title: "Communication"});
}
};
spyOn(CourseServiceMock , 'get').and.callThrough();
expect(CourseServiceMock .get).toHaveBeenCalled()
Do not test your framework(s). Here that means do not test ui-router, it is known to work correctly. Specifically, we know it is going to call the registered resolve functions.
If you wish to test the resolve function itself, it is actually quite simple. We just need to make the function available to test. We can actually test this function without launching a web browser or doing anything fancy.
Here is an example that uses a test library called blue-tape* to run the test using NodeJS, you can adapt it to use Jasmine if you must.
// tests.js
const test = require("blue-tape");
test("courseDetails resolve retrieves the course based on route/:courseId", async t => {
// arrange
const courses = [{courseId: "32432535", title: "Communication"}];
const mockCourseService = {
get(id) {
return Promise.resolve(courses.find(course => course.id === id));
}
};
const mock$stateParams = {courseId: "32432535"};
// act
const result = await courseDetails(mockCourseService, mock$stateParams);
// assert
t.equal(result.courseId, courses[0].courseId);
});
To get this working, run
npm install --save-dev blue-tape
And then run
node test.js
The benefit is that your test does not depend on ui-router or even AngularJS and that it just tests what you care about, your business logic.
Note, this test itself is a bit arbitrary.
*I am using blue-tape instead of tape here because it makes testing asynchronous functions very simple as it automatically handles any function that returns a thennable.

angularJS ES6 Directive

I am trying to develop an application in angular es6 . I have a problem with directve.
Here is my code
export default class RoleDirective {
constructor() {
this.template="";
this.restrict = 'A';
this.scope = {
role :"#rolePermission"
};
this.controller = RoleDirectiveController;
this.controllerAs = 'ctrl';
this.bindToController = true;
}
// Directive compile function
compile(element,attrs,ctrl) {
console.log("df",this)
}
// Directive link function
link(scope,element,attrs,ctrl) {
console.log("dsf",ctrl.role)
}
}
// Directive's controller
class RoleDirectiveController {
constructor () {
console.log(this.role)
//console.log("role", commonService.userModule().getUserPermission("change_corsmodel"));
//$($element[0]).css('visibility', 'hidden');
}
}
export default angular
.module('common.directive', [])
.directive('rolePermission',[() => new RoleDirective()]);
The problem is i couldn't get the role value inside constructor.
here is my html implementation
<a ui-sref="event" class="button text-uppercase button-md" role-permission="dfsd" detail="sdfdsfsfdssd">Create event</a>
If i console this it will get the controller object. But it will not get any result while use this.role.
Ok, so I managed to find out how this works.
Basically, the scope values cannot be initialized on the controller's constructor (because this is the first thing executed on a new object) and there is also binding to be considered.
There is a hook that you can implement in your controller that can help you with your use case: $onInit:
class RoleDirectiveController {
constructor () {
// data not available yet on 'this' - they couldn't be
}
$onInit() {
console.log(this.role)
}
}
This should work. Note that this is angular1.5+ way of doing things when not relying on $scope to hold the model anymore. Because if you use the scope, you could have it in the controller's constructor (injected).

UI-router stateParams cloning objects?

I have this route:
{
name : 'myPage',
url : '/myPage',
views: {
'#': {
component: components.MyComponent.name
}
},
params: {
turtle: {y: 2}
},
resolve : {
turtle: function($stateParams) {
window.turtle = $stateParams.turtle;
window.daaa = $stateParams;
return $stateParams.turtle;
}
}
}
I have this component binding def:
bindings: {
turtle: '<'
}
and the constructor of MyComponent's controller:
class MyComponentController {
let self = this;
this.$onInit = function() {
console.log("-----")
console.log("window.turtle: " + JSON.stringify(window.turtle))
console.log("self.turtle: " + JSON.stringify(self.turtle))
console.log("window.turtle == self.turtle: " + (window.turtle == self.turtle))
console.log("window.turtle == self.$stateParams.turtle: " + (window.turtle == self.$stateParams.turtle))
console.log("window.daaa == self.$stateParams: " + (window.daaa == self.$stateParams))
console.log("-----")
}
}
It prints:
-----
window.turtle: {y:2}
self.turtle: {y:2}
window.turtle == self.turtle: true
window.turtle == self.$stateParams.turtle: false
window.daaa == self.$stateParams: false
-----
This is really strange to me..., made me think that once you're inside a state, UI-router makes a clone of $stateParams (so the $stateParams you see inside the resolves != $stateParams you see in the controller)... and it also makes a clone of each declared params (deep clone???).
I wasn't expecting that. Is it a bug? or a feature (maybe protection mechanism?). Who is the culpable here? UI-router? Or is it angular 1.5 component (with its isolated scope stuffs)?
Thanks in advance for helping me clarify this.
According to ui-router source code (was linked by Daniel here) $stateParams is deprecated and it's advised to use $transition$ injectable instead (see ui-router docs on $transition$)
Here's an example of using it in a resolve function:
$stateProvider.state('a-route', {
// ...
// define params for route
params: {
data: null
},
// set up data to resolve to params.data
resolve: {
data: ($transition$) => {
return $transition$.params().data;
}
}
})
Please, see working example here:
http://jsbin.com/velekot/edit?js,output
NOTE: Docs also claim that one can inject the $transition$ to a controller, but I couldn't make it work, so used a resolve fn.

Angular 1.5 Component - dependency injection

My goal was to use dependency injection of a constant value (the base URL) to use in defining the component's templateUrl property. Nothing so far has worked. The code below is in Typescript
Some explanation: app.core.Iconstants holds the baseUrl value.
app.core.AngularGlobals.appCoreConstants is the string representing "app.core.Constants".
Mind you, I'm just cutting my teeth of Angular.
namespace donationEntry.Components{
"use strict";
class test1 implements ng.IComponentOptions {
public static id: string = AngularGlobals.donationEntry + ".test1";
bindings: any;
controller: any;
templateUrl: string;
constructor(clientContext: app.core.IConstants) {
this.bindings = {
textBinding: '#',
dataBinding: '<',
functionBinding: '&'
};
this.controller = TestController;
this.templateUrl = clientContext.baseUrl + "Areas/ng/app/fundraising/donationEntry/components/test1/test1.html";
}
}
// register the controller with app
angular.module(AngularGlobals.donationEntry)
.component("test1", [app.core.AngularGlobals.appCoreConstants, (c) => new test1(c)]);
}
When I run this, I end up with the following error:
angular.js:61 Uncaught Error: [$injector:modulerr] Failed to instantiate module app due to:
Error: [$injector:modulerr] Failed to instantiate module donationEntry due to:
TypeError: t.charAt is not a function
This appears to work, though I wouldn't call it the most eloquent of answers. Again, written in Typescript:
namespace donationEntry.Components{
"use strict";
class test1 implements ng.IComponentOptions {
public static id: string = AngularGlobals.donationEntry + ".test1";
bindings: any;
controller: any;
templateUrl: string;
constructor(clientContext: app.core.IConstants) {
this.bindings = {
textBinding: '#',
dataBinding: '<',
functionBinding: '&'
};
this.controller = TestController;
this.templateUrl = clientContext.baseUrl + "Areas/ng/app/fundraising/donationEntry/components/test1/test1.html";
}
}
// register the controller with app
var clientContext = <app.core.IConstants>angular.injector(['ng', app.core.AngularGlobals.appCore]).get(app.core.AngularGlobals.appCoreConstants);
angular.module(AngularGlobals.donationEntry)
.component("test1", new test1(clientContext));
}
I try to stay away from Angular's injection or module mechanism whenever I can, preferring straight ES6.
You can do:
//clientContext.ts
export = { baseUrl: 'whateva' }
And then import like this:
import clientContext from './path/to/clientContext'

Typescript + Angular 1.x, directive w/ require:'^someCtrl' failing with 'unknown provider'

Using Angular with Typescript. I have a Tabs controller based on Angular Bootstrap UI tabs that works in pure Angular, but when I move it over to our Typescript style, I can't get one directive (OneTab) to find the controller for the (parent) directive (TabSet).
ERROR: Unknown provider: tabProvider <- tab
I've tried a bunch of different ways of doing this but none of them make it work properly like the non-Typescript version does.
Ex: Angular-only Plunker
We're using a modular model, which is tied together with a config file each for directives, controllers, etc. This structure is working fine except for this tab directive experiment.
module app.directives {
'use strict';
angular.module('app')
.directive('tabSet', ()=> new TabSet())
.directive('oneTab', ()=> new OneTab())
;
}
module app.directives {
interface ITabScope extends ng.IScope {
active:boolean;
}
export class OneTab implements ng.IDirective {
priority = 0;
restrict = 'E';
transclude = true;
require = '^tabsetCtrl';
scope = {
heading: '#'
};
template = '<div role="tabpanel" ng-transclude ng-show="active"></div>';
link = function (scope:ITabScope, element:ng.IAugmentedJQuery, attr:ng.IAttributes, tabsetCtrl:any) {
scope.active = false;
tabsetCtrl.addTab(scope);
}
}
}
module app.directives {
import TabSetController = app.controllers.TabSetController;
export class TabSet implements ng.IDirective{
priority = 0;
restrict = 'E';
transclude = true;
scope = {};
templateUrl = 'layout/tabset.html';
controller = TabSetController;
controllerAs = 'tabsetCtrl';
bindToController = true;
}
}
module app.controllers {
'use strict';
export class TabSetController {
tabs:Array<any>;
tab:any;
selectedTab:any;
constructor(tab:any, selectedTab:any) {
this.tabs = [];
this.addTab(tab);
this.select(selectedTab);
console.log("in constructor");
}
addTab(tab?:any) {
if(tab){
this.tabs.push(tab);
if (this.tabs.length === 1) {
tab.active = true
}
}
};
select(selectedTab?:any) {
if (selectedTab){
angular.forEach(this.tabs, function (tab:any) {
if (tab.active && tab !== selectedTab) {
tab.active = false;
}
});
selectedTab.active = true;
}
}
}
}
The issue isn't with your directives, as far as I can tell, it's with your controller class. Angular assumes that any parameters in the constructor method of a controller are services that you want to inject - seeing as you're trying to pass in parameters called tab and selectedTab, it's searching for services to inject with those names. There's no service named tab, so you get an unknown provider error.

Resources