Testing angular component's controller with Jasmine - angularjs-directive

This is my component:
const comp = {
controller: 'gridDetailsNavigationCtrl',
controllerAs: '$ctrl',
templateUrl: '/Scripts/app/shared/grid/navigation/gridDetailsNavigation.html',
bindToController: true,
bindings: {
viewData: '<',
listState: '#',
detailsState: '#',
pagingData: '=',
backSpinnerId: '#',
stateParams: '<'
}
};
This is part of the component's controller:
class GridDetailsNavigation {
constructor($rootScope, $scope, $state, tipaltiConsts) {
this._$state = $state;
this._$scope = $scope;
this._$rootScope = $rootScope;
this._consts = tipaltiConsts;
}
$onInit() {
....
}
$onDestroy() {
...
}
goToNext() {
....
}
goToPrev() {
....
}
}
I'm trying to test the component and its controller. I'm able to create a component and even get its controller scope:
const component = compile(angular.element(template))(scope);
scope.$digest();
component.$onInit();
// get controller in two different ways.
const $ctrl = component.scope().$ctrl;
const $ctrl2 = component.controller('gridDetailsNavigationCtrl');
My problem is that the $ctrl returns only scope properties and public methods e.g goToNext and goToPrev are not defined, and $ctrl2 is undefined.
I know I can use $componentController but I want the component itself and from it to get the controller. What am I doing wrong here? Why are methods unreachable? why is the second controller undefined?
Thanks

Related

Wait for Promise to finish

I want to run some code, after some other code has run.
This is what I have come up with so far:
import { Controller } from "stimulus"
export default class extends Controller {
this.dotheform().then(data => {
this.updateprovisioncost(data, survey_id, service_id)
})
dotheform() {
return new Promise(resolve => {
var form = document.querySelector('#configure-form');
Rails.fire(form, 'submit')
resolve(true)
});
}
updateprovisioncost(data, survey_id, service_id){
Rails.ajax({
type: "GET",
url: `/provisions/survey_id/${survey_id}/service_id/${service_id}`
})
}
}
The code does not do what I want, It runs the updateprovisioncost method before the dotheform method has completed.

Needed factory working in angular js

Actually i was new to angular js i am trying to call my factory operation into controller i dont know where i am going wrong
and my js goes here
app.factory("myFactory",function(){
var something = {};
something.getsum = function() {
$scope.service = " heloo people"
}
return something;
});
app.controller("helloController", function($scope,myFactory) {
$scope.clickme = function() {
$scope.service=myFactory.getsum();
}
});
and my html goes here
<div ng-controller="hello controller">
<button ng-click="clickme"></button>
<h2>{{service}}</h2>
</div>
and my config goes here:
$urlRouterProvider.otherwise("/index/utilise");
$stateProvider
.state('index', {
abstract: true,
url: "/index",
templateUrl: "display.html",
controller:'mainController',
controllerAs: "parentCtrl",
})
.state('index.sample', {
url: "/home",
templateUrl: "content/sample.html",
})
.state('index.utilise', {
url: "/utilise",
templateUrl: "content/utilise.html",
})
})
First issue is that to use the myFactory factory in your controller you would need to inject it into the controller via dependency injection:
app.controller("helloController", function($scope, myFactory) {
$scope.clickme = function() {
$scope.service = myFactory.getsum();
}
});
Second issue you would not use $scope in the myFactory factory method getsum(), you would simply return the value you need:
app.factory("myFactory",function(){
var something = {};
something.getsum = function() {
return " heloo people";
}
return something;
});
Third issue is ng-click was not actually execute controller function clickme as there was parenthesis () as you would with any JavaScript function. It should be ng-click="clickme()" to actually call the function on the controller:
<div ng-controller="helloController">
<button ng-click="clickme()"></button>
<h2>{{service}}</h2>
</div>
Finally, it's unclear what the structure of your application based on the ui-router configuration your provided. With ui-router you wouldn't really have the need to use ng-controller as you can specify what controller any given view should be using. I've created multiple Plunkers, one and two, demonstrating the factory functionality with and without controllers specified for child routes. This should be more than enough to demonstrating calling a controller function in different situations.
Hopefully that helps!

How to extend bookshelf.js

I have a validate function that I am copying into almost every model. I want to abstract it by extending the base bookshelf.Model object. I'm not sure what the correct way to go about this is in ES6. I'd like to do this without forking bookshelf.
An example model:
import bookshelf from '../bookshelf';
import Checkit from 'checkit';
const Design = bookshelf.Model.extend({
tableName: 'foo',
constructor: function() {
bookshelf.Model.apply(this, arguments); // super()
this.on('saving', this.validate.bind(this));
},
validations: {
barColumn: ['required', 'integer', 'greaterThan:0'],
},
validate: function(model, attrs, options) {
let validations;
if (options.patch === true) {
Object.keys(this.validations).forEach((value, index) => {
if (this.attributes[index] !== undefined) {
validations[index] = value;
}
});
} else {
validations = this.validations;
}
return new Checkit(validations).run(this.toJSON());
}
});
export default Design;
The main bookshelf file is here.
I couldn't work out how to extend Bookshelf so I solved it like this:
import bookshelf from '../bookshelf';
import validate from '../utils/validate';
const Design = bookshelf.Model.extend({
tableName: 'foo',
constructor: function() {
bookshelf.Model.apply(this, arguments); // super()
this.on('saving', validate);
},
validations: {
barColumn: ['required', 'integer', 'greaterThan:0'],
},
});
export default Design;
And the new validate file:
import Checkit from 'checkit';
export default function validate(model, attributes, options) {
let validations;
if (options.patch === true) {
Object.keys(model.validations).forEach((value, index) => {
if (attributes[index] !== undefined) {
validations[index] = value;
}
});
} else {
validations = model.validations;
}
return new Checkit(validations).run(model.toJSON());
};
I think you need Facade or Decorator pattern + some dependency injection.
I used Facade for my own purposes the following way:
class MyFacade {
constructor(legacyLibrary) {
this.legacyLibrary = legacyLibrary;
}
newMethod() {
this.legacyLibrary.doSomething();
this.legacyLibrary.doSomethingElse();
}
}
export default MyFacade;
Idea of decorator is the same, but you should extend your existing class. So all the functionality will remain the same, but you'll have more methods in it.
The good thing about decorator is that you can nest them. Decorate with one, then with another, etc.

pass parameters to angular js directive partial view

I have an MVC application using angularJS. I have a primary navigation and secondary navigation. I am using ngRoute for primary navigation. I made secondary navigation template a directive that I can use in all the other pages. The template used in the directive needs some input parameters.
Routing code:
myApp.config(['$routeProvider', function ($routeProvider) {
$routeProvider.
when('/SecondaryNavigation/1', {
templateUrl: 'administration/Page1',
resolve: {
SecNavItems: ["$http", function($http){
var navItems = $http.get('/Navigation/SecondaryNavigation', {params: { pageName: 'Administration'}});
navItems.success(function (data) {
return data;
});
}]
},
controller: 'AdminController'
})}]);
var AdminController = function ($scope, SecNavItems) {
$scope.secList = SecNavItems;
}
AdminController.$inject = ["$scope", "SecNavItems"];
myApp.controller("AdminController", AdminController);
Web method code:
[HttpGet]
public JsonResult SecondaryNavigation(string pageName)
{
Dictionary<string, string> secnavItems = new Dictionary<string, string>();
secnavItems.Add("1", "Item1");
secnavItems.Add("2", "Item2");
var navigationItemsJson = Json(secnavItems, JsonRequestBehavior.AllowGet);
return navigationItemsJson;
}
Page1 code is
<secondary-navigation></secondary-navigation>
My directive is defined as follows:
myApp.directive("secondaryNavigation", function () {
return {
restrict: 'E',
scope: {},
templateUrl: '/navigation/secondaryNavigation'
}
});
Partial view template:
<div style="height:100%; width:25%; background-color:#675c5c; color: white; float:left">
#foreach (KeyValuePair<int, string> navItem in secList)
{
#navItem.Value<br /><br />
}
</div>
<div style="height:100%; width:75%; float:right"></div>
When I run the application I do not see the Item1 and Item2 in the page instead I see {{object}}
Please advise what I am missing in passing the parameters to the template used in the directive.
Thank you.
Figured what went wrong.
I had to create a html template of secondary navigation. I then included the web service call in the admincontroller, set the object value to the webservice result and added it to the routeProvider. I then
set the scope to false in the directive.
Following are the changes I made.
Routing code:
myApp.config(['$routeProvider', function ($routeProvider) {
$routeProvider.
when('/SecondaryNavigation/1', {
templateUrl: 'administration/Page1',
controller: 'AdminController'
})}]);
var AdminController = function ($scope, $http) {
var navItems = $http.get('/Navigation/SecondaryNavigation', {params: { pageName: 'Administration'}});
navItems.success(function (data) {
$scope.secList = data;
});
}]
},
}
AdminController.$inject = ["$scope", "$http"];
myApp.controller("AdminController", AdminController);
My directive is defined as follows:
myApp.directive("secondaryNavigation", function () {
return {
restrict: 'E',
scope: false,
templateUrl: '/navigation/secondaryNavigation'
}
});

Route Angular to New Controller after Login

I'm kind of stuck on how to route my angular app to a new controller after login. I have a simple app, that uses 'loginservice'... after logging in, it then routes to /home which has a different template from the index.html(login page).
I want to use /home as the route that displays the partial views of my flightforms controllers. What is the best way to configure my routes so that after login, /home is the default and the routes are called into that particular templates view. Seems easy but I keep getting the /login page when i click on a link which is suppose to pass the partial view into the default.html template:
var app= angular.module('myApp', ['ngRoute']);
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/login', {
templateUrl: 'partials/login.html',
controller: 'loginCtrl'
});
$routeProvider.when('/home', {
templateUrl: 'partials/default.html',
controller: 'defaultCtrl'
});
}]);
flightforms.config(['$routeProvider', function($routeProvider){
//sub pages
$routeProvider.when('/home', {
templateUrl: 'partials/default.html',
controller: 'defaultCtrl'
});
$routeProvider.when('/status', {
templateUrl: 'partials/subpages/home.html',
controller: 'statusCtrl'
});
$routeProvider.when('/observer-ao', {
templateUrl: 'partials/subpages/aobsrv.html',
controller: 'obsvaoCtrl'
});
$routeProvider.when('/dispatch', {
templateUrl: 'partials/subpages/disp.html',
controller: 'dispatchCtrl'
});
$routeProvider.when('/fieldmgr', {
templateUrl: 'partials/subpages/fieldopmgr.html',
controller: 'fieldmgrCtrl'
});
$routeProvider.when('/obs-backoffice', {
templateUrl: 'partials/subpages/obsbkoff.html',
controller: 'obsbkoffCtrl'
});
$routeProvider.when('/add-user', {
templateUrl: 'partials/subpages/users.html',
controller: 'userCtrl'
});
$routeProvider.otherwise({
redirectTo: '/status'
});
}]);
app.run(function($rootScope, $location, loginService) {
var routespermission=['/home']; //route that require login
$rootScope.$on('$routeChangeStart', function(){
if( routespermission.indexOf($location.path()) !=-1)
{
var connected=loginService.islogged();
connected.then(function(msg) {
if(!msg.data) $location.path('/login');
});
}
});
});
and my controllers are simple. Here's a sample of what they look like:
var flightformsControllers = angular.module('flightformsController', []);
flightforms.controller('fieldmgrCtrl', ['$scope','$http','loginService',
function($scope,loginService) {
$scope.txt='You are logged in';
$scope.logout=function(){
loginService.logout();
}
}]);
Any ideas on how to get my partials to display in the /home default.html template would be appreciated.
1) Move all the routing into the main app.config, and remove the duplicate route for /home.
2) change this line
var flightformsControllers = angular.module('flightformsController', []);
to
var flightforms = angular.module('flightforms', []);
3) change the app definition line to inject the flightforms module:
var app= angular.module('myApp', ['ngRoute', 'flightforms']);
That should get ya close.
For one of your comments, its a good idea to have an interecptor which catches any 401 un-authenticated errors from the server. This way, if a user's session expires before a route change, they will still have to login again to start a new session. Something like this in app.config should do it.
$provide.factory('logoutOn401', ['$q', '$injector', function ($q, $injector) {
return {
'responseError': function(response) {
if (response.status === 401) {
$location.path('/login')
return $q.reject();
} else {
return $q.reject(response);
}
}
};
}]);
$httpProvider.interceptors.push('logoutOn401');