I want to use resolve to check if user is present on localStorage before controller fires up but can't get it to work.
Example:
//loginDirective.js
import {LoginController as controller} from './login.controller';
import template from './login.html';
export const loginDirective = () =>{
return{
controller,
template,
scope:{},
controllerAs:'vm',
replace: true,
restrict: 'E'
}
}
//login.js
import {loginDirective} from './login.directive';
import angular from 'angular';
import uiRouter from 'angular-ui-router';
export const login = angular.module('admin', [uiRouter])
.config(($stateProvider) => {
$stateProvider.state('login', {
url : '/login',
template: '<login></login>',
resolve : {
loggedUser: function () {
if(localStorage.getItem('user')){
return true;
}
return false;
}
}
})
})
.directive('login', loginDirective);
//LoginController
'use strict';
class LoginController {
constructor($state, AuthenticationService, loggedUser) {
this.$state = $state;
this.AuthenticationService = AuthenticationService;
this.email = '';
this.password = '';
this.loggedIn = loggedUser;
}
login = () => {
this.AuthenticationService.Login(this.email, this.password).then((result) => {
this.loggedIn = result;
if (this.loggedIn === true) {
this.$state.go('home', {}, {reload: true});
}
});
}
logout = () => {
this.AuthenticationService.Logout();
this.loggedIn = false;
this.$state.go('login', {location: true});
}
}
LoginController.$inject = ['$state', 'AuthenticationService', 'loggedUser'];
export {LoginController}
This throws Unknown provider: loggedUserProvider <- loggedUser
Note:
I am using webpack, gulp and babel to transpile. WebPack has babel stage 1 enabled.
I can see the resolve method in this.$state but that is not the way I want to access it.
Your 'login' state definition has no controller. So ui-router doesn't instantiate the login controller. So it can't pass anything to its constructor.
The controller is not the controller of the state. It's the controller of a directive that happens to be used in the template of the state. So that can't work.
Read https://ui-router.github.io/guide/ng1/route-to-component to understand how you must do: either use a controller and a template, or use a component.
Related
I have an Angular js module
var appModule = angular.module('appModule', ['datatables','ngMaterial', 'ui.select'])
when i declare a new component using my appModule, I refresh my app in browser but it creates a request load cycle in XHR and my app breaks
appModule.component('myCustomTable', {
templateUrl: 'table-component',
controller: function () {
this.user = {
name: 'user name'
};
}
});
Am using AngularJS 1.65
It might happen because your controller is an anonymous function.
(According to: https://docs.angularjs.org/tutorial/step_03)
appModule.component('myCustomTable', {
templateUrl: 'table-component',
controller: function () { // <- Here
this.user = {
name: 'user name'
};
}
});
I'm changing my existing AngularJS app from (AngularJS + ES5 + Webpack) to (AngularJS + Typescript + Webpack)
This is one of my module implementation:
login.controller.ts
import {ILoginScope} from "./login.scope";
import {LoginViewModel} from "../../../view-models/user/login.view-model";
export class LoginController implements ng.IController{
//#region Properties
public email: string = '123456';
//#endregion
//#region Constructor
/*
* Initialize controller with injectors.
* */
public constructor(public $scope: ILoginScope){
this.$scope.loginModel = new LoginViewModel();
this.$scope.loginModel.email = '123';
this.$scope.hasEmail = this.hasEmail;
}
//#endregion
//#region Methods
//#endregion
//#region Events
public clickLogin(): void {
console.log('Hello world');
}
public hasEmail(): boolean{
let loginModel = this.$scope.loginModel;
if (!loginModel || !loginModel.email)
return false;
return true;
}
//#endregion
}
login.scope.ts
import {LoginViewModel} from "../../../view-models/user/login.view-model";
export interface ILoginScope extends ng.IScope {
//#region Properties
/*
* Login information.
* */
loginModel: LoginViewModel;
//#endregion
//#region Methods
/*
* Called when login is clicked.
* */
clickLogin: Function;
hasEmail: () => boolean;
//#endregion
}
login.module.ts
import {StateProvider} from "#uirouter/angularjs";
import {UrlStatesConstant} from "../../../constants/url-states.constant";
import {module} from 'angular';
export class LoginModule {
//#region Constructors
public constructor(private $stateProvider: StateProvider) {
$stateProvider
.state(UrlStatesConstant.LoginStateName, {
url: UrlStatesConstant.LoginStateUrl,
controller: 'loginController',
templateProvider: ['$q', ($q) => {
// We have to inject $q service manually due to some reasons that ng-annotate cannot add $q service in production mode.
return $q((resolve) => {
// lazy load the view
require.ensure([], () => resolve(require('./login.html')));
});
}],
parent: UrlStatesConstant.AuthorizeLayoutName,
resolve: {
/*
* Load login controller.
* */
loadLoginController: ($q, $ocLazyLoad) => {
return $q((resolve) => {
require.ensure([], (require) => {
// load only controller module
let ngModule = module('account.login', []);
// Import controller file.
const {LoginController} = require("./login.controller");
ngModule.controller('loginController', LoginController);
$ocLazyLoad.load({name: ngModule.name});
resolve(ngModule.controller);
})
});
}
}
});
}
//#endregion
}
And this is how I registered my typescript angularjs module to existing es5 angularjs application:
module.exports = (ngModule) => {
// Load routes.
let login = require('./login');
ngModule.config(($stateProvider) => login.LoginModule($stateProvider));
};
When I run the application and access to login page
There is an error thrown in my developer console:
TypeError: Cannot read property 'loginModel' of undefined at ChildScope.LoginController.hasEmail (login.controller.ts:26)
This is the generated source map:
From what the error message says, the this.$scope at line 26 cannot be recognized even it has been declared as public $scope: ILoginScope in the constructor.
What wrong am I doing ?
Thank you,
This is the error I'm seeing when trying to run my unit test ..
Expected undefined to be defined.
TypeError: undefined is not an object (evaluating '$rootScope.$digest')
Module 'ThirdPartyModule' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
Any ideas how to mock the testService so that I can still compile my component?
test.component.spec.ts
import { TestModule } from '../index';
describe('Component: testComponent', () => {
let $rootScope: angular.IScope;
let element: angular.IAugmentedJQuery;
beforeEach(() => {
angular.mock.module('ui.router');
angular.mock.module(TestModule.name);
});
beforeEach(inject((
_$rootScope_: angular.IScope,
$compile: angular.ICompileService,
_$state_: angular.ui.IStateService) => {
$rootScope = _$rootScope_;
element = angular.element('<test></test>');
element = $compile(element)($rootScope);
}));
it('should verify component compiled and rendered template', () => {
expect(element).toBeDefined();
$rootScope.$digest();
let link = element.find('a');
expect(link.text()).toContain('Click this link!');
});
});
test.module.ts
import { TestComponent } from './test';
export let TestModule: ng.IModule = angular.module(
'test', // my module name
['ui.router', 'ThirdPartyModule']) // dependencies, ThirdPartyModule contains testService
.component('test', new TestComponent());
test.component.ts
import { TestComponentController } from './test.component.controller';
export class TestComponent implements ng.IComponentOptions {
public template: string = '<a ng-if="ctrl.serviceReturned">Click this link!</a>';
public controller: Function = TestComponentController;
public controllerAs: string = 'ctrl';
constructor() {}
}
test.component.controller.ts
export class TestComponentController {
public serviceReturned: boolean = false;
constructor(private testService: any) {
if (this.testService.isDone()) {
this.serviceReturned = true;
}
}
}
TestComponentController.$inject = ['testService'];
Don't you need to add 'ThirdPartyModule' in an angular.mock.module?
I'm using ES6 codestyle and implement a sample module like this:
import angular from 'angular';
import { IndicatorSelectorComponent } from './indicatorSelector.component';
import './indicatorSelector.css';
export const IndicatorSelectorModule = angular
.module('indicatorSelector', [])
.component('indicatorSelector', IndicatorSelectorComponent)
.name;
with this code for the component:
import templateUrl from './indicatorSelector.html';
export const IndicatorSelectorComponent = {
templateUrl,
controller: class IndicatorSelectorComponent {
contructor($http) {
'ngInject';
this.$http = $http;
}
$onInit() {
console.log(this.$http);
}
}
}
The console.log(this.$http) is returning undefined. I was suspecting that the problem was with my webpack code and ng-annotate, but I can see that the generated bunddle is including the annotation:
var IndicatorSelectorComponent = exports.IndicatorSelectorComponent = {
templateUrl: _indicatorSelector2.default,
controller: function () {
function IndicatorSelectorComponent() {
_classCallCheck(this, IndicatorSelectorComponent);
}
_createClass(IndicatorSelectorComponent, [{
key: 'contructor',
value: ["$http", function contructor($http) { // <==== HERE!
'ngInject';
this.$http = $http;
}]
}, {
key: '$onInit',
value: function $onInit() {
console.log(this.$http);
}
}]);
return IndicatorSelectorComponent;
}()
};
But still it is not working. I tried to inspect on the module code a console.log(IndicatorSelectorComponent);
I can see that $inject is empty. I don't know what else to do. I appreciate any help on this.
Object
controller :function IndicatorSelectorComponent()
$inject :Array[0]
arguments : (...)
caller : (...)
length : 0
name : "IndicatorSelectorComponent"
prototype : Object
__proto__ : function ()
[[FunctionLocation]] : indicatorSelector.component.js:5
[[Scopes]] : Scopes[3]
templateUrl : "C:/Projetos/IndicadoresPCM/client/src/app/components/goals/indicatorSelector/indicatorSelector.html"
__proto__ : Object
If using this way it will works.
import templateUrl from './indicatorSelector.html';
export const IndicatorSelectorComponent = {
templateUrl,
controller: function ($http) {
"ngInject";
this.$onInit = function () {
console.log($http);
};
}
}
But still, would be very interesting to make it work with the class expression...
I'm trying to write a test for this directive:
export default angular.module('app.page.section.accountBalance', [])
.directive('accountBalance', accountBalanceConfig);
function accountBalanceConfig() {
return {
restrict: 'E',
replace: true,
scope: {
data: '='
},
template: require('./account-balance.tpl.html'),
controller: accountBalanceController,
controllerAs: 'accountBalance'
}
}
class accountBalanceController {
constructor($scope, domFactory, userService, $filter) {
// controller logic
}
}
As you can see, the directive's controller depend on domFactory which is a custom service I created that compiles the DOM:
export class DomGenerator {
/**
* #constructor
* inject dependencies
*/
constructor() {
'ngInject';
}
// service logic
}
This is the test:
import { DomGenerator } from '../../shared/services/dom-genereator/dom-generator.srv';
import compile from '../compile/compile.drv';
import module from './account-balance.drv';
describe('account balance', () => {
var $rootScope, $compile, $location, $window, $document, userService, domFactory, $provide;
beforeEach(() => {
domFactory = new DomGenerator();
userService = new UserData();
});
beforeEach(angular.mock.module(compile.name));
beforeEach(angular.mock.module(module.name));
beforeEach(inject(($injector) => {
$rootScope = $injector.get('$rootScope');
$compile = $injector.get('$compile');
}));
it('renders account balance widget headline', () => {
var element = $compile(`<account-balance data="{headline: {title: 'account balance'}}"></account-balance>`)($rootScope);
$rootScope.$digest();
expect(element.html()).to.contain('account balance');
});
});
class UserData {
constructor() {
}
}
This is the error I get:
Error: [$injector:unpr] Unknown provider: domFactoryProvider <- domFactory
http://errors.angularjs.org/1.5.8/$injector/unpr?p0=domFactoryProvider%20%3C-%20domFactory
at webpack:///~/angular/angular.js:68:0 <- spec.bundle.js:16824:13
at webpack:///~/angular/angular.js:4511:0 <- spec.bundle.js:21267:20
at Object.getService [as get] (webpack:///~/angular/angular.js:4664:0 <- spec.bundle.js:21420:40)
at webpack:///~/angular/angular.js:4516:0 <- spec.bundle.js:21272:46
at getService (webpack:///~/angular/angular.js:4664:0 <- spec.bundle.js:21420:40)
at injectionArgs (webpack:///~/angular/angular.js:4688:0 <- spec.bundle.js:21444:59)
at Object.invoke (webpack:///~/angular/angular.js:4710:0 <- spec.bundle.js:21466:19)
at $controllerInit (webpack:///~/angular/angular.js:10354:0 <- spec.bundle.js:27110:35)
at nodeLinkFn (webpack:///~/angular/angular.js:9263:0 <- spec.bundle.js:26019:35)
at compositeLinkFn (webpack:///~/angular/angular.js:8620:0 <- spec.bundle.js:25376:14)
Which means that I didn't inject the service to the directive's controller.
What am I doing wrong?
Isn't
beforeEach(() => {
domFactory = new DomGenerator();
userService = new UserData();
});`
enough?
You should do something like that , You should mock the service (Use sinon).
import accountBalanceController from 'path for account balance controller'
// import accountBalanceConfig
// import html template
import { DomGenerator } from '../../shared/services/dom-genereator/dom-generator.srv';
import compile from '../compile/compile.drv';
import module from './account-balance.drv';
import sinon from 'sinon'
describe('account balance', () => {
let $rootScope, makeController,DomGeneratorFactory,$scope,$filter,userService;
beforeEach(inject((_$rootScope_,_$scope_,_$filter_) => {
$rootScope = _$rootScope_;
$scope = _$scope_;
$filter = _$filter_;
DomGeneratorFactory = sinon.createStubInstance(DomGenerator);
// Create mock to user Service
makeController = () => {
return new accountBalanceController($scope, DomGeneratorFactory, userService, $filter);
};
}));
describe('Controller', () => {
// controller specs
it('has a name property [REMOVE]', () => { // erase if removing this.name from the controller
let controller = makeController();
expect(controller).to.have.property('name');
});
it('includes the intended template', () => {
expect(accountBalanceConfig.template).to.equal(htmlTemplate);
});
});
});