jQuery element initialization in Angular - angularjs

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/

Related

Add class later request was completed with Angular loading bar

I'm working my frontend with angular and angular-loading-bar, in the controller I put this code.
$rootScope.$on("cfpLoadingBar:completed",function(){
$(".animated").addClass("fadeIn");
});
or
$scope.$on("cfpLoadingBar:completed",function(){
$(".animated").addClass("fadeIn");
});
When the all XHR requests have returned, I want to add a clase in my section content, but the code inside event don't run.
How is the correct way to achieve it?
Firstly, check that you use appropriate event name. For example, are you sure thet its name is cfpLoadingBar:completed? Maybe its a cfpLoadingBar::completed (its a very common pattern) or something else?
Second, ensure that you have to subscribe to this event using $rootScope. Maybe you have to subscribe for it in some concrete controller witj its own $scope?
And as a big suggestion: DO NOT USE JQUERY AND ANGULAR TOGETHER IN YOU CODE, DO NOT MESS IT UP!!! Angular has a built in possibility to work also as a jquery. All that you need is to call angular.element() which returns you an element as if would use jquery. In your case you can write angular.element(".animated").addClass("fadeIn"); and it will do the same thing, but in angular way
Yeah, I use both cfpLoadingBar::completed and cfpLoadingBar:completed but don't run this event.
In the other hand I only have one controller by one section, it ran but I needed add a main controller and registered this event and propagate up the event with $broadcast in my child controller.
This is code in MainController
$scope.$on('cfpLoadingBar:completed', function(event, data) {
angular.element(".animated").addClass("fadeIn");
});
And This is code in other Child Controller
$rootScope.$broadcast('cfpLoadingBar:completed');
it is the only way to achieve, I don't know why XD
Thanks Andrew this way is better angular.element()

Dynamically created element not firing click event

$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);

Access Angular Controller Defined Element

Is there a way to access the div which is in the controller div or the controller defined div without defining them with and class or id JUST using the $scope.
<div ng-controller="gridController">
<div></div> // < -- I want to access this element
</div>
To be a bit more specific does angular saves and gives access to the element DOM info which the ng-controller was called ?
A controller has no concept of the DOM, and it should stay that way or you run the very likely risk of writing untestable code. This is a part of the separation of concerns in the angular framework. A controller can be bound to multiple different elements or even to the controller function of different directives and there would be know way to tell them apart.
If you are attempting to do anything to the DOM you should be using a directive.
Given more information about what you want to accomplish with the element in question more guidance to reach your goal could be given.
You can access the element the controller is defined on by injecting the angular variable $element into your controller
angular.module('myApp')
.controller('SomeCtrl',['$scope', '$element', function($scope, $element) {
$scope.buttonClick = function () {
$element.find('div').css('background-color', '#f00');
}
}]);
If jQuery is included in your project, $element will be a jQuery object of the element your controller is defined on (the outer div in your example). You can use standard jQuery directives to access its sub elements. Accessing an unnamed child div would require you to know its position, or use some other criteria to target it.
If jQuery is not installed, angular includes jqlite which is a subset of jQuery allowing you to use most jQuery selectors to target elements, but lacking most of the other manipulation features.
However, it is generally considered bad practice to access, and especially manipulate the DOM from within a controller. Because of the way jQuery binds variables to html, if you make changes to the DOM from certain functions, angular may not pick these changes up and overwrite them when it next does a binding cycle. To avoid this, you should do most of your DOM manipulation from inside an angular directive. Sometimes however, its just easier to do it from the controller... If you are going to do this, you should learn about $apply and $digest.

init ng-controller when content is loaded dynamically in angular

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.

Is passing data between directives, controllers, and services using $emit and $broadcast bad practice?

Hi I'm a beginning Angular developer and I was wondering if the way I've passed data between controllers is bad practice or not.
I'm creating a seatmap widget whereby you click on a seat and it will display data in another part of the App using a different controller.
1) I have a directive that incorporates dynamic templating. Based on the model being passed (from an ng-repeat), it will create an event listener like so:
if(scope.seat.isAvailable) {
element.bind('click', function() {
scope.$emit('clickedSeat', scope.seat);
}
element.append(seatAvailableTemplate);
}
2) In my controller, I have the following listening for the click:
$scope.$on('clickedSeat', function(event, seat) {
seatSelectionService.broadcastSeatData(seat);
}
3) I have a service where I've passed in $rootScope which allows me to broadcast the data to a different controller:
var seatSelectionService = {};
seatSelectionService.broadcastSeatData = function(clickedSeat) {
seatSelectionService.seatData = clickedSeat;
$rootScope.$broadcast('seatSelected');
}
return seatSelectionService;
4) In the other controller, I have a listener which sets the $scope attribute, which renders {{selectedSeat}} in the view:
$scope.$on('seatSelected', function() {
$scope.selectedSeat = seatSelectionService.seatData;
$scope.apply();
}
Of course I have had to pass in seatSelectionService into both controllers for it to work.
Is there a better way to do this? Or is this way of passing data valid practice?
I would say less is more- if you can get rid of code do it- I would suggest you remove this in your controller unless you are doing something you are not showing, and put it in your directive, in step one:
$scope.$on('clickedSeat', function(event, seat) {
seatSelectionService.broadcastSeatData(seat);
}
$broadcast and $emit themselves aren't bad practice, but it does look like you've over complicated your app.
You can add services directly to directives - they can have controllers built into them that depend on the services.
So in your directive, add a controller that uses the seatSelectionService, something like:
seatSelectionService.setSeat( scope.seat )
Which cuts out one of the steps at least - your setSeat method would still want to do a $broadcast when it's done down to anything listening.
Directives can be really simple or really complex - they can simply be a quick way of outputting a repeated template, or a dynamic template with a controller and access to multiple services.
Something else you might want to look at is promises - I used to use $broadcast and $emit a lot more than I needed to, until I learnt how to use promises and defer properly

Resources