Override Angular Directive link - javascript

I have limited knowledge of Angular so please bear with me. I am in a situation where I can only modify one js file which is included BEFORE all of the Angular stuff. There is a directive that is causing a problem, yet I can not modify it directly. So I've tried to override it by adding the snippet below in a document ready block:
app.directive('selectionChange', function($rootScope){
return {
priority: 1,
terminal: true,
link: function(scope, el, attr) {
console.log('works');
};
};
});
I can see this directive added to the end of the invokeQueue, but it is never executed. How do I get this attached? Thanks!
UPDATE:
Sorry, let me try to clarify. Problem is, the original directive continues to fire, but the newly attached one does not (tested by using console.log and alert). The markup is something like this:
<html>
<head>
...
<script src="[the file I can modify].js"></script>
...
</head>
<body>
...
<script src="angular.js"></script>
<script src="directives.js"></script> // here is where the existing selectionChange directive is defined
...
</body>
</html>

Here's a plunker
$(function () {
var app = angular.module('app');
app.config(function ($provide) {
$provide.decorator('badDirective', function ($delegate) {
var badDirective = $delegate[0];
var link = function (scope, element) {
element.text('good');
}
var originalCompile = badDirective.compile || function () {};
badDirective.compile = function () {
originalCompile.apply(badDirective, arguments);
// compile returns link fn, directive 'link' property will be ignored anyway
return link;
}
return $delegate;
});
});
})
Doing it on 'ready' state (e.g. jQuery ready implementation) is the right thing. This way the code will be launched before the bootstrapping process (it will be queued on 'ready' via ng-app as soon as angular.js is loaded).
bad directive is just badDirective service internally which contains an array of DDO (because there can be several directives with the same name). And it can be decorated, as any other service.
Link function can be defined with either link or compile (it can return link) DDO properties. The second overrides the first, so always stick to compile when decorating directives.

In the file you can modify create a script tag with a (reference/definition) to your new directive and place that tag at the bottom of the body right after the troubled directive definition. By being the last one defined you'll ensure that is your directive the one rendered.

Related

Creating dynamic link with ui-sref inserting with highcharts

I'm trying to create a category xAxis that acts as links to other areas of the app. I want to avoid using href because that will reload everything. I have the core of the problem down, I think.
labels: {
formatter: function () {
let newvar = $compile(`<a class="link">${this.value} - ${vm.tableData.contentMain[this.pos].bothNeeded} - ${vm.tableData.contentMain[this.pos].percentageCompleted}</a> - `)($scope)
return angular.element(newvar[0]);
},
useHTML: true
}
My problem is that [object Object] shows on the page instead of the link. I believe I need to compile in order to get the ui-sref to work, which is why it is there. Any help creating these dynamic links greatly appreciated!
I am running Angular 1.6 in case that matters
UPDATE
Hopefully I am misunderstanding how $compile works. Here is a bad, non-working example OUTSIDE of highcharts
http://jsfiddle.net/HB7LU/31241/
From HighCharts Docs formatter returns String. So you have nothing to do with it. $compile generated DOM element that has been regestered to digest cycle. So its not out case.
However you can generate string something like:
formatter: function () {
return '<div style="width:70px" onclick="alert(\''+this.value+'\')">'+$scope.someValues[this.value]+'</div>';
},
Demo Fiddle
I want to avoid using href ...
you can build custom href by modifieng it based on this.vaue
As a side note:
When you print in DOM $compiled object, you get something like: {"0":{"ng-1505336047666":6},"length":1} where 0 is id of $scope and 1505336047666 element id that regenerates each build
Okay so if you provide a cloneAttachFn and then assign the result to the scope you can get it to do what you're looking for.
HTML
<div ng-controller="MyCtrl">
Hello, {{name}}!
{{newthing}}
</div>
Javascript
var myApp = angular.module('myApp',[]);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope, $compile) {
$scope.name = 'Superhero';
$scope.link = $compile(`<div>Test</div>`)(
$scope,
function(clonedElement, scope) {
console.log(clonedElement[0]);
$scope.newthing = clonedElement[0].outerText;
}
);
}
Here's the fiddle: http://jsfiddle.net/HB7LU/31285/

Directive at angularjs and custom method/html

I have this code:
<body ng-controller="testController">
<div test-directive transform="transform()">
</div>
<script type="text/ng-template" id="testDirective.html">
<div>
<p>
{{transform()}}
</p>
</div>
</script>
<script>
angular.module("Test", [])
.directive("testDirective", function() {
return {
templateUrl: "testDirective.html",
scope: {
transform: "&"
},
link: function(scope) {
}
};
})
.controller("testController", function($scope) {
$scope.transform = function() {
return "<a ng-click='somethingInController()'>Do Something</a>";
};
$scope.somethingInController = function() {
alert("Good!");
};
});
</script>
</body>
So basically what I want to accomplish is to create a directive with a method that will be called from the controller. And that method will do something with the values passed (in this example it does not receives nothing, but in the real code it does).
Up to that point is working. However, the next thing I want to do is create an element that will call a method in the controller. The directive does not knows what kind of element will be (can be anything) nor what method will be. Is there any way to do it?
Fiddle Example:
http://jsfiddle.net/abrahamsustaita/C57Ft/0/ - Version 0
http://jsfiddle.net/abrahamsustaita/C57Ft/1/ - Version 1
FIDDLE EXAMPLE WORKING
http://jsfiddle.net/abrahamsustaita/C57Ft/2/ - Version 2
The version 2 is now working (I'm not sure if this is the way to go, but it works...). However, I cannot execute the method in the parent controller.
Yes. However there is a few problems with your code. I will start by answering your question.
<test-directive transform='mycustommethod'></test-directive>
// transform in the directive scope will point to mycustommethod
angular.module('app').directive('testDirective', function() {
return {
restrict: 'E',
scope: {
transform: '&'
}
}
});
The problem is that printing the html will be escaped and you will get < instead of < (etc.). You can use ng-bind-html instead but the returned html will not be bound. You will need to inject the html manually (you can use jquery for this) in your link method and use var compiled = $compile(html)(scope) to bind the result. Then call ele.after(compiled) or ele.replace(compiled) to add it to your page.
I finally get to get it working.
The solution is combined. First of all, I needed to add another directive to parse the element I wanted:
.directive("tableAppendElement", function ($compile) {
return {
restrict: "E",
replace: true,
link: function(scope, element, attrs) {
var el = angular.element("<span />");
el.append(attrs.element);
$compile(el)(scope);
element.append(el);
}
}
})
This will receive the element/text that will be appended and then will registered it to the scope.
However, the problem still exists. How to access the scope of the controller? Since my directive will be used by a lot of controllers, and will depend on the model of the controller, then I just set scope: false. And with that, every method in the controller is now accessible from the directive :D
See the fiddle working here. This also helped me because now, there is no need to pass the transform method, so the controller can be the one handling that as well.

AngularJS: Custom $anchorScroll provider execution

I need to scroll to a specific anchor tag on page reload. I tried using $anchorScroll but it evaluates $location.hash(), which is not what I needed.
I wrote a custom provider based on the source code of $anchorScrollProvider. In it, it adds a value to the rootScope's $watch list, and calls an $evalAsync on change.
Provider:
zlc.provider('scroll', function() {
this.$get = ['$window', '$rootScope', function($window, $rootScope) {
var document = $window.document;
var elm;
function scroll() {
elm = document.getElementById($rootScope.trendHistory.id);
if (elm) elm.scrollIntoView();
}
$rootScope.$watch(function scrollWatch() {return $rootScope.trendHistory.id;},
function scrollWatchAction() {
if ($rootScope.trendHistory.id) $rootScope.$eval(scroll);
});
return scroll;
}];
});
Now, when I try to call the scroll provider in my controller, I must force a digest with $scope.$apply() before the call to scroll():
Controller:
//inside function called on reload
$scope.apply();
scroll();
Why must I call $scope.$apply()? Why isn't the scroll function evaluating in the Angular context when called inside the current scope? Thank you for your help!
I'm not sure what your thinking is behind using $rootScope.$eval(scroll) - since the scroll() function is already executing in a context where it has direct access to the $rootScope.
If I understand correctly, you want to be able to scroll to a particular element as denoted by an id which is stored in $rootScope.trendHistory.id.
When that id is changed, you want to scroll to that element (if it exists on the page).
Assuming this is a correct interpretation of what you are trying to achieve, here is how I might go about implementing it:
app.service('scrollService', function($rootScope) {
$rootScope.trendHistory = {};
$rootScope.$watch('trendHistory.id', function(val) {
if (val) {
elm = document.getElementById($rootScope.trendHistory.id);
if (elm) elm.scrollIntoView();
}
});
this.scrollTo = function(linkId) {
$rootScope.trendHistory.id = linkId;
}
});
This is a service (like your provider, but using the simpler "service" approach) which will set up a $watch on the $rootScope, looking for changes to $rootScope.trendHistory.id. When a change is detected, it scrolls to the element indicated if it exists - that bit is taken directly from your code.
So to use this in a controller, you'd inject the scrollService and then call its scrollTo() method with the ID as an argument. Example:
app.controller('AppController', function($scope, scrollService) {
scrollService.scrollTo('some_id');
});
In your question, you mention this needing to occur on reload, so you'd just put the call into your reload handler. You could also just directly modify the value of $rootScope.trendHistory.id from anywhere in the app and it would also attempt to scroll.
Here is a demo illustrating the basic approach: http://plnkr.co/edit/cJpHoSemj2Z9muCQVKmj?p=preview
Hope that helps, and apologies if I misunderstood your requirements.

Applying multiple custom directives to the same HTML tag in AngularJS

How can I assign multiple custom directives to the same tag using AngularJS?
My code is something along the lines of:
<div id="fixed" when-scrolled-up="loadMoreUp()" when-scrolled-down="loadMoreDown()">
<ul>
<li ng-repeat="i in items">{{i.id}}</li>
</ul>
</div>
Here, the loadMoreDown() function is not being triggered. (This is supposed to trigger on the user scrolling down). The following is the definition of the directive:
angular.module('scroll', []).directive('whenScrolledDown', function() {
return function(scope, elm, attr) {
var raw = elm[0];
elm.bind('scroll', function() {
if (raw.scrollTop + raw.offsetHeight >= raw.scrollHeight) {
scope.$apply(attr.whenScrolledDown);
}
});
};
});
I have pasted the complete code here : http://plnkr.co/edit/Pn9F2GuJvvkpnXD43km9?p=preview .
This code is supposed implements a reverse infinite scroll. What am I doing wrong here?
The problem of your example is that you execute the module code twice:
// create a new module 'scroll' ...
// and add a directive 'whenScrolledDown'
angular.module('scroll', []).directive('whenScrolledDown', ...)
// create a new module 'scroll' ...
// and add a directive 'whenScrolledUp'
angular.module('scroll', []).directive('whenScrolledUp', ...)
The first directive whenScrolledDown is no longer present since the module is cleared. The proper way to add another directive to the same module is:
// without the second parameter
angular.module('scroll').directive('whenScrolledUp', ...)
// or
var module = angular.module('scroll', [])
module.directive('whenScrolledDown', ...)
module.directive('whenScrolledUp', ...)

Call AngularJS function on document ready

Is there a way to call an Angular function from a JavaScript function?
function AngularCtrl($scope) {
$scope.setUserName = function(student){
$scope.user_name = 'John';
}
}
I need the following functionality in my HTML:
jQuery(document).ready(function(){
AngularCtrl.setUserName();
}
The problem here is my HTML code is present when page is loaded and hence the ng directives in the html are not compiled. So I would like to $compile(jQuery("PopupID")); when the DOM is loaded.
Is there a way to call a Angular function on document ready?
Angular has its own function to test on document ready. You could do a manual bootstrap and then set the username:
angular.element(document).ready(function () {
var $injector = angular.bootstrap(document, ['myApp']);
var $controller = $injector.get('$controller');
var AngularCtrl = $controller('AngularCtrl');
AngularCtrl.setUserName();
});
For this to work you need to remove the ng-app directive from the html.
The answer above although correct, is an anti-pattern. In most cases when you want to modify the DOM or wait for the DOM to load and then do stuff (document ready) you don't do it in the controller but in he link function.
angular.module('myModule').directive('someDirective', function() {
return {
restrict: 'E',
scope: {
something: '='
},
templateUrl: 'stuff.html',
controller: function($scope, MyService, OtherStuff) {
// stuff to be done before the DOM loads as in data computation, model initialisation...
},
link: function (scope, element, attributes)
// stuff that needs to be done when the DOM loads
// the parameter element of the link function is the directive's jqlite wraped element
// you can do stuff like element.addClass('myClass');
// WARNING: link function arguments are not dependency injections, they are just arguments and thus need to be given in a specific order: first scope, then element etc.
}
};
});
In all honesty, valid use of $document or angular.element is extremely rare (unable to use a directive instead of just a controller) and in most cases you're better of reviewing your design.
PS: I know this question is old but still had to point out some best practices. :)

Categories

Resources