I have a function which I want to call after page content is loaded. I read about $viewContentLoaded and it doesn't work for me. I am looking for something like
document.addEventListener('DOMContentLoaded', function () {
//Content goes here
}, false);
Above call doesn't work for me in AngularJs controller.
According to documentation of $viewContentLoaded, it supposed to work
Emitted every time the ngView content is reloaded.
$viewContentLoaded event is emitted that means to receive this event you need a parent controller like
<div ng-controller="MainCtrl">
<div ng-view></div>
</div>
From MainCtrl you can listen the event
$scope.$on('$viewContentLoaded', function(){
//Here your view content is fully loaded !!
});
Check the Demo
Angular < 1.6.X
angular.element(document).ready(function () {
console.log('page loading completed');
});
Angular >= 1.6.X
angular.element(function () {
console.log('page loading completed');
});
fixed - 2015.06.09
Use a directive and the angular element ready method like so:
js
.directive( 'elemReady', function( $parse ) {
return {
restrict: 'A',
link: function( $scope, elem, attrs ) {
elem.ready(function(){
$scope.$apply(function(){
var func = $parse(attrs.elemReady);
func($scope);
})
})
}
}
})
html
<div elem-ready="someMethod()"></div>
or for those using controller-as syntax...
<div elem-ready="vm.someMethod()"></div>
The benefit of this is that you can be as broad or granular w/ your UI as you like and you are removing DOM logic from your controllers. I would argue this is the recommended Angular way.
You may need to prioritize this directive in case you have other directives operating on the same node.
You can directly call it by adding {{YourFunction()}} after HTML element.
Here is a Plunker Link.
I had to implement this logic while handling with google charts. what i did was that at the end of my html inside controller definition i added.
<body>
-- some html here --
--and at the end or where ever you want --
<div ng-init="FunCall()"></div>
</body>
and in that function simply call your logic.
$scope.FunCall = function () {
alert("Called");
}
var myM = angular.module('data-module');
myM.directive('myDirect',['$document', function( $document ){
function link( scope , element , attrs ){
element.ready( function(){
} );
scope.$on( '$viewContentLoaded' , function(){
console.log(" ===> Called on View Load ") ;
} );
}
return {
link: link
};
}] );
Above method worked for me
you can call javascript version of onload event in angular js. this ng-load event can be applied to any dom element like div, span, body, iframe, img etc. following is the link to add ng-load in your existing project.
download ng-load for angular js
Following is example for iframe, once it is loaded testCallbackFunction will be called in controller
EXAMPLE
JS
// include the `ngLoad` module
var app = angular.module('myApp', ['ngLoad']);
app.controller('myCtrl', function($scope) {
$scope.testCallbackFunction = function() {
//TODO : Things to do once Element is loaded
};
});
HTML
<div ng-app='myApp' ng-controller='myCtrl'>
<iframe src="test.html" ng-load callback="testCallbackFunction()">
</div>
If you're getting a $digest already in progress error, this might help:
return {
restrict: 'A',
link: function( $scope, elem, attrs ) {
elem.ready(function(){
if(!$scope.$$phase) {
$scope.$apply(function(){
var func = $parse(attrs.elemReady);
func($scope);
})
}
else {
var func = $parse(attrs.elemReady);
func($scope);
}
})
}
}
I was using {{myFunction()}} in the template but then found another way here using $timeout inside the controller. Thought I'd share it, works great for me.
angular.module('myApp').controller('myCtrl', ['$timeout',
function($timeout) {
var self = this;
self.controllerFunction = function () { alert('controller function');}
$timeout(function () {
var vanillaFunction = function () { alert('vanilla function'); }();
self.controllerFunction();
});
}]);
Running after the page load should partially be satisfied by setting an event listener to the window load event
window.addEventListener("load",function()...)
Inside the module.run(function()...) of angular you will have all access to the module structure and dependencies.
You can broadcast and emit events for communications bridges.
For example:
module set onload event and build logic
module broadcast event to controllers when logic required it
controllers will listen and execute their own logic based on module onload processes.
If you want certain element to completely loaded, Use ng-init on that element .
e.g. <div class="modal fade" id="modalFacultyInfo" role="dialog" ng-init="initModalFacultyInfo()"> ..</div>
the initModalFacultyInfo() function should exist in the controller.
I found that if you have nested views - $viewContentLoaded gets triggered for every of the nested views. I've created this workaround to find the final $viewContentLoaded. Seems to work alright for setting $window.prerenderReady as required by Prerender (goes into .run() in the main app.js):
// Trigger $window.prerenderReady once page is stable
// Note that since we have nested views - $viewContentLoaded is fired multiple
// times and we need to go around this problem
var viewContentLoads = 0;
var checkReady = function(previousContentLoads) {
var currentContentLoads = Number(viewContentLoads) + 0; // Create a local copy of the number of loads
if (previousContentLoads === currentContentLoads) { // Check if we are in a steady state
$window.prerenderReady = true; // Raise the flag saying we are ready
} else {
if ($window.prerenderReady || currentContentLoads > 20) return; // Runaway check
$timeout(function() {checkReady(currentContentLoads);}, 100); // Wait 100ms and recheck
}
};
$rootScope.$on('$stateChangeSuccess', function() {
checkReady(-1); // Changed the state - ready to listen for end of render
});
$rootScope.$on('$viewContentLoaded', function() {
viewContentLoads ++;
});
var myTestApp = angular.module("myTestApp", []);
myTestApp.controller("myTestController", function($scope, $window) {
$window.onload = function() {
alert("is called on page load.");
};
});
The solution that work for me is the following
app.directive('onFinishRender', ['$timeout', '$parse', function ($timeout, $parse) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function () {
scope.$emit('ngRepeatFinished');
if (!!attr.onFinishRender) {
$parse(attr.onFinishRender)(scope);
}
});
}
if (!!attr.onStartRender) {
if (scope.$first === true) {
$timeout(function () {
scope.$emit('ngRepeatStarted');
if (!!attr.onStartRender) {
$parse(attr.onStartRender)(scope);
}
});
}
}
}
}
}]);
Controller code is the following
$scope.crearTooltip = function () {
$('[data-toggle="popover"]').popover();
}
Html code is the following
<tr ng-repeat="item in $data" on-finish-render="crearTooltip()">
I use setInterval to wait for the content loaded. I hope this can help you to solve that problem.
var $audio = $('#audio');
var src = $audio.attr('src');
var a;
a = window.setInterval(function(){
src = $audio.attr('src');
if(src != undefined){
window.clearInterval(a);
$('audio').mediaelementplayer({
audioWidth: '100%'
});
}
}, 0);
Related
I'm very new to angularjs, so this may be a very obvious to a lot of you...
I have a "main" view with a "MainCtrl" controller. The controller has a "$scope.init()" function that is being called when the controller is initialised.
However, I have an iFrame in the view that needs to complete it's "onload" js function before the "init" function can successfully run.
I tried triggering the "init" function using the iFrame's ngInit, but that happens before the "onload"
Please can you tell me how to make init function run after the iFrame's onload="otherFunction" has finished?
Created a demo below where ng-init="increaseCount()" increases the value of $scope.carCount variable but only after the <iframe> was loaded.
You can load the <iframe> by clicking on the button and watch how $scope.carCount changes.
Not sure if this is the right way though.
Basically, I have 2 functions inside my INIT function named increaseCount():
A function increaseCount which contains the main INIT logic ie. increasing the amount of cars
A function onIframeLoad which waits until the iframe is loaded and then executes main INIT logic by calling increaseCount();
Interactive DEMO here or run the code below ↓↓↓
var app = angular.module('App', []);
// Allow iframe loading from various sources
app.config(["$sceDelegateProvider", function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
// Allow same origin resource loads
"self",
// Allow YouTube iframes
"https://www.youtube.com/embed/**"
]);
}]);
// Allow a directive "iframe-onload" in HTML
app.directive('iframeOnload', [function(){
return {
scope: {
callBack: '&iframeOnload'
},
link: function(scope, element, attrs){
element.on('load', function(){
return scope.callBack();
})
}
}}]);
// Main controller
app.controller('MainCtrl', function($scope, $sce) {
$scope.car = 'Mercedes';
$scope.carCount = 0;
$scope.iframeSource = "";
// INIT
$scope.increaseCount = function () {
// First wait on iframe load
$scope.onIframeLoad = function () {
console.log('Iframe fully loaded');
increaseCount(); // If iframe loaded then execute main INIT logic
};
// INIT body - main INIT logic
function increaseCount () {
$scope.$apply('carCount = 10'); // change $scope.carCount to 10
}
};
// Load iframe when clicked on the button
$scope.loadIframe = function () {
console.log("Clicked on the button.");
$scope.iframeSource = "https://www.youtube.com/embed/Ra__OWuOU1M";
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-app="App" ng-controller="MainCtrl" ng-init="increaseCount()">
<h1>{{ car }} count: {{ carCount }}</h1>
<iframe iframe-onload="onIframeLoad()" ng-src="{{ iframeSource }}"></iframe>
<button ng-click="loadIframe()">Load Iframe</button>
</div>
I am working on a custom directive to preload images while the state (with ui-router is changing) so that both data and content are resolved before navigating to the new state.
I have created a Pen here: https://codepen.io/wons88/pen/NgbNvO to show my basic implementation with the bgSrc directive:
app.directive('bgSrc', ['preloadService', function (preloadService) {
return function (scope, element, attrs) {
element.hide();
preloadService(attrs.bgSrc).then(function () {
console.log(element.css);
element.css({
"background-image": "url('" + attrs.bgSrc + "')"
});
element.fadeIn();
});
}
}]);
and the preloadService:
app.factory('preloadService', ['$q', '$rootScope', function ($q, $rootScope) {
return function (url) {
var deffered = $q.defer(),
image = new Image();
image.src = url;
if (image.complete) {
$rootScope.loading = false;
deffered.resolve();
} else {
$rootScope.loading = true;
image.addEventListener('load',
function () {
deffered.resolve();
$rootScope.loading = false;
});
image.addEventListener('error',
function () {
deffered.reject();
$rootScope.loading = true;
});
}
return deffered.promise;
};
}]);
And the HTML:
<div bg-src="https://images5.alphacoders.com/446/thumb-1920-446028.jpg">Background img should show up...</div>
EDIT:
The problem is that the image is never shown even if it is loaded (as shown in the network tab in chrome dev tools). If I make it statically applying style (or class) the image is shown no problem, just loads slower... hence trying to apply the directive.
I know there is something wrong happening in the bgSrc directive but I am unable to pinpoint it. Any help on the matter would be much appreciated :)
element.hide is not a function
You are calling
element.hide();
in your link function, but jQlite does not have a hide() method. Since you did not include jQuery in your Codepen, it's failing on that line.
https://docs.angularjs.org/api/ng/function/angular.element
Instead, use css
element.css('visibility', 'invisible');
Or add jQuery
At what stage of the compile / link process are variables from the isolated scope of a directive bound to the parent (controller) scope? I have an application in which I want to call a directive api automatically, as soon as the view is loaded.
I understood that scope binding happens in the directive linking phase, so that post linking, the variables exposed on the isolated scope should be available on the parent scope.
However, I find that this is not the case, as demonstrated in the code below (plunker here).
//plunker code
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.buttonClick = function() {
console.log("In buttonClick function, call is: " + this.call);
this.call();
}
$scope.$on("LinkComplete", function(event) {
console.log("In LinkComplete, call is: " + event.currentScope.call);
//event.currentScope.call();
});
console.log("In Constructor, call is: " + this.call);
})
.directive('myDirective', function(){
return {
scope: {
myMethod: '='
},
controller: function ($scope) {
$scope.myMethod = function() {
alert("method called");
};
},
link: function postLink(scope)
{
scope.$emit("LinkComplete");
}
};
});
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<div my-directive my-method="call"></div>
<button ng-click="buttonClick()">Call</button>
</body>
Note that the code attempts to access the linked variable (which points to a method on the directive controller) twice during the view initialisation, and on both occasions, the variable is undefined. I wouldn't expect the variable to be available during the main controller constructor, but I would expect it to be available during the post-link event handler. Once the view is loaded, the bound variables are available (click Call button to witness).
How can I access the bound variables from the controller, without requiring the user to click on a button or the like?
That's a good question, when you see some words like 'x is y in z stage' you need to be careful on the accuracy, always dig into the source code to prove it.
Your plunker is using v1.2.27, checkout this line:
https://github.com/angular/angular.js/blob/v1.2.27/src/ng/compile.js#L1492
isolateScope.$watch(function parentValueWatch() {
var parentValue = parentGet(scope);
if (!compare(parentValue, isolateScope[scopeName])) {
// we are out of sync and need to copy
if (!compare(parentValue, lastValue)) {
// parent changed and it has precedence
isolateScope[scopeName] = parentValue;
} else {
// if the parent can be assigned then do so
parentSet(scope, parentValue = isolateScope[scopeName]);
}
}
return lastValue = parentValue;
}, null, parentGet.literal);
This will be evaluated in next $digest cycle and by then parentScope.call will be assigned. At the same time, postLink function is executed synchronously right below it:
https://github.com/angular/angular.js/blob/v1.2.27/src/ng/compile.js#L1575
// POSTLINKING
for (i = postLinkFns.length - 1; i >= 0; i--) {
try {
linkFn = postLinkFns[i];
linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs,
linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn);
} catch (e) {
$exceptionHandler(e, startingTag($element));
}
}
After postLink has been executed, controller got the event but parentScope.call has not been initialized yet via $digest.
So if you add a setTimeout to check, it looks like what you want:
$scope.$on("LinkComplete", function(event) {
setTimeout(function () {
console.log("In LinkComplete, call is: " + event.currentScope.call);
//event.currentScope.call();
});
});
I'm trying to see if there's a simple way to access the internal scope of a controller through an external javascript function (completely irrelevant to the target controller)
I've seen on a couple of other questions here that
angular.element("#scope").scope();
would retrieve the scope from a DOM element, but my attempts are currently yielding no proper results.
Here's the jsfiddle: http://jsfiddle.net/sXkjc/5/
I'm currently going through a transition from plain JS to Angular. The main reason I'm trying to achieve this is to keep my original library code intact as much as possible; saving the need for me to add each function to the controller.
Any ideas on how I could go about achieving this? Comments on the above fiddle are also welcome.
You need to use $scope.$apply() if you want to make any changes to a scope value from outside the control of angularjs like a jquery/javascript event handler.
function change() {
alert("a");
var scope = angular.element($("#outer")).scope();
scope.$apply(function(){
scope.msg = 'Superhero';
})
}
Demo: Fiddle
It's been a while since I posted this question, but considering the views this still seems to get, here's another solution I've come upon during these last few months:
$scope.safeApply = function( fn ) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest') {
if(fn) {
fn();
}
} else {
this.$apply(fn);
}
};
The above code basically creates a function called safeApply that calles the $apply function (as stated in Arun's answer) if and only Angular currently isn't going through the $digest stage. On the other hand, if Angular is currently digesting things, it will just execute the function as it is, since that will be enough to signal to Angular to make the changes.
Numerous errors occur when trying to use the $apply function while AngularJs is currently in its $digest stage. The safeApply code above is a safe wrapper to prevent such errors.
(note: I personally like to chuck in safeApply as a function of $rootScope for convenience purposes)
Example:
function change() {
alert("a");
var scope = angular.element($("#outer")).scope();
scope.safeApply(function(){
scope.msg = 'Superhero';
})
}
Demo: http://jsfiddle.net/sXkjc/227/
Another way to do that is:
var extScope;
var app = angular.module('myApp', []);
app.controller('myController',function($scope, $http){
extScope = $scope;
})
//below you do what you want to do with $scope as extScope
extScope.$apply(function(){
extScope.test = 'Hello world';
})
we can call it after loaded
http://jsfiddle.net/gentletech/s3qtv/3/
<div id="wrap" ng-controller="Ctrl">
{{message}}<br>
{{info}}
</div>
<a onClick="hi()">click me </a>
function Ctrl($scope) {
$scope.message = "hi robi";
$scope.updateMessage = function(_s){
$scope.message = _s;
};
}
function hi(){
var scope = angular.element(document.getElementById("wrap")).scope();
scope.$apply(function() {
scope.info = "nami";
scope.updateMessage("i am new fans like nami");
});
}
It's been a long time since I asked this question, but here's an answer that doesn't require jquery:
function change() {
var scope = angular.element(document.querySelector('#outside')).scope();
scope.$apply(function(){
scope.msg = 'Superhero';
})
}
Here's a reusable solution: http://jsfiddle.net/flobar/r28b0gmq/
function accessScope(node, func) {
var scope = angular.element(document.querySelector(node)).scope();
scope.$apply(func);
}
window.onload = function () {
accessScope('#outer', function (scope) {
// change any property inside the scope
scope.name = 'John';
scope.sname = 'Doe';
scope.msg = 'Superhero';
});
};
You can also try:
function change() {
var scope = angular.element( document.getElementById('outer') ).scope();
scope.$apply(function(){
scope.msg = 'Superhero';
})
}
The accepted answer is great. I wanted to look at what happens to the Angular scope in the context of ng-repeat. The thing is, Angular will create a sub-scope for each repeated item. When calling into a method defined on the original $scope, that retains its original value (due to javascript closure). However, the this refers the calling scope/object. This works out well, so long as you're clear on when $scope and this are the same and when they are different. hth
Here is a fiddle that illustrates the difference: https://jsfiddle.net/creitzel/oxsxjcyc/
I'm newbie, so sorry if is a bad practice. Based on the chosen answer, I did this function:
function x_apply(selector, variable, value) {
var scope = angular.element( $(selector) ).scope();
scope.$apply(function(){
scope[variable] = value;
});
}
I'm using it this way:
x_apply('#fileuploader', 'thereisfiles', true);
By the way, sorry for my english
<input type="text" class="form-control timepicker2" ng-model='programRow.StationAuxiliaryTime.ST88' />
accessing scope value
assume that programRow.StationAuxiliaryTime is an array of object
$('.timepicker2').on('click', function ()
{
var currentElement = $(this);
var scopeValues = angular.element(currentElement).scope();
var model = currentElement.attr('ng-model');
var stationNumber = model.split('.')[2];
var val = '';
if (model.indexOf("StationWaterTime") > 0) {
val = scopeValues.programRow.StationWaterTime[stationNumber];
}
else {
val = scopeValues.programRow.StationAuxiliaryTime[stationNumber];
}
currentElement.timepicker('setTime', val);
});
We need to use Angular Js built in function $apply to acsess scope variables or functions outside the controller function.
This can be done in two ways :
|*| Method 1 : Using Id :
<div id="nameNgsDivUid" ng-app="">
<a onclick="actNgsFnc()"> Activate Angular Scope</a><br><br>
{{ nameNgsVar }}
</div>
<script type="text/javascript">
var nameNgsDivVar = document.getElementById('nameNgsDivUid')
function actNgsFnc()
{
var scopeNgsVar = angular.element(nameNgsDivVar).scope();
scopeNgsVar.$apply(function()
{
scopeNgsVar.nameNgsVar = "Tst Txt";
})
}
</script>
|*| Method 2 : Using init of ng-controller :
<div ng-app="nameNgsApp" ng-controller="nameNgsCtl">
<a onclick="actNgsFnc()"> Activate Angular Scope</a><br><br>
{{ nameNgsVar }}
</div>
<script type="text/javascript">
var scopeNgsVar;
var nameNgsAppVar=angular.module("nameNgsApp",[])
nameNgsAppVar.controller("nameNgsCtl",function($scope)
{
scopeNgsVar=$scope;
})
function actNgsFnc()
{
scopeNgsVar.$apply(function()
{
scopeNgsVar.nameNgsVar = "Tst Txt";
})
}
</script>
This is how I did for my CRUDManager class initialized in Angular controller, which later passed over to jQuery button-click event defined outside the controller:
In Angular Controller:
// Note that I can even pass over the $scope to my CRUDManager's constructor.
var crudManager = new CRUDManager($scope, contextData, opMode);
crudManager.initialize()
.then(() => {
crudManager.dataBind();
$scope.crudManager = crudManager;
$scope.$apply();
})
.catch(error => {
alert(error);
});
In jQuery Save button click event outside the controller:
$(document).on("click", "#ElementWithNgControllerDefined #btnSave", function () {
var ngScope = angular.element($("#ElementWithNgControllerDefined")).scope();
var crudManager = ngScope.crudManager;
crudManager.saveData()
.then(finalData => {
alert("Successfully saved!");
})
.catch(error => {
alert("Failed to save.");
});
});
This is particularly important and useful when your jQuery events need to be placed OUTSIDE OF CONTROLLER in order to prevent it from firing twice.
I recently chose AngularJS over ember.js for a project I am working on, and have been very pleased with it so far. One nice thing about ember is its built in support for "computed properties" with automatic data binding. I have been able to accomplish something similar in Angular with the code below, but am not sure if it is the best way to do so.
// Controller
angular.module('mathSkills.controller', [])
.controller('nav', ['navigation', '$scope', function (navigation, $scope) {
// "Computed Property"
$scope.$watch(navigation.getCurrentPageNumber, function(newVal, oldVal, scope) {
scope.currentPageNumber = newVal;
});
$scope.totalPages = navigation.getTotalPages();
}]);
// 'navigation' service
angular.module('mathSkills.services', [])
.factory('navigation', function() {
var currentPage = 0,
pages = [];
return {
getCurrentPageNumber: function() {
return currentPage + 1;
},
getTotalPages: function() {
return pages.length;
}
};
});
// HTML template
<div id=problemPager ng-controller=nav>
Problem {{currentPageNumber}} of {{totalPages}}
</div>
I would like for the UI to update whenever the currentPage of the navigation service changes, which the above code accomplishes.
Is this the best way to solve this problem in AngularJS? Are there (significant) performance implications for using $watch() like this? Would something like this be better accomplished using custom events and $emit() or $broadcast()?
While your self-answer works, it doesn't actually implement computed properties. You simply solved the problem by calling a function in your binding to force the binding to be greedy. I'm not 100% sure it'd work in all cases, and the greediness might have unwanted performance characteristics in some situations.
I worked up a solution for a computed properties w/dependencies similar to what EmberJS has:
function ngCreateComputedProperty($scope, computedPropertyName, dependentProperties, f) {
function assignF($scope) {
var computedVal = f($scope);
$scope[computedPropertyName] = computedVal;
};
$scope.$watchCollection(dependentProperties, function(newVal, oldVal, $scope) {
assignF($scope);
});
assignF($scope);
};
// in some controller...
ngCreateComputedProperty($scope, 'aSquared', 'a', function($scope) { return $scope.a * $scope.a } );
ngCreateComputedProperty($scope, 'aPlusB', '[a,b]', function($scope) { return $scope.a + $scope.b } );
See it live: http://jsfiddle.net/apinstein/2kR2c/3/
It's worth noting that $scope.$watchCollection is efficient -- I verified that "assignF()" is called only once even if multiple dependencies are changed simultaneously (same $apply cycle).
"
I think I found the answer. This example can be dramatically simplified to:
// Controller
angular.module('mathSkills.controller', [])
.controller('nav', ['navigation', '$scope', function (navigation, $scope) {
// Property is now just a reference to the service's function.
$scope.currentPageNumber = navigation.getCurrentPageNumber;
$scope.totalPages = navigation.getTotalPages();
}]);
// HTML template
// Notice the first binding is to the result of a function call.
<div id=problemPager ng-controller=nav>
Problem {{currentPageNumber()}} of {{totalPages}}
</div>
Note that with ECMAScript 5 you can now also do something like this:
// Controller
angular.module('mathSkills.controller', [])
.controller('nav', function(navigation, $scope) {
$scope.totalPages = navigation.getTotalPages();
Object.defineProperty($scope, 'currentPageNumber', {
get: function() {
return navigation.getCurrentPageNumber();
}
});
]);
//HTML
<div ng-controller="nav">Problem {{currentPageNumber}} of {{totalPages}}</div>