angular 1.5 component and babel - angularjs

so, I'm trying to use the 1.5 component feature, but coding with fat arrows. I am using babel to build the system
My component, stripped down to the bare minimum to show my problem, is thus:
angular.module('myApp')
.component('myComponent', {
controller: () => {
this.$onInit = () => {};
},
template: `<p>foobar1</p>`
});
when I try and load this component, I get an error complaining about
typeError: Cannot set property '$onInit' of undefined
so, when I look at the sources in chrome devtools, I see
angular.module('myApp').component('myComponent', {
/** #ngInject */
controller: function controller() {
undefined.$onInit = function () {};
},
template: '<p>foobar1</p>'
});
I would expect that I have done something very wrong, but can't see it ;)
anyone got any tips ?
thanks

Angular creates new instantion of controller for every component. In ES5 we dont have classes, so we pass construction function here.
But in es6 we have class, so you can use it instead
let myComponent = {
controller: myComponentController,
template: `<p>foobar1</p>`
};
class myComponentController{
constructor() {
this.answer = 41;
}
$onInit() {
this.answer++;
}
};
angular.module('myApp')
.component('myComponent', myComponent);
Pascal has also written something about it here: http://blog.thoughtram.io/angularjs/es6/2015/01/23/exploring-angular-1.3-using-es6.html

Related

AngularJS anonymous component replace inner scope

Hi I am using Angular with ES6, now I want to get rid of the $scope since Angular2 will not use it anymore and I want to create futureprove code,... this works:
let tab = this.tabManager.getArea.open({
controller: ['$scope', function (scope) {
console.log(scope);
scope.close = function () {
tab.close();
}
}],
template: '<component-name on-close="close()" ...></component-name>'
});
but how do I rewrite it without injecting the scope, I thought about something like this:
let tab = this.tabManager.getArea.open({
controller: [class {
constructor() {
console.log('construct');
}
close() {
console.log('close');
tab.close();
}
}],
template: '<component-name on-close="close()" ...></component-name>'
});
But it does not seem to work properly, the construtor is called up, however the binding for on-close does not seem to work.
the controller class in 1.6 exposes $ctrl object , can access "close()" using that.try once

Does UI-Router Resolve Work with Angular 1.5 Components?

So, UI-router resolves were a thing of beauty in angular 1:
$stateProvider.state('myState', {
resolve:{
myVarFromResolve: function(){
return 'test';
}
}
})
controller: function($scope, myVar){
$scope.myVar= myVarFromResolve;
if(true){console.log($scope.myVar}; //logs 'test'
}
How do I do the same with an Angular 1.5. component (example below)?
export default function FooComponent() {
let fooComponent = {
restrict: 'E',
templateUrl: 'foo.html',
controller: controller,
controllerAs: 'foo',
bindToController: true,
};
return landingComponent;
}
And the resolve...
.state('fooState', {
parent: 'someParent',
url: '/fooUrl',
template: '<foo></foo>',
resolve:{
myVarFromResolve: function(){
return 'test';
}
}
})
I read a guide on how to do this, but I don't really understand it. Seems like the functionality isn't fully in place and that's a hack.
Looks like the answer is "Yes, kinda"...
I was given a work-around to this problem by a team member that works for angular-ui-router 0.2.18 (use npm list angular-ui-router --depth=0 to check your version quickly). It does work while using ES6 classes.
The work around is based on the fact that there is no (easy/known) way to get the variable passed into the controller, like you would in Angular 1 before component architecture introduced in 1.5. Instead, we create a service method that creates a promise (or returns an http call). Then you simply put the promise in the resolve. The promise method WILL return before the controller loads. So while you do not have a easy to use var, you have the data you need in a service.
resolve: {
preloadSomeVariableIntoAService: function (MyExampleService) {
return MyExampleService.presetSomeVariable();
}
//Note that 'preloadSomeVariableIntoAService' won't be used anywhere in our case
}
The service could be something like this:
export default class MyExampleService {
constructor( $q ) {
this.q = $q;
this.myVariableFromResolve = undefined;
}
presetStreamStatus(){
var deferred = this.q.defer();
return $http.get(someUrl).then(response) => {
this.myVariableFromResolve = response.data;
deferred.resolve(events);
});
return deferred;
};
};
You will then be able to get myVariableFromResolve from the service. I am not sure you have to use $q deferred, but it seems to be necessary since I am not simply returning the call but also setting the variable. Not sure. Hope this helps someone.

Invoking function breaks scope

Same issue as here: AngularJS directive binding a function with multiple arguments
Following this answer: https://stackoverflow.com/a/26244600/2892106
In as small of a nutshell as I can.
This works:
<my-directive on-save="ctrl.saveFn"></my-directive>
With:
angular.module('app')
.controller('MyController', function($scope) {
var vm = this;
vm.saveFn = function(value) { vm.doSomethingWithValue(value); }
});
But when I convert to Typescript and use real classes, this breaks.
class MyController {
constructor() {}
saveFn(value) {
this.doSomethingWithValue(value);
}
}
In the Typescript version when debugging, "this" is referencing the Window global. So my scope is messed up somehow, but I don't know how to fix it. How can I get "this" to refer to MyController as expected?
So my scope is messed up somehow
Use an arrow function instead of a method.
Fixed
class MyController {
constructor() {}
saveFn = (value) => {
this.doSomethingWithValue(value);
}
}
More
https://basarat.gitbooks.io/typescript/content/docs/arrow-functions.html

Angular, Redux, ES6, Unit Testing Controllers

Our Angular project moved to ES6 and Reduxjs, and now I am struggling to get controller unit tests working. Specifically, I cant seem to mock correctly when it comes to the class constructor. From what i have researched, i cant spyOn an ES6 class constructor, so i need to mock its dependencies and also accommodate the binding to lexical 'this' that ngRedux.connect() facilitates.
My test makes it to the connect function in the constructor, and then gives me the error: "'connect' is not a function"
I think i may have several things wrong here. If i comment out the connect line in the constructor, it'll get to my runOnLoad function and the error will tell me that fromMyActions isnt a function. this is because the redux connect function binds the actions to 'this', so given these issues, I take it I cant mock redux unless i provide its implementation. any advice? I am relatively new to angular as well - and my weakest area is unit testing and DI.
Here is my module and controller:
export const myControllerModule = angular.module('my-controller-module',[]);
export class MyController {
constructor($ngRedux, $scope) {
'ngInject';
this.ngRedux = $ngRedux;
const unsubscribe = this.ngRedux.connect(this.mapState.bind(this), myActions)(this);
$scope.$on('$destroy', unsubscribe);
this.runOnLoad();
}
mapState(state) {
return {};
}
runOnLoad() {
this.fromMyActions(this.prop);
}
}
myControllerModule
.controller(controllerId, MyController
.directive('myDirective', () => {
return {
restrict: 'E',
controllerAs: 'vm',
controller: controllerId,
templateUrl: htmltemplate
bindToController: true,
scope: {
data: '=',
person: '='
}
};
});
export default myControllerModule.name;
and my test:
import {myControllerModule,MyController} from './myController';
import 'angular-mocks/angular-mocks';
describe('test', () => {
let controller, scope;
beforeEach(function() {
let reduxFuncs = {
connect: function(){}
}
angular.mock.module('my-controller-module', function ($provide) {
$provide.constant('$ngRedux',reduxFuncs);
});
angular.mock.inject(function (_$ngRedux_, _$controller_, _$rootScope_) {
scope = _$rootScope_.$new();
redux = _$ngRedux_;
var scopeData = {
data : {"test":"stuff"},
person : {"name":"thatDude"}
} ;
scope.$digest();
controller = _$controller_(MyController, {
$scope: scope,
$ngRedux: redux
}, scopeData);
});
});
});
The idea behind Redux is that most of your controllers have no, or very little logic. The logic will be in action creators, reducers and selectors mostly.
In the example you provide, most of your code is just wiring things.
I personally don't test wiring, because it adds very little value, and those kinds of test are generally very brittle.
With that said, if you want to test your controllers nonetheless you have two options:
Use functions instead of classes for controllers. For most controllers using a class adds no real value. Instead use a function, and isolate the logic you want to test in another pure function. You can then test this function without even needing mocks etc.
If you still want to use classes, you will need to use a stub of ng-redux, (something like this: https://gist.github.com/wbuchwalter/d1448395f0dee9212b70 (it's in TypeScript))
And use it like this:
let myState = {
someProp: 'someValue'
}
let ngReduxStub;
let myController;
beforeEach(angular.mock.inject($injector => {
ngReduxStub = new NgReduxStub();
//how the state should be initially
ngReduxStub.push(myState);
myController = new MyController(ngReduxStub, $injector.get('someOtherService');
}));
Note: I personnaly don't use mapDispatchToProps (I expose my action creators through an angular service), so the stub does not handle actions, but it should be easy to add.
I have been getting the following error while trying to write tests using Karma and Jasmine:
TypeError: undefined is not a constructor (evaluating
$ngRedux.connect(function (state) { ({}, state.editAudience); }, _actions2.default)')
I managed to make it work by roughly translating wbuch's stub into es6 and using it like this:
angular.mock.module( $provide => {
ngReduxStub = new NgReduxStub()
ngReduxStub.push(myState)
const dependencies = ['$scope', '$ngRedux']
dependencies.forEach( dependency => {
if(dependency =='$ngRedux') { return $provide.value('$ngRedux', ngReduxStub)}
return $provide.value(dependency, {})
})
})
angular.mock.inject( ($compile, $rootScope, $componentController) => {
scope = $rootScope.$new()
ctrl = $componentController('csEditAudience', {$scope: scope}, {})
})
Here's my version of the stub
I hope this helps!

Angular - How to show modal reject reason in table?

I have small problem to solve.
I have modal controller rejectIssueModalCtrl.js
(function () {
'use strict';
function rejectIssueModalCtrl($modalInstance, issue, $rootScope) {
var self = this;
self.cancel = function () {
$modalInstance.dismiss('cancel');
};
self.reject = function ($rootScope) {
$modalInstance.close(self.reason);
console.log(self.reason);
};
$rootScope.reasono = self.reason;
}
rejectIssueModalCtrl.$inject = ['$modalInstance', 'issue', '$rootScope'];
angular
.module('app')
.controller('rejectIssueModalCtrl', rejectIssueModalCtrl);
})();
When I click the button I can open this modal and write a reason. I want to show this reject reason in table in other controller.
Here's my code from other controller issueDetailsCtrl.js
$scope.reasonoo = $rootScope.reasono;
function rejectIssue() {
var rejectModal = $modal.open({
templateUrl: '/App/Issue/rejectIssueModal',
controller: 'rejectIssueModalCtrl',
controllerAs: 'rejectModal',
size: 'lg',
resolve: {
issue: self.issueData
}
});
rejectModal.result.then(function (reason) {
issueSvc
.rejectIssue(self.issueData.id, reason)
.then(function (issueStatus) {
changeIssueStatus(issueStatus.code);
getIssue();
}, requestFailed);
});
};
and html code
<div>
<span class="right" ng-bind="$root.reasono"></span>
</div>
As you can see I tried to use $rootScope. I can console.log the reason but I cant make it to show in this html. Any help?
We're missing some context, but I believe this is your problem:
self.reject = function ($rootScope) {
$modalInstance.close(self.reason);
console.log(self.reason);
};
$rootScope.reasono = self.reason;
Assuming self.reason is bound to the input in your modal, it won't be defined outside of the reject callback - that's the nature of async. You're able to log to console because you're doing so within the callback.
Define $rootScope.reasono inside of the callback like so:
self.reject = function () {
$modalInstance.close(self.reason);
console.log(self.reason);
$rootScope.reasono = self.reason;
};
Edited to show that $rootScope should be removed as a named parameter in the reject function definition.
Using root scope is not recommended. For this reason it is recommended to create a service for intercommuncation with variable to store reject reason, then inject this service for each controller - that way you will be able to read/write reason from different controllers.

Resources