I have some legacy jQuery code that looks like the following:
$(document).on('replace', 'div', function (e, new_path, original_path) {
// do stuff here
});
I'm trying to figure out how to move this code into an AngularJS consistent approach. The code above is running when index.html gets loaded. I'm trying to move initialization code into a directive. Currently, I'm calling the directive as shown here:
<body initialize-page>
... content goes here
</body>
My directive looks like the following:
.directive('initializePage', function ($document) {
return {
restrict: 'A',
link: function (element) {
console.log('initialization code goes here.');
}
};
})
However, I don't know what the AngularJS equivalent of 'on' is. I'd like to get away from using jQuery if at all possible.
Thanks!
Angular includes a subset of jquery it calls jqLite. The jqlite version of .on has these constraints:
on() - Does not support namespaces, selectors or eventData
So, we can use the Angular on, but slightly differently than you did in jQuery (namely without the selector).
A directive's link function's second parameter is the element the directive is applied to. So, while we can't specify a selector in on, we can use find on the element parameter to get your div. Then we can chain the on to that result. This gives us the following link function:
link: function (scope,element,attrs) {
element.find('div').on('replace', function (event) {
console.log("got event: ",event);
});
};
Here's a demo fiddle in which I used click instead of replace just because it's easier to show.
Related
I have a component, and i would like to inject it dynamically into my html.
I have a component like this:
angular.module('test1', []);
angular.module('test1').component('test1', {
templateUrl: 'components/test1/test1.template.html',
controller: function test1Controller($scope) {
}
});
the test1.template.html file looks like this:
<p>TEST 1</p>
on my controller i have this:
angular.module('myApp')
.controller('ctrlCtrl', function ($scope, $sce) {
$scope.tag = "<test1/>";
});
on my index.html, i have this:
<ng-bind-html ng-bind-html="tag"></ng-bind-html>
but the tag will not show up. I have tried writing literaly "'<p>hi!</p>'" on the ng-bind-html field, and the text "hi!" shows up on a paragraph, so i don't think this error is because of a typo.
I also tried to use $sce.trustAsHtml, but it didn't work neither :(
$scope.tag = $sce.trustAsHtml("<test1/>");
when i insert an input field, the trustAsHtml method does work, but when i try to inject my components dynamically, it just won't let me, please help D:
Why ng-include won't work?
Components need to be compiled before you can use them on the markup. Try editing the html of the app with the developer tools from your browser, by artificially injecting your component on the markup: it won't work.
How to dynamically include components?
you'll need to use directives, this tutorial (thanks to #Artem K.) is friendly to follow, but you can also read the angular's official documentation, it is a little hard to understand though.
Following the logic of the final example of the angular's official documentation, you can create a directive that compiles everything that is passed to it, like this:
// source: https://docs.angularjs.org/api/ng/service/$compile
angular.module('myApp')
.directive('my-compile', function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.compile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
});
and then, on your index.html, you'll have to invoke the directive, sending the the string containing the component's tag as an argument.
<div compile="tag"></div>
As #charlietfl and #Artem K. said, you have to understand the angular's $compile so, thanks guys for pointing me in the right direction :)
I'm wrapping a jQuery plugin inside a AngularJS directive. The way I would like to call the directive is for example:
<my-dialog data-trigger-id="myTriggerId">My dialog content...</my-dialog>
Inside my directive template it looks like this:
<button id="{{triggerId}}">Button text...</button>
I attach the event for the jQuery plugin (where you specify the trigger selector) inside the link function of my directive. My problem is that it works if I hardcode the id of the button inside the directive template like this:
<button id="myTriggerId">Button text...</button>
The generated html looks fine in the browser, which means that rendering an element with a dynamic id works. It's just that the jQuery plugin cannot find this element if I use the dynamic id but it works with the hardcoded version.
I also looked up AngularJS compile because it looks like at the point where the jQuery plugin wants to initialize the element doesn't exist yet.
Is there a gotcha I'm missing? Thanks!
Edit: I finally managed to simplify it down and create a jsfiddle example. If you run the example, you will see in the console that the element doesn't exist at the time I'm logging it but if you inspect the DOM, you will see that it's there and has the correct id.
However if you hardcode the id in the template (id=test instead of id={{elemId}}), the console log will show that one element could be found. I hope this helps to find a solution.
http://jsfiddle.net/a1nxyv8u/7/
The digest has not yet rendered in the DOM by the time you are calling you $("#test").length.
You need to add in a $timeout so that the digest will complete, then call your method
var app = angular.module('app', []);
app.directive('myDialog', ['$timeout', function ($timeout) {
return {
restrict: 'E',
template: '<button id="{{elemId}}" class="{{elemClass}}">Open dialog</button>',
link: function (scope, element, attrs) {
var selector = scope.elemSelector,
elemClass = (selector.indexOf('.') > -1) ? selector.substr(1) : '',
elemId = (selector.indexOf('#') > -1) ? selector.substr(1) : '';
scope.elemClass = elemClass;
scope.elemId = elemId;
$timeout(function() {
console.log('elem: ', $('#test').length);
});
// jQuery plugin init here but element doesn't seem to exist yet.
},
scope: {
elemSelector: '#'
}
}
}]);
Although it should be noted that you should try and alleviate any Id's at all and just use $(element) instead unless your jQuery absolutely needs the Id.
I'm integrating Angular into a web application and I noticed that the wrapper is messing with some of the CSS on the page. After researching into directives, I saw that custom directives can have a property called 'replace' to be set to true so that the templateUrl directly replaces instead of being wrapped in the directive's tags.
Is there a way to do the same with ng-view, or more generally any Angular directive? Thanks for the help!
I think your best bet is to decorate the original ng-view directive and add the replace:true switch to it. Note, replace is going to be - hah - replaced in the next major version of angularjs, but for now, this will work:
app.config(function($provide) {
$provide.decorator('ngViewDirective', function($delegate) {
var directive = $delegate[0];
directive.replace = true;
return $delegate;
});
});
And of course, the jsFiddle: Here
Is there a way to do the same with ng-view
I can't think of a way of doing that without editing the source of Angular or something. replace is for custom directives, not (necessarily) the built in ones.
or more generally any Angular directive? Thanks for the help!
Same thing. But you wouldn't want to do it with any directive, seeing how you could have multiple directives in a single element. I suppose you may want to do it with all template'd directives (since I think you can only have one per element), but, again, without editing the source of the directive I can't think of a way to do that.
Also, note that Angular has stated that replace "will be removed in next major release", so it's probably not best to rely on it.
You can, however, if you wish, create you own ngInclude/ngView-esque directive that renders templates without a wrapping tag.
A naive and probably problematic one (or at least inefficient) might look like:
app.directive('myRender', function ($compile, $http) {
return {
restrict: 'E',
link: function (scope, element, attrs) {
var newScope;
var newElement;
attrs.$observe('url', function (value) {
if (value) {
$http.get(value).then(function (result) {
if (newElement !== undefined) {
newElement.remove();
}
newElement = angular.element(result.data);
if (newScope !== undefined) {
newScope.$destroy();
}
newScope = scope.$new();
$compile(newElement)(newScope);
element.parent().append(newElement);
});
}
})
}
}
});
http://plnkr.co/edit/Vm96f2rQsT2PX1C3rgK8?p=preview
My first advice would be to adapt your css to angular if you want to use angular.
My other advice would to do your entire application in angular, and not add it after the fact.
I created a directive that should add a ng-change directive dynamically to all child input tags:
myApp.directive('autosave', function ($compile) {
return {
compile: function compile(tElement, tAttrs) {
return function postLink(scope, iElement, iAttrs) {
var shouldRun = scope.$eval(iAttrs.autosave);
if (shouldRun) {
iElement.find(':input[ng-model]').each(function () {
$(this).attr("ng-change", iAttrs.ngSubmit);
});
$compile(iElement.contents())(scope);
console.log("Done");
}
}; //end linking fn
}
};
});
The problem that I have is that the ng-change directive isn't running. I can see it that its added to the DOM element BUT not executing when value changes.
The strange thing is that if I try with ng-click, it does work.
Dont know if this is a bug on ng-change or if I did somehting wrong.
Fiddle is with ng-click (click on the input) http://jsfiddle.net/dimirc/fq52V/
Fiddle is with ng-change (should fire on change) http://jsfiddle.net/dimirc/6E3Sk/
BTW, I can make this work if I move all to compile function, but I need to be able to evaluate the attribute of the directive and I dont have access to directive from compile fn.
Thanks
You make your life harder than it is. you do'nt need to do all the angular compile/eval/etc stuff - at the end angular is javascript : see your modified (and now working) example here :
if (shouldRun) {
iElement.find(':input[ng-model]').on( 'change', function () {
this.form.submit();
});
console.log("Done");
}
http://jsfiddle.net/lgersman/WuW8B/1/
a few notes to your approach :
ng-change maps directly to the javascript change event. so your submit handler will never be called if somebody uses cut/copy/paste on the INPUT elements. the better solution would be to use the "input" event (which catches all modification cases).
native events like change/input etc will be bubbled up to the parent dom elements in the browser. so it would have exactly the same to attach the change listener to the form instead of each input.
if you want to autosave EVERY edit that you will have an unbelievable mass of calls to your submit handler. a better approach would be to slow-down/throttle the submit event delegation (see http://orangevolt.blogspot.de/2013/08/debounced-throttled-model-updates-for.html ).
if you want to autosave EVERY edit you skip your change handler stuff completely and suimply watch the scope for changes (which will happen during angular model updates caused by edits) and everything will be fine :
scope.watch( function() {
eElement[0].submit();
});
In my AngularJS app, there's several points at which I want to wait for a $scope to be processed into the DOM, and then run some code on it, like a jQuery fadeIn, for example.
Is there a way to listen for a "digestComplete" message of some sort?
My current method is: immediately after setting whatever $scope variables I want rendered, use setTimeout with a delay of 0 ms, so that it will let the scope finish digesting, and then run the code, which works perfectly. Only problem is, I very occasionally see the DOM render before that setTimeout returns. I'd like a method that is guaranteed to fire after digest, and before render.
In this jQuery fade-in-and-out fiddle (which I found it on the JSFiddles Examples wiki page), the author defines a "fadey" directive and performs the jQuery fadeIn (or fadeOut) in the directive's link function"
<li ng-repeat="item in items" fadey="500">
...
myApp.directive('fadey', function() {
return {
restrict: 'A',
link: function(scope, elm, attrs) {
var duration = parseInt(attrs.fadey);
if (isNaN(duration)) {
duration = 500;
}
elm = jQuery(elm); // this line is not needed if jQuery is loaded before Angular
elm.hide();
elm.fadeIn(duration)
Another possible solution is to use $evalAsync: see this comment by Miško, in which he states:
The asyncEval is after the DOM construction but before the browser renders.
I believe that is the time you want to attach the jquery plugins. otherwise
you will have flicker. if you really want to do after the browser render
you can do $defer(fn, 0);
($defer was renamed $timeout).
However, I think using a directive (since you are manipulating the DOM) is the better approach.
Here's a SO post where the OP tried listening for $viewContentLoaded events on the scope (which is yet another alternative), in order to apply some jQuery functions. The suggestion/answer was again to use a directive.
Alternatively, this example will work the same way as an AngularJS built-in ng-show directive, except it will fade-in or fade-out based on AngularJS condition:
<li ng-repeat="item in items" ng-ds-fade="{condition}">
<!-- E.g.: ng-ds-fade="items.length > 0" -->
...
myApp.directive('ngDsFade', function () {
return function(scope, element, attrs) {
element.css('display', 'none');
scope.$watch(attrs.ngDsFade, function(value) {
if (value) {
element.fadeIn(200);
} else {
element.fadeOut(100);
}
});
};
});
Source:
http://www.codeproject.com/Articles/464939/Angular-JS-Using-Directives-to-Create-Custom-Attri
If all you want is to run some jQuery stuff why not try the Angular-UI jQuery Passthrough?