In the .run section of the main module of my application, I have an event handler for the $locationChangeStart event. I want to use this in order to confirm discarding unsaved changes. The problem is that I need a reference to the $scope in order to perform these checks.
I tried adding that reference as I added the one for the $rootScope, but I get an error Uncaught Error: Unknown provider: $scopeProvider <- $scope.
How should I proceed to this? I am open for alternatives.
.run(['$rootScope', '$location', function ($rootScope, $location) {
$rootScope.$on("$locationChangeStart", function (event, next, current) {
if ($scope.unsavedChanges && !confirm('Unsaved changes') {
event.preventDefault();
}
});
}
You can only inject instances (not Providers) into the run blocks. This is from the doc of module.
angular.module('myModule', []).
run(function(injectables) { // instance-injector
// This is an example of a run block.
// You can have as many of these as you want.
// You can only inject instances (not Providers)
// into the run blocks
});
So you won't be able to inject $scopeProvider.
You could inject $scope to your function like;
.run(['$rootScope', '$location', '$scope', function ($rootScope, $location, $scope)
Related
I am using angular 1.5.6 and my application is composed of several modules, each module has his own view and controller. My question is how can I watch $rootScope on each of those controllers. I need to watch $rootScope on all controllers because I have a global variable on $rootScope.account and when user refresh the page $rootScope.account is cleared and all my application crash. I would like to watch $rootScope.account on all my modules, so when a user refresh I can handle the refresh and redirect to login page. I thought maybe of a service that I would inject on all each of my modules?
You can do it in two simple ways.
First: include $rootScope into your controller:
app.controller('MyCtrl', [
'$rootScope',
'$scope',
function($rootScope, $scope) {
$rootScope.$watch('myvar', function(newValue, oldValue) {
doSomething();
}
//...
}
]);
Second. If you don't have variables with same names in the other scopes then you can avoid using $rootScope and use just $scope. This will work because every $scope is inherited from $rootScope.
app.controller('MyCtrl', [
'$scope',
function($scope) {
$scope.$watch('myvar', function(newValue, oldValue) {
doSomething();
}
//...
}
]);
I am building a Website with AngularJS. I ran into problems with translations, got a hacky more or less working solution to it but i am thinking that it can be done a lot better.
I have a main controller which loads translations from database.
$http.get($rootScope.ApiUrl + '/?a=sprache&lang=' + $rootScope.lang).success(function (data) {
$scope.spr = data;
$rootScope.translations = data;
$rootScope.updateTranslations();
});
data is an array formatted like this:
{key: "translation",…}
Further on I have a controller for each state. I hoped it could do something like this:
app.controller('InventoryCtrl', [
'$scope',
'$http',
'$location',
'$state',
'$stateParams',
'$rootScope',
'$uibModalStack',
function ($scope, $http, $location, $state, $stateParams, $rootScope, $uibModalStack) {
$scope.title = $rootscope.translations.myTranslatedTitleForThisState
]);
Obviously this does not work as the get-request is not finished before this Code gets called and therefore $rootscope.translations variable is not set.
Instead I wrote the following. The updateTranslations() function is called from the loadTransition() function in MainController (above) after successfully finishing the get-request.
app.controller('InventoryCtrl', [
'$scope',
'$http',
'$location',
'$state',
'$stateParams',
'$rootScope',
'$uibModalStack',
function ($scope, $http, $location, $state, $stateParams, $rootScope, $uibModalStack) {
$rootScope.updateTranslations = function() {
$rootScope.setMetaTags($rootScope.translations.inventory_title, $rootScope.translations.inventory_description);
$rootScope.updateTranslations();
}
}
]);
I am pretty sure this can be done a lot better. Any ideas?
Maybe this answer is not a direct answer to your problem, but a possible new approach...
We have been using ui-router quite alot in recent Angular projects.
ui-router is ideal for defining routes within the application. More about that subject here.
A nice feature is the state resolvement. Meaning that the state will only resolve when the resolvement promise is resolved.
By example
angular.module('testApp').config(function($stateProvider) {
$stateProvider.state('', {
url: '/',
templateUrl: 'main.html',
resolve: {
labels: function(labelService) {
return labelService.loadLabels();
}
}
});
});
In the above example, the state / will resolve after the $http.get promise behind the loadService.loadLabels() is resolved. Meaning the template will be loaded, after all the resolvements are resolved.
In this case, your view will be loaded - after that all your labels are loaded and accessible by the controller (later the view).
A nice thing is that nested state definitions are perfectly possible.
Meaning, that you could have 1 root state, and many child state of the root state.
In this ng-book JSBin, does $scope.$watch() resolves to $rootScope.$watch() due to prototypal inheritance.
Can we inject $rootScope explicitly inside the controller so that $scope would be same as $rootScope inside the controller, without going through prototypal inheritance?
Replicating code here for reference:
// open this example and type person.name into the test field
angular.module('myApp', [])
.controller('MyController',
['$scope', '$parse', function($scope, $parse) {
$scope.person = {
name: "Ari Lerner"
};
$scope.$watch('expr', function(newVal, oldVal, scope) {
if (newVal !== oldVal) {
// Let's set up our parseFun with the expression
var parseFun = $parse(newVal);
// Get the value of the parsed expression, set it on the scope for output
scope.parsedExpr = parseFun(scope);
}
});
}]);
Just inject it the same way as $scope or $parse, anything defined on $rootScope will be then accessible inside your controller.
app.controller('MyController', ['$scope', '$parse', '$rootScope',
function($scope, $parse, $rootScope) {
$rootScope.foo();
console.log($rootScope.bar);
}
]);
etc.
If you intend to use the rootScope so badly, it has a provider just as scope does. Including '$rootScope' into your controller as you do with the '$scope' does the trick.
There's also the $parent attribute of the $scope which could come in handy, but IMO it tends to make code less maintainable if abused. In particular when multiple scopes are nested, as you need to traverse the whole hierarchy.
I have added some logic for authentication in my angular app and the initial the call to the service wrapping the webapi is executed in the app.run function, like this:
myApp.run(function ($rootScope, $location, myService) {
myService.getCurrentUser().then(function (promise) {
//reroute to either access denied or the start page when access is verified.
});
// register listener to watch route changes
$rootScope.$on("$routeChangeStart", function (event, next, current) {
//check access
});
});
After this all my unit tests broke, since I don't know how to inject the mocked version of myService before creating the app module. I've factored out the service and the mocked service in a seperate module so I can create it before creating the actual app. Like this:
angular.mock.module('ServiceModule');
angular.mock.module('EArkivApp', function ($provide, myMockedService) {
$provide.value('myService', myMockedService);
});
This is not working however, it complains that "myMockedService" (which is part of the ServiceModule) is an unkown provider. Do you have any good suggestions of how I should solve this?
Could you provide us for a jsfiddle?
Meanwhile, if you are using Karma then you can use something like this:
/*global describe, beforeEach, module, inject, it, expect*/
describe('Controller: MyCtrl', function () {
'use strict';
// load the controller's module
beforeEach(module('myApp'));
var MyCtrl,
scope,
modalInstance;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope, myMockedService) {
scope = $rootScope.$new();
modalInstance = _$modal_.open({
templateUrl: 'views/my-template.html'
});
MyCtrl = $controller('MyCtrl', {
$scope: scope,
$myMockedService: myMockedService
});
}));
it('should be true', function () {
expect(true).toBe(true);
});
});
I learned this by using Yeoman scaffolding, by the way :)
I have a controller that is attached to a route. The controller constantly polls the server using $timeout. When the route changes, I need to stop polling, and start it again when the route changes back.
Please help.
Here is my code:
(angular
.module('app.controllers', ['ng', 'ngResource'])
.controller('myContr', [
/******/ '$scope', '$resource', '$timeout',
function ($scope, $resource, $timeout) {
function update() {
$resource('my-service').get({}, function (d) {
// ...use data...
$timeout(update, UPDATE_INTERVAL);
});
};
update();
}
])
);
Save the return value (a promise) from $timeout (to a $scope property).
Register a $destroy event handler on your scope.
Call cancel() on that $timeout promise when the event handler triggers.
When the route changes back, the controller will get recreated, so your existing code should start up the polling again.