I have a situation where I need to have a piece of code execute only once at the start of the angular app. My app searches for a beacon using the cordova-plugin and if it finds one, the app is redirected to another state otherwise it stays on the home/splash state.
Currently I have this code running in the "home" controller and it is working but I was hoping for a more elegant solution. I am controlling the state change using a simple if/else statement and a variable on the $scope.
Here's my controller:
homeCtrl.$inject = ['$scope', '$state', '$rootScope', '$ionicPlatform', '$cordovaBeacon'];
function homeCtrl($scope, $state, $rootScope, $ionicPlatform, $cordovaBeacon) {
$scope.skip = false;
$ionicPlatform.ready(function() {
$cordovaBeacon.requestWhenInUseAuthorization();
$rootScope.$on("$cordovaBeacon:didRangeBeaconsInRegion", function(event, pluginResult) {
if (!$scope.skip) {
$scope.skip = true;
$state.go('app.beacon');
}
});
$cordovaBeacon.startRangingBeaconsInRegion($cordovaBeacon.createBeaconRegion("abcdefg", "8484848484848484848"));
});
}
Thanks in advance for help.
Use Run
app.run(function('$scope', '$state', '$rootScope', '$ionicPlatform', '$cordovaBeacon'){
function homeCtrl($scope, $state, $rootScope, $ionicPlatform, $cordovaBeacon) {
$scope.skip = false;
$ionicPlatform.ready(function() {
$cordovaBeacon.requestWhenInUseAuthorization();
$rootScope.$on("$cordovaBeacon:didRangeBeaconsInRegion", function(event, pluginResult) {
if (!$scope.skip) {
$scope.skip = true;
$state.go('app.beacon');
}
});
$cordovaBeacon.startRangingBeaconsInRegion($cordovaBeacon.createBeaconRegion("abcdefg", "8484848484848484848"));
});
Run will be executed first before any controller is loaded or any service is injected.
ngInit is something that you need in your case
Whereever you're defining your app or controller you can point the method that you want to execute at the beginning of your app only once in ng-init attribute.
<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body>
<div ng-app="" ng-init="myText='Hello World!'">
<h1>{{myText}}</h1>
<p>The ng-init directive has created an AngularJS variable, which you can use in the application.</p>
</div>
</body>
</html>
source
However, angular docs strictly states that you should not use ngInit for initializing purposes. (see docs)
This directive can be abused to add unnecessary amounts of logic into
your templates. There are only a few appropriate uses of ngInit, such
as for aliasing special properties of ngRepeat, as seen in the demo
below; and for injecting data via server side scripting. Besides these
few cases, you should use controllers rather than ngInit to initialize
values on a scope.
Initializing values is something that should be done from controller. (see docs)
Use controllers to:
Set up the initial state of the $scope object. Add behavior to the
$scope object.
Related
I am trying to load a template file in an AngularStrap popover, however I am having trouble using $templateCache. I seem to be a step further back than the other SO questions, hence this seemingly double one.
Following the API docs I added a <script type="text/ng-template" id="popoverTemplate.html"></script> right before the closing </body> tag. When I use <div ng-include="'popoverTemplate.html'"></div> on my page, I get nothing. If I try using console.log($templateCache.get("popoverTemplate.html")) I get "$templateCache is not defined", which leads me to assume I am missing a crucial step. However, I can't find how to do it in the docs or other SO questions.
EDIT:
Injecting the service was the missing link. However, when I inject the service, the controller's other function no longer works, but if you inject al the function's parameters the working code becomes:
(function() {
"use strict";
angular.module("app").controller("managerController", ["$scope", "imageHierarchyRepository", "$templateCache", function ($scope, imageHierarchyRepository, $templateCache) {
imageHierarchyRepository.query(function(data) {
$scope.hierarchies = data;
});
var template = $templateCache.get("popoverTemplate.html");
console.log(template);
}]);
})();
To use the template script tag . You have to insert it inside the angular application. That is inside the element with the ng-app attribute or the element used to bootstrap the app if you don't use the ng-app tag.
<body ng-app="myapp">
<div ng-template="'myTemplate.html'"></div>
<script type="text/ng-template" id="myTemplate.html">
// whate ever
</script>
</body>
If you want to retrieve the template on a component of the application then you need to inject the service where you want to consume it:
controller('FooCtrl', ['$templateCache', function ($templateCache) {
var template = $templateCache.get('myTemplate.html');
}]);
Or
controller('FooCtlr', FooCtrl);
FooCtrl ($templateCache) {};
FooCtrl.$inject = ['$templateCache'];
EDIT
Do not register two controllers with the same name because then you override the first one with the last one.
(function() {
"use strict";
angular.module("app").controller("managerController",["$scope", "imageHierarchyRepository", "$templateCache", function ($scope, imageHierarchyRepository, $templateCache) {
var template = $templateCache.get("popoverTemplate.html");
console.log(template);
imageHierarchyRepository.query(function(data) {
$scope.hierarchies = data;
});
}]);
})();
Small addition: Although there are few ways to achieve your goals, like wrapping your whole HTML in <script> tags and all that, the best approach for me was to add the $templateCache logic into each Angular directive. This way, I could avoid using any external packages like grunt angular-templates (which is excellent but overkill for my app).
angular.module('MyApp')
.directive('MyDirective', ['$templateCache', function($templateCache) {
return {
restrict: 'E',
template: $templateCache.get('MyTemplate').data,
controller: 'MyController',
controllerAs: 'MyController'
};
}]).run(function($templateCache, $http) {
$http.get('templates/MyTemplate.html').then(function(response) {
$templateCache.put('MyTemplate', response);
})
});
Hope this helps!
I have a Controller which has something like this...
angular.module('kZoneApp').controller('DemandController', ['$http', '$scope', '$rootScope', 'dataFactory', '$window', '$routeParams','$sce', function ($http, $scope,$rootScope, dataFactory, $window, $routeParams,$sce) {
$scope.channel = $routeParams.channel;
now within my page.html, I want to do the following....
<script>
YoutubeVideoPlayer.openVideo('{{channel}}');
</script>
please note, I am using Routes, and page.html is loaded into my ng-view Via a Route.
<div id="myView" class="reveal-animation" ng-view></div>
I tried below code, but it returns undefined....
$().ready(function () {
var channel = angular.element("#myView").scope().channel
YoutubeVideoPlayer.openVideo(channel);
})
How can I get the value of Channel within my Template View?
Update
below works...but I am sure there has to be a cleaner solution
setTimeout(function () {
alert(angular.element("#myView").scope().channel)
}, 500);
A workaround solution should be creating a variable before your Angular.module definition.
var scopeInAngular = null;
and inside controller assign controller to your variable.
scopeInAngular = $scope;
then if you manipulate anything inside angular view use
scopeInAngular.$apply();
Upon updating from angular 1.2.0-RC.2 to 1.2.0-RC.3 I noticed a breaking change triggered by changes to how ngBindHtmlDirective parses scope data (via this change). What I do is fetch a piece of SVG and then display it (SVG is valid and all that).
I have this bit of template markup:
<div ng-controller="MainCtrl">
<div ng-bind-html="svg"></div>
</div>
And some logic on my app that requests the SVG from the server and then assigns it to $scope:
app.controller('MainCtrl', ['$scope', '$sce', 'API', function($scope, $sce, API) {
API.getSVG().then( function(resp) {
$scope.svg = $sce.trustAsHtml(resp.data.svg);
});
}]);
This used to work on RC2 but doesn't anymore on RC3. Any idea what I'm doing wrong? thanks.
I set up a simple jsFiddle and it works fine with Angular 1.2.0-RC3 up to 1.2.4.
JS
$scope.svg = $sce.trustAsHtml('<svg xmlns="http://www.w3.org/2000/svg"><circle r="50"/></svg>');
I have this piece of layout html:
<body ng-controller="MainController">
<div id="terminal"></div>
<div ng-view></div>
<!-- including scripts -->
</body>
Now apparently, when I try to use $routeParams in MainController, it's always empty. It's important to note that MainController is supposed to be in effect in every possible route; therefore I'm not defining it in my app.js. I mean, I'm not defining it here:
$routeProvider.when("/view1", {
templateUrl: "partials/partial1.html"
controller: "MyCtrl1"
})
$routeProvider.when("/view2", {
templateUrl: "partials/partial2.html"
controller: "MyCtrl2"
})
// I'm not defining MainController here!!
In fact, I think my problem is perfectly the same as this one: https://groups.google.com/forum/#!topic/angular/ib2wHQozeNE
However, I still don't get how to get route parameters in my main controller...
EDIT:
What I meant was that I'm not associating my MainController with any specific route. It's defined; and it's the parent controller of all other controllers. What I'm trying to know is that when you go to a URL like /whatever, which is matched by a route like /:whatever, why is it that only the sub-controller is able to access the route parameter, whereas the main controller is not? How do I get the :whatever route parameter in my main controller?
The $routeParams service is populated asynchronously. This means it will typically appear empty when first used in a controller.
To be notified when $routeParams has been populated, subscribe to the $routeChangeSuccess event on the $scope. (If you're in a component that doesn't have access to a child $scope, e.g., a service or a factory, you can inject and use $rootScope instead.)
module.controller('FooCtrl', function($scope, $routeParams) {
$scope.$on('$routeChangeSuccess', function() {
// $routeParams should be populated here
});
);
Controllers used by a route, or within a template included by a route, will have immediate access to the fully-populated $routeParams because ng-view waits for the $routeChangeSuccess event before continuing. (It has to wait, since it needs the route information in order to decide which template/controller to even load.)
If you know your controller will be used inside of ng-view, you won't need to wait for the routing event. If you know your controller will not, you will. If you're not sure, you'll have to explicitly allow for both possibilities. Subscribing to $routeChangeSuccess will not be enough; you will only see the event if $routeParams wasn't already populated:
module.controller('FooCtrl', function($scope, $routeParams) {
// $routeParams will already be populated
// here if this controller is used within ng-view
$scope.$on('$routeChangeSuccess', function() {
// $routeParams will be populated here if
// this controller is used outside ng-view
});
);
As an alternate to the $timeout that plong0 mentioned...
You can also inject the $route service which will show your params immediately.
angular.module('MyModule')
.controller('MainCtrl', function ($scope, $route) {
console.log('routeParams:'+JSON.stringify($route.current.params));
});
I have the same problem.
What I discovered is that, $routeParams take some time to load in the Main Controller, it probably initiate the Main Controller first and then set $routeParams at the Child Controller. I did a workaround for it creating a method in the Main Controller $scope and pass $routeParams through it in the Child Controllers:
angular.module('MyModule')
.controller('MainController', ["$scope", function ($scope) {
$scope.parentMethod = function($routeParams) {
//do stuff
}
}]);
angular.module('MyModule')
.controller('MyCtrl1', ["$scope", function ($scope) {
$scope.parentMethod($routeParams);
}]);
angular.module('MyModule')
.controller('MyCtrl2', ["$scope", function ($scope) {
$scope.parentMethod($routeParams);
}]);
had the same problem, and building off what Andre mentioned in his answer about $routeParams taking a moment to load in the main controller, I just put it in a timeout inside my MainCtrl.
angular.module('MyModule')
.controller('MainCtrl', function ($scope, $routeParams, $timeout) {
$timeout(function(){
// do stuff with $routeParams
console.log('routeParams:'+JSON.stringify($routeParams));
}, 20);
});
20ms delay to use $routeParams is not even noticeable, and less than that seems to have inconsistent results.
More specifically about my problem, I was confused because I had the exact same setup working with a different project structure (yo cg-angular) and when I rebuilt my project (yo angular-fullstack) I started experiencing the problem.
You have at least two problems here:
with $routeParams you get the route parameters, which you didn't define
the file where you define a main controller doesn't really matter. the important thing is in which module/function
The parameters have to be defined with the $routeProvider with the syntax :paramName:
$routeProvider.when("/view2/name1/:a/name2/:b"
and then you can retrieve them with $routeParams.paramName.
You can also use the query parameters, like index.html?k1=v1&k2=v2.
app.js is the file where you'd normally define dependencies and configuration (that's why you'd have there the app module .config block) and it contains the application module:
var myapp = angular.module(...);
This module can have other modules as dependencies, like directives or services, or a module per feature.
A simple approach is to have a module to encapsulate controllers. An approach closer to your original code is putting at least one controller in the main module:
myapp.controller('MainCtrl', function ($scope) {...}
Maybe you defined the controller as a global function? function MainCtrl() {...}? This pollutes the global namespace. avoid it.
Defining your controller in the main module will not make it "to take effect in all routes". This has to be defined with $routeProvider or make the controller of each route "inherit" from the main controller. This way, the controller of each route is instantiated after the route has changed, whereas the main controller is instantiated only once, when the line ng-controller="MainCtrl" is reached (which happens only once, during application startup)
You can simply pass values of $routeParams defined into your controller into the $rootScope
.controller('MainCtrl', function ($scope, $routeParams, MainFactory, $rootScope) {
$scope.contents = MainFactory.getThing($routeParams.id);
$rootScope.total = MainFactory.getMax(); // Send total to the rootScope
}
and inject $rootScope in your IndexCtrl (related to the index.html)
.controller('IndexCtrl', function($scope, $rootScope){
// Some code
});
What is correct way of passing variables from web page when initializing $scope?
I currently know 2 possibilities:
ng-init, which looks awful and not recommended (?)
using AJAX request for resource, which requires additional request to server which I do not want.
Is there any other way?
If those variables are able to be injected through ng-init, I'm assuming you have them declared in Javascript.
So you should create a service (constant) to share these variables:
var variablesFromWebPage = ...;
app.constant('initValues', variablesFromWebPage);
With this service, you don't need to add them to the scope in the app start, you can use it from any controller you have, just by injecting it (function MyCtrl(initValues) {}).
Althouhg, if you do require it to be in the scope, then this is one of the main reasons what controllers are meant for, as per the docs:
Use controllers to:
Set up the initial state of a scope object.
Add behavior to the scope object.
Just add this cotroller to your root node:
app.controller('InitCtrl', function($rootScope, initValues) {
$rootScope.variable1 = initValue.someVariable;
$rootScope.variable2 = initValue.anotherVariable;
});
#Cunha: Tnx.
Here some more details how I did it:
In the Webpage:
<script type="text/javascript">
var variablesFromWebPage = {};
variablesFromWebPage.phoneNumber = '#Model.PhoneNumber';
</script>
In the angular application file, registering the service:
var module= angular.module('mymodule', ['ngRoute', 'ngAnimate']);
module.factory('variablesFromWebPage', function () {
return variablesFromWebPage
});
And then the controller:
module.controller('IndexController',
function ($scope, $location, $http, $interval, variablesFromWebPage) {
$scope.phoneNumber = variablesFromWebPage.phoneNumber;
}
);