I'm learning Angular I tried to init a controller after create a new content by ajax (with jQuery, maybe it's not a good idea but just I'm starting to learning step by step.)
this is my code.
angular.module('app').controller('TestController',function($scope){
$scope.products = [{
'code': 'A-1',
'name': 'Product 01'
}];
$scope.clickTest = function(){
alert('test');
}
});
$.ajax({..})
.done(function(html){
angular.element(document).injector().invoke(function($compile){
$('#content').html(html);
var scope = $('#content').scope();
$compile($('#content').contents())(scope);
});
});
In my html I use ng-repeat but not load nothing, however when I click in
ng-click="clickTest" it works! and ng-repeat is loaded. this is my problem I need that the ng-repeat load when I load for first time.
Thanks.
Sorry for my bad english.
with jQuery, maybe it's not a good idea
Yes spot on
Now getting into your issue:- When you click on element with ng-click on the html, it works because it then runs a digest cycle and refreshes the DOM and your repeat renders. $Compile has already instantiated the controller and methods are available and attached to DOM. But there is one digest cycle that runs in angular after controller initialization, which renders data in DOM, and that does not happen in your case since you are outside angular.
You could do a scope.$digest() after compile to make sure element is rendered and digest cycle is run.
Also probably one more better thing would be to wrap it inside angularRootElement.ready function, just to make sure before the injector is accessed the element is ready, in your case enclosing it inside ajax callback saves you (the time for it to be ready) but better to have it.
Try:-
$.ajax({..})
.done(function(html){
var rootElement = angular.element(document);
rootElement.ready(function(){
rootElement.injector().invoke(function($compile){
var $content = $('#content'),
scope = $content.scope();
$content.html(html);
$compile($content.contents())(scope);
scope.$digest();
});
});
});
Sample Demo
Demo - Controller on dynamic html
Instead of getting html from the server you could create partials and templates, use routing , use angular ajax wrapper with $http etc... Well i would not suggest doing this method though - however based on your question you understand that already. There was a similar question that i answered last day
If you are not looking for a better way to do what you are doing, ignore the following.
The beauty of using frameworks like AngularJS and KnockoutJS (or many more) is that we don't have to worry about the timing of when the data loads. You just set up the bindings between the controller and the UI and once the data is loaded into the respective properties, these frameworks will take care of updating the UI for you.
I am not sure why you are trying to set up the UI using JQuery and waiting for the data to loaded first to do so but normally you will not have to do all that.
You can create a UI with ng-repeat and click bindings ,etc and make an ajax call to get the data from anywhere. Once the data is loaded, in the callback, just push the necessary data into the collection bound to the ng-repeat directive. That is all you will have to do.
Related
Suppose, I am making a custom Angular directive that has to examine and manipulate the DOM tree inside it (to be precise: the DOM tree under the element the directive is applied to). The right place for such manipulation is the directive's post-link function.
That works fine while all the HTML inside the directive is inlined. Problems appear when inside the directive we have other directives that load their templates using "templateUrl" property, or just "ng-include" directives to insert partials.
Those templates and partials are loaded asynchronously. That means, at the compile stage Angular will initiate the partial loading and will continue compiling without waiting for the loading to complete. Then, at the moment the parent directive is linked, the contained partials loading may still be in progress, so the directive's post-link function sees nothing inside.
In other words: the directive's post-link function is designed to have all nested DOM ready by the moment it is called, but with the async templates and includes this is not the case!
And template pre-loading does not help, because they are still accessed asynchronously.
How do people overcome that?
The task seems to be quite common, but I did not manage to find a good and reliable solution. Do I miss something obvious?...
Update: Well I have created a Plunk to illustrate the problem. Actually it reproduces only the problem with ng-include, the external template for sub-directive works. In my project it did not though, maybe this is a race condition, I have to investigate more.
You can wait for the load of the main view with:
$scope.$on('$viewContentLoaded', function () {
//Here your view content is fully loaded !!
});
This trick, same as $timeout, not works if you are loading views with ng-include. You should wait for all the partial views. The right events order is:
$viewContentLoaded
$includeContentRequested
$timeout
$includeContentLoaded
You can use $includeContentRequested and $includeContentLoaded with a counter for wait the content of all included partials:
var nIncludes = 0;
$scope.$on('$includeContentRequested', function (event, templateName) {
nIncludes++;
console.log(nIncludes, '$includeContentRequested', templateName);
});
$scope.$on("$includeContentLoaded", function (event, templateName) {
nIncludes--;
console.log(nIncludes, '$includeContentLoaded', templateName);
if (nIncludes === 0) {
console.log('everything is loaded!!');
// Do stuff here
}
});
I have not found another more elegant solution.
use $timeout for the external template directive (This will still not work for ng-include). it will call when async loaded template finish loading. (By the way this is clean code, it won't cause performance issue)
$scope.addNew = function(){
$('.thumb-img-gallary').append("<li><span class='img-del' ng-click='delThumbImgGallaryPhoto($event)'>X</span><img class='thumb-img' src='data:image/jpeg;base64,"+imageData+"'/></li>");
}
I am calling this function to add element dynamically. But then delThumbImgGallaryPhoto() is not getting called.
you cannot just append an element with a ng-click or any other directive, and expect it to work. it has got to be compiled by angular.
explenation about compilation from angular docs:
For AngularJS, "compilation" means attaching directives to the HTML to make it interactive
compelation happens in one of two cases:
When Angular bootstraps your application, the HTML compiler traverses the DOM matching directives against the DOM elements
when you call the $compile service inside an angular context
so what you need to do, is first to compile it(remeber to inject the $compile service in your controller), and then append it:
$scope.addNew = function(){
var elem = $compile("<li><span class='img-del' ng-click='delThumbImgGallaryPhoto($event)'>X</span><img class='thumb-img' src='data:image/jpeg;base64,"+imageData+"'/></li>")($scope);
$('.thumb-img-gallary').append(elem);
}
BTW, remember it is prefable not to have any DOM manipulations done in the controller. angular has directives for that.
You have to compile it with scope
try like this
var item="<li><span class='img-del' ng-click='delThumbImgGallaryPhoto($event)'>X</span><img class='thumb-img' src='data:image/jpeg;base64,"+imageData+"'/></li>"
$('.thumb-img-gallary').append(item);
$compile(item)($scope);
angular doesn't know anything about your newly added element. You need to compile your newly added element using $compile. and you should better use directive for this task.
It is a bad habit to access ui elements from controller.
edit: it would be best using ng-repeat for this task. lets say you have a thumb-gallery directive which is repeated using ng-repeat by thumbs array.
when you need to add a new image you only need to add it to your thumbs array.
it is simple and straightforward
Your html would look like
<thumb-gallery ng-repeat="gallery in galleries"></thumb-gallery>
and your js would look like
var gallery = {};
$scope.galleries.add(gallery);
I'm looking to pre-render multiple controllers with static data.
My code structure
Controller in app.js file ($scope.userList = localData.users)
View in separate template file user_list.html (ng-repeat="users in userList")
My goal is to avoid flickering when rendering user_list view in angularJS on slow devices, such as phones.
#Ofiris answer is correct, so I upvoted it. But it only solves part of the flickering, that of a template in its raw format before Angular has a chance to render it.
There are actually 3 states to the template:
When the template is raw and unprocessed
When the template has been processed by angular
When the template has had its data loaded by angular
ng-cloak resolves the transition from 1 to 2, but it cannot resolve the transition from 2 to 3, since it does not know when all of your data is ready.
Let's say you are loading up an object via $resource or $http and it will set a scope object foo to be "Jim". Here is how your 3 states look in the browser.
First state (pre-rendering):
<div>{{foo}}</div>
Second state (post-rendering, while ajax is running in background, maybe some processing):
<div></div>
Third state (all loaded):
<div>Jim</div>
So ng-cloack very much solves the transition from 1 to 2, and hides that ugly {{foo}}, but it won't help you with the flicker from nothing to "Jim".
For that, you would need some sort of flag of your own. I wrote a directive which I wrap all of my templates in (along with ng-cloak), called "loading". Each controller sets $scope.ready = true; when all of the data is ready for state 3. But it is very simple, you just need to wrap as follows:
<div ng-hide="!ready">
<!-- all of your stuff here -->
<div>{{foo}}</div>
</div>
and then your controller can do:
.controller('MyCtrl',function($scope,MyService) {
MyService.get('/data',function(data) {
$scope.foo = data.foo; // or whatever else you do here
// other processing
$scope.ready = true; // this shows everything
});
});
Check out the ngCloak directive.
The ngCloak directive is used to prevent the Angular html template
from being briefly displayed by the browser in its raw (uncompiled)
form while your application is loading. Use this directive to avoid
the undesirable flicker effect caused by the html template display.
Try to place it on the list containing div.
If you would like to resolve your data before the controller is instantiated, you can use routeProvider.resolve method, see this example.
I'm learning AngularJs and I'm playing with a third party Javascript component.
At a certain point the control is initialized as follows:
$(document).ready(function () {
$('#SomeId').initialize();
});
Is there a way to convert this to something more Angularish? I don't want to manipulate the DOM from the controller, but I'm not sure how to initialize this.
This code is not necessarily wrong as is. What you want to be careful about is javascript outside of the angular context (jQuery callbacks/plugins etc) manipulating $scope values because those functions will not trigger an angular digest loop, and will result in a disconnect between the DOM and the $scope.
Here is some more information about this cycle and what you need to know about it: http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
Here is a common use case with jQuery (and also why you should try to use angular services (ie: $http) instead:
// When you manipulate the $scope with a non-angular callback,
// you have to run $scope.$apply() to tell angular about the
// change in order to repaint the DOM
$.get( "/getUser", function( data ) {
$scope.user = data; // set $scope data in callback
$scope.$apply(); // run digest so anything in the DOM binded to the {{user}} model gets updated
});
Generally you don't need to manipulate the DOM like this with Angular...so you really don't need jQuery. Using an Angular "directive" is the declarative, encapsulated way to do it in Angular. There are a ton of 3rd party components out there for you to use that have been built properly with directives: I would recommend using these rather than trying to convert a jQuery plugin (which is really just adding the jQuery bloat for things that you already have access to with Angular). A good place to start looking for Angular directives that you can use is http://ngmodules.org/
Using AngularJS and UI Bootstrap, I want to dynamically add alerts to DOM. But if I dynamically add an <alert> element to DOM, it's not compiled automatically. I tried to use $compile but it doesn't seem to understand tag names not present in core AngularJS. How can I achieve this? Is it even the right way to "manually" add elements to DOM in services?
See Plunker. The alert in #hardcodedalert is compiled and shown correctly but the contents of #dynamicalert are not being compiled.
Edit:
I'd later want to have alerts shown on different context and locations on my web page and that's why I created a constructor function for the alerts, to have a new instance in every controller which needs alerts. And just for curiosity's sake, I was wondering if it's possible to add the <alert> tags dynamically instead of including them in html.
I've updated your plunker to do what you're trying to do the "angular way".
There are a few problems with what you were trying to do. The biggest of which was DOM manipulation from within you controller. I see you were trying to offset that by handling part of it in the service, but you were still referencing the DOM in your controller when you were using JQuery to select that element.
All in all, your directives weren't compiling because you're still developing in a very JQuery-centric fashion. As a rule of thumb you should let directives handle the adding and removing of DOM elements for you. This handles all of the directive compiling and processing for you. If you add things manually the way you were trying, you will have to use the $compile provider to compile them and run them against a scope... it will also be a testing and maintenance nightmare.
Another note: I'm not sure if you meant to have a service that returned an object with a constructor on it, so I made it just an object. Something to note is that services are created and managed in a singleton fashion, so every instance of that $alertService you pass in to any controller will be the same. It's an interesting way to share data, although $rootScope is recommended for that in most cases.
Here is the code:
app.factory('alertservice', [function() {
function Alert() {
this.alerts = [];
this.addAlert = function(alert) {
this.alerts.push(alert);
};
}
return {
Alert: Alert
};
}]);
app.controller('MainCtrl', function($scope, alertservice) {
var myAlert = new alertservice.Alert();
$scope.alerts = myAlert.alerts;
$scope.add = function() {
myAlert.addAlert({"text": "bar"});
};
});
Here are the important parts of the updated markup:
<body ng-controller="MainCtrl">
<div id="dynamicalert">
<alert ng-repeat="alert in alerts">{{alert.text}}</alert>
</div>
<button ng-click="add()">Add more alerts...</button>
</body>
EDIT: updated to reflect your request