I am trying to create a service to use throughout my Angular app that pulls in data from a .json file using $http. This is what the factory looks like:
var trooNewsServices = angular.module('trooNewsServices', []);
trooNewsServices.factory('Articles', ['$http',
function($http){
$http.get('resources/articles.json').success(function(data) {
return data;
});
}]);
I passed in the trooNewsServices dependency into my module declaration. Any controller that I try to pass in my new Articles service, I get a
"Could not instantiate controller HomeController"
error in the console. Not sure what I am missing/what is wrong with this code. Should I be using $resource instead of $http?
Here is how I am passing the 'trooNewsServices' into my main module:
var TrooNews = angular
.module('TrooNews', ['ngMaterial', 'ngNewRouter', 'trooNewsServices'])
.config(function($mdThemingProvider) {
$mdThemingProvider
.theme('default')
.primaryPalette('indigo')
.accentPalette('pink');
})
.config(function($locationProvider) {
$locationProvider.html5Mode({
enabled: false,
requireBase: false
});
});
Here is how I try to inject 'Articles' into one of my controllers:
TrooNews.controller('HomeController', ['Articles',
function(Articles) {
this.name = 'Troo News';
this.articles = Articles.query();
}]);
And here is how I set up routing in my 'AppController':
TrooNews.controller('AppController', function($router, $mdSidenav, $mdToast, $parse, $http) {
$router.config([{
path: '/',
component: 'home'
}, {
path: '/home',
component: 'home'
}, {
path: '/about',
component: 'about'
}, {
path: '/settings',
component: 'settings'
}, {
path: '/article/:id',
component: 'article'
}]);
this.toggleSidenav = function(menuId) {
$mdSidenav(menuId).toggle();
};
this.navigateTo = function(link) {
var parts = link.match(/^(.+?)(?:\((.*)\))?$/);
var url;
if (parts[2]) {
url = '.' + $router.generate(parts[1], $parse(parts[2])());
} else {
url = '.' + $router.generate(parts[1]);
}
$mdToast.show($mdToast.simple().content('Navigate To: ' + url).position('bottom right'));
$router.navigate(url);
this.toggleSidenav('left');
};
});
Inside your HomeController, you are executing this.articles = Articles.query();, but your Articles service doesn't define any query function.
Instead, your service is just immediately executing an HTTP GET request upon creation. Not sure why this would lead to your error, but it is a red flag.
Try changing your Articles service to the following:
trooNewsServices.factory('Articles', ['$http',
function Articles($http){
this.query = function() {
return $http.get('resources/articles.json')
.then(function(response) { return response.data; });
};
}]);
I was experiencing the same error message under different conditions. In my case, it was because I was referencing $scope in my dependencies (old habit that I'm trying to break). In my case, I wasn't using $scope and could easily remove the reference. That cleared up my error. Check your code for $scope references and see if that fixes it.
https://github.com/angular/router/issues/313
and
How can we watch expressions inside a controller in angular 1.4 using angular-new-router
Related
I have a webapp that I would like to have some consistent themes across pages. To do so, I have attempted to modify the routeProvider provider to allow for utilization of such a template. As far as I can tell, the ways to do this are to define the provider, configure it, include it in the controller scope, and then inject it into the controller. I believe I have done this. Here are the relevant code blocks:
var webApp = angular.module('myApp.module', ['ngRoute','ngCookies','angular-jwt']);
webApp.provider('extendedRouteProvider', function($routeProvider) {
this.$get = function() {
return {
when: function(path,route) {
if (route.layOutTemplateUrl) {
route.resolve = route.resolve || {};
route.resolve.layOutTemplateUrl = function (){
return route.layOutTemplateUrl;
};
}
$routeProvider.when(path,route);
}
}
}
});
webApp.config(function(extendedRouteProvider) {
extendedRouteProvider
.when('/', {
templateUrl: 'webapp_loginScreen_UIDesign.html',
controller: 'loginScreenController'
})
//Could add the following to after the .html bellow in the file if we want screen to load at anchor point specified in the HTML file: #!/mainPage...
//This would also require adding it into the $location.path within the screenController function if so
.when('/mainPage', {
templateUrl: '/UI Design/Main Page/HTML/webapp_mainPageScreen_UIDesign.html',
controller: 'mainPageScreenController',
layOutTemplateUrl: '/UI Design/webapp_mainLayoutTemplate.html'
})
.otherwise({
redirectTo: '/'
});
});
function loginScreenController($http, $cookies, $location, $route,extendedRouteProvider) {
var vm = this;
var layOutTemplateUrl = $route.current.layOutTemplateUrl;
// do stuff
const serverURL = "URL";
$location.path('/mainPage');
}
webApp.controller('loginScreenController',loginScreenController);
loginScreenController.$inject = ['$http','$cookies','$location','$route','extendedRouteProvider'];
However, this code, when running via VSCode's live server, gives me an $injector:unpr error. I don't understand why this is happening. Note that I 'enter' the webapp through an HTML page which does not itself utilize the layout template, but $location.path('/main') should be sending the user to the mainpage, which does utilize the layout template.
I've been trying to make ocLazyLoad work on my project, but I keep getting this Angular error
Error: $controller:ctrlreg
A controller with this name is not registered
The controller with the name 'eventoCtrl' is not registered.
NOTICE: I'm also using ui-router to define my app's states.
NOTICE #2: Any suggestion on other methods of using routes or lazy loads will also be apreciated
app.js
(function(){
angular
.module('kulchr', [
'ui.router',
'oc.lazyLoad'
]);})();
config.js
angular
.module('kulchr')
.config(function ($stateProvider, $urlRouterProvider, $ocLazyLoadProvider) {
$stateProvider
.state('eventos', {
url: '/eventos',
views: {
'top-menu-nav': {
templateUrl: 'templates/top-menu-nav.html',
},
'main': {
templateUrl: 'templates/eventos.html',
controller: 'eventoCtrl as listaEvento',
resolve: {
eventoCtrl: function ($ocLazyLoad) {
return $ocLazyLoad.load(
{
files: ['controller/listaEventoController.js'
,'service/eventoService.js']
});
}
}
}
}
});
$urlRouterProvider.otherwise('/');
});
controller
(function() {
'use strict';
angular
.module('kulchr')
.controller('eventoCtrl', ListaEventoController);
ListaEventoController.$inject = ['servicoEvento'];
function ListaEventoController(evento){
var vm = this;
var promise = evento.buscaDados();
promise.then (function(response){
vm.eventos = response.data;
})
}
})();
service
(function() {
'use strict';
angular
.module('kulchr')
.service('servicoEvento', Evento);
function Evento($http, $q) {
var d = $q.defer();
var self = this;
$http.get('/mockup-data/eventos.json')
.then(function (response){
d.resolve(response);
}, function(reason) {
console.log("Motivo: " + reason.data +
"Status: " + reason.status +
" - " + reason.statusText);
return $q.reject(reason);
});
self.buscaDados = function(){
return d.promise;
}
}
})();
What am I missing here? I've reached the ui-router documentation but it just made more confused
BTW, Everything is working fine when adding the files directly on the index.html file using .
Currently what happening is, your listaEventoController is haven't goad loaded when named view is rendering. The reason being is resolve object has been used in wrong place. It doesn't work on named view level. It should taken out and keep it after url(flatten property) inside state definition object.
By taking resolve out oc-lazyLoad module will take care of downloading listaEventoController & eventoService file from server and would make download service register inside angular context and available to use inside angular application.
Code
$stateProvider
.state('eventos', {
url: '/eventos',
views: {
'top-menu-nav': {
templateUrl: 'templates/top-menu-nav.html',
},
'main': {
templateUrl: 'templates/eventos.html',
controller: 'eventoCtrl as listaEvento'
}
},
//moved resolve out from "named view" object.
resolve: {
eventoCtrl: function ($ocLazyLoad) {
return $ocLazyLoad.load({
files: [
'controller/listaEventoController.js',
'service/eventoService.js'
]
}
);
}
}
})
I am facing a issue where angular is not passing resolve parameters to controller.
route.js
var routingModule = require('../'); -- routing module is already defined, just injecting new routes
routingModule.config(myconfig);
myconfig.$inject = ['$stateProvider'];
function myconfig($stateProvider) {
$stateProvider
.state('home.clone', {
abstract: true,
url: '/clone',
template: '<ui-view/>'
})
.state('home.clone.dummy', {
url: '/dummy?channelId',
templateUrl: '/templates/clone_dummy.html',
resolve:{
simpleObj: function(){ ---> it is not passing to controller
return {value: 'simple!'};
}
},
controller: 'makingCloneDummyCtrl', ---> it is defined in another module
controllerAs: 'rv',
date: {
title: "Making Clone",
}
})
Here is the associated Controller below which is defined in another module which expects simpleObj
controller.js
angular.module('somemodule', [])
.controller('makingCloneDummyCtrl', [
'$scope', 'simpleObj',
function ($scope, simpleObj)
{
var self = this;
console.debug(simpleObj); ---> It prints function
console.debug(simpleObj.value); ---> undefined
}
console.debug(simpleObj) prints below function
function Resource(value) {
shallowClearAndCopy(value || {}, this);
}
while
console.debug(simpleObj.value) prints undefined
I am not understanding what I am missing.
May be controller is present in another module somemodule than routing module. Is this the reason simpleObj is not getting passed to controller ?
Any help would be appreciated.
Thanks
As mentioned in the comments you missed a comma in you injection array.
Here is a working plunker with a simplified setup.
Just add to comma and you should be good to go.
angular.module('somemodule', [])
.controller('makingCloneDummyCtrl', [
'$scope', // Comma added here
'simpleObj',
function($scope, simpleObj) {
var self = this;
console.debug(simpleObj);
console.debug(simpleObj.value);
}
]);
In my controller, I have $state.go('purchase');, but am having the following error :
Error: Could not resolve'purchase'from state ''.
But initially, I defined url '' to '/home' in my file states.js, which goes to state 'home'. Why is my state transition not happening? Please let me know where I am wrong.
states.js
/**
* Dashboard routes.
*/
define(['./templates'], function(templates) {
var mod = angular.module("item.states", ["ui.router"]);
console.log("Inside item home states");
var StateProvider = function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.when('', '/home');
$stateProvider
.state("item", {
url: '/home',
template: templates.home,
controller : 'controller.item.home.main'
})
.state("purchase", {
url: '/purchase',
template: templates.purchase,
controller : 'controller.item.home.main'
});
}
StateProvider.$inject = ["$stateProvider", "$urlRouterProvider"];
mod.config(StateProvider);
return mod;
});
controller.js
function _purchase(_event){
console.log("Clicked purchase button");
$state.go('purchase');
}
main.js
define(['./controllers','./states'], function(controllers,states) {
var mod = angular.module("item.home", ['common.services.ItemService',
'common.filters.format']);
mod.controller('controller.item.home.main', controllers.main);
console.log("Inside item home main");
return mod;
});
my server side jade
extends ../layouts/default
block content
div(ng-controller="controller.item.home.main as main" ng-init="item= #{JSON.stringify(item)}")
.masterhead.segment
.ui.page.grid.stackable
.row.ui.basic.segment
.four.wide.column.ui.center.aligned
img.ui.image.rounded(src="/images/itemicon.png")
h2() !{item.name} - !{item.activeseason.name}
.twelve.wide.column
.ui.small.orange.button(id="purchaseBtn" ng-click="purchase($event)”)
| Purchase
div.ui.page.grid(ui-view)
Thanks.
use transition to
$state.transitionTo('purchase')
and change syntax error in yor code
url: '/purchase,
to
url: '/purchase',
I'm not sure the syntax you are using and why defining your routes inside a template, but I'd recommend another way to do it, create a file on the 'module folder' or your root folder and define your routes in a more 'general' way like the following:
'use strict';
//Setting up route
angular.module('mainModule').config(['$stateProvider',
function($stateProvider) {
// main module state routing
$stateProvider.
state('home', {
url: '/home',
templateUrl: 'controller.item.home.main', //I'd recommend to use plain html files, instead something rendered in the server
controller:'HomeController'
}).
state('purchase', {
url: '/purchase',
templateUrl: 'templates.purchase',
controller: 'purchaseController'
});
}
]);
I guess in your home controller:
angular.module('mainModule').controller('HomeController', ['$scope','$state',
function($scope,$state) {
$scope.onclick = function(){
$state.go('purchase');
};
}]);
on the other side your states are not related to your server side at all.
The error was because,
In my main.js - I missed to include the module 'item.states'
define(['./controllers','./states'], function(controllers,states) {
var mod = angular.module("item.home", ['common.services.ItemService',
'common.filters.format','item.states']);
mod.controller('controller.item.home.main', controllers.main);
console.log("Inside item home main");
return mod;
});
I'm having some trouble unit testing the router in my application, which is built on the Angular ui router. What I want to test is whether state transitions change the URL appropriately (there will be more complicated tests later, but this is where I'm starting.)
Here is the relevant portion of my application code:
angular.module('scrapbooks')
.config( function($stateProvider){
$stateProvider.state('splash', {
url: "/splash/",
templateUrl: "/app/splash/splash.tpl.html",
controller: "SplashCtrl"
})
})
And the testing code:
it("should change to the splash state", function(){
inject(function($state, $rootScope){
$rootScope.$apply(function(){
$state.go("splash");
});
expect($state.current.name).to.equal("splash");
})
})
Similar questions on Stackoverflow (and the official ui router test code) suggest wrapping the $state.go call in $apply should be enough. But I've done that and the state is still not updating. $state.current.name remains empty.
Been having this issue as well, and finally figured out how to do it.
Here is a sample state:
angular.module('myApp', ['ui.router'])
.config(['$stateProvider', function($stateProvider) {
$stateProvider.state('myState', {
url: '/state/:id',
templateUrl: 'template.html',
controller: 'MyCtrl',
resolve: {
data: ['myService', function(service) {
return service.findAll();
}]
}
});
}]);
The unit test below will cover testing the URL w/ params, and executing the resolves which inject its own dependencies:
describe('myApp/myState', function() {
var $rootScope, $state, $injector, myServiceMock, state = 'myState';
beforeEach(function() {
module('myApp', function($provide) {
$provide.value('myService', myServiceMock = {});
});
inject(function(_$rootScope_, _$state_, _$injector_, $templateCache) {
$rootScope = _$rootScope_;
$state = _$state_;
$injector = _$injector_;
// We need add the template entry into the templateCache if we ever
// specify a templateUrl
$templateCache.put('template.html', '');
})
});
it('should respond to URL', function() {
expect($state.href(state, { id: 1 })).toEqual('#/state/1');
});
it('should resolve data', function() {
myServiceMock.findAll = jasmine.createSpy('findAll').and.returnValue('findAll');
// earlier than jasmine 2.0, replace "and.returnValue" with "andReturn"
$state.go(state);
$rootScope.$digest();
expect($state.current.name).toBe(state);
// Call invoke to inject dependencies and run function
expect($injector.invoke($state.current.resolve.data)).toBe('findAll');
});
});
If you want to check only the current state's name it's easier to use $state.transitionTo('splash')
it('should transition to splash', inject(function($state,$rootScope){
$state.transitionTo('splash');
$rootScope.$apply();
expect($state.current.name).toBe('splash');
}));
I realize this is slightly off topic, but I came here from Google looking for a simple way to test a route's template, controller, and URL.
$state.get('stateName')
will give you
{
url: '...',
templateUrl: '...',
controller: '...',
name: 'stateName',
resolve: {
foo: function () {}
}
}
in your tests.
So your tests could look something like this:
var state;
beforeEach(inject(function ($state) {
state = $state.get('otherwise');
}));
it('matches a wild card', function () {
expect(state.url).toEqual('/path/to/page');
});
it('renders the 404 page', function () {
expect(state.templateUrl).toEqual('views/errors/404.html');
});
it('uses the right controller', function () {
expect(state.controller).toEqual(...);
});
it('resolves the right thing', function () {
expect(state.resolve.foo()).toEqual(...);
});
// etc
For a state that without resolve:
// TEST DESCRIPTION
describe('UI ROUTER', function () {
// TEST SPECIFICATION
it('should go to the state', function () {
module('app');
inject(function ($rootScope, $state, $templateCache) {
// When you transition to the state with $state, UI-ROUTER
// will look for the 'templateUrl' mentioned in the state's
// configuration, so supply those templateUrls with templateCache
$templateCache.put('app/templates/someTemplate.html');
// Now GO to the state.
$state.go('someState');
// Run a digest cycle to update the $state object
// you can also run it with $state.$digest();
$state.$apply();
// TEST EXPECTATION
expect($state.current.name)
.toBe('someState');
});
});
});
NOTE:-
For a nested state we may need to supply more than one template. For ex. if we have a nested state core.public.home and each state, i.e. core, core.public and core.public.home has a templateUrl defined, we will have to add $templateCache.put() for each state's templateUrl key:-
$templateCache.put('app/templates/template1.html');
$templateCache.put('app/templates/template2.html');
$templateCache.put('app/templates/template3.html');
Hope this helps. Good Luck.
You could use $state.$current.locals.globals to access all resolved values (see the code snippet).
// Given
$httpBackend
.expectGET('/api/users/123')
.respond(200, { id: 1, email: 'test#email.com');
// When
$state.go('users.show', { id: 123 });
$httpBackend.flush();
// Then
var user = $state.$current.locals.globals['user']
expact(user).to.have.property('id', 123);
expact(user).to.have.property('email', 'test#email.com');
In ui-router 1.0.0 (currently beta) you could try to invoke $resolve.resolve(state, locals).then((resolved) => {}) in the specs. For instance https://github.com/lucassus/angular-webpack-seed/blob/9a5af271439fd447510c0e3e87332959cb0eda0f/src/app/contacts/one/one.state.spec.js#L29
If you're not interested in anything in the content of the template, you could just mock $templateCache:
beforeEach(inject(function($templateCache) {
spyOn($templateCache,'get').and.returnValue('<div></div>');
}