Best way to extend third party libraries in AngularJs - javascript

I am using the angular bootstrap ui third party library as a dependency inside my angular app. I was just wondering what is the best way to add functionality to directives and controllers inside this library?
I understand that I can just edit the directives/controllers inside ui-bootstrap-tpls-0.11.0.js, but if I were to re pull the dependencies on a build server, it would wipe away my changes. If I were to update the library version it would also wipe away my changes. I'm looking for a clean way to extend functionality.
For example, if I want to do something like extend the datepicker directive to accept a customMethod or customData then use these within the linking function. What is the best way to do this?
<datepicker ng-model="dt" custom-method="myCustomMethod()"
custom-attribute="myCustomAttribute" min-date="minDate"
show-weeks="true" class="well well-sm"></datepicker>
Thanks in advance.

One option is to decorate the directive. Decoration looks something like:
angular.module('app', ['ui.bootstrap']).
config(function($provide){
// Inject provide into the config of your app
$provide.decorator('datepickerDirective', function($delegate){
// the directive is the first element of $delegate
var datepicker = $delegate[0];
// Add whatever you want to the scope:
angular.extend(datepicker.scope, {
customAttribute: '#',
customMethod: '#'
});
// Might want to grab a reference to the old link incase
// you want to use the default behavior.
var oldLink = datepicker.link;
datepicker.link = function(scope, element, attrs){
// here you can write your new link function
oldLink(scope, element, attrs);
};
return $delegate;
});
});

Related

$(document).ready alternative for AngularJS

I'm using a template called Gentelella and I'm trying to implement AngularJS into it. However, I'm having an issue with a certain Javascript file. In the end of this file, a $(document).ready function is called which initialises Javascript code that makes some changes in the HTML code. The issue is that the $(document).ready function is called too early, before the HTML is fully loaded.
This issue occurs probably because I'm using ngRoute and this injects the template html file into the ng-view of the index.html. When this happens, the DOM probably already announces a document ready before AngularJS has injected the template (=HTML).
So basically, I just need to find a way to call some code in a Javascript file once AngularJS has injected the template.
I attached some code to gain some insight into the issue:
Snippet of the custom.min.js
$(document).ready(function () {
init_sparklines(), init_flot_chart(), init_sidebar(), init_wysiwyg(), init_InputMask(), ...
});
Snippet of the main.js:
.config(function($routeProvider, $httpProvider) {
$routeProvider.when('/', {
templateUrl : 'dash.html',
controller : 'dash',
controllerAs: 'controller'
}).when('/login', {
templateUrl : 'login.html',
controller : 'navigation',
controllerAs: 'controller'
}).when('/plain_page', {
templateUrl : 'plain_page.html',
controller : 'dash',
controllerAs: 'controller'
}).otherwise('/');
$httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
})
Thanks in advance!
Many jQuery plugins depend on a workflow of 1. draw the DOM. 2. run an init() function to set up code against those DOM elements.
That workflow fares poorly in Angular, because the DOM isn't static: Angular sets up and destroys DOM nodes on its own lifecycle, which can overwrite event bindings or DOM changes made outside Angular. Document ready isn't particularly useful when you're using Angular, because all it indicates is that Angular itself is ready to start running.
To use Angular effectively, you have to get into the habit of initing code only when it's actually needed. So instead of a big bucket of init_foo(); init_bar(); on document.ready, you should have a Foo directive with its own init code, and a Bar directive with its own specific init code, and so on. Each of those directives should only modify the DOM created by that specific directive. This is the only safe way to ensure that the DOM elements you need to modify actually exist, and that you're not creating conflicts or unexpected interdependencies between directives.
To take one example: I'm guessing your init_flot_chart() crawls down through the DOM looking for a particular element inside of which it'll draw a flot chart. Instead of that top-down approach, create a directive:
angular.module('yourApp')
.directive('flotWrapper', function () {
return {
template: "<div></div>",
scope: {
data: '#'
},
link: function(scope, elem, attrs) {
var options = {}; // or could pass this in as an attribute if desired
$.plot(elem, scope.data, options); // <-- this calls flot on the directive's element; no DOM crawling necessary
}
};
});
which you use like this:
<flot-wrapper data="{{theChartData}}"></flot-wrapper>
...where theChartData is an object containing whatever data is to be drawn in the chart. (You can add other attributes to pass in whatever other parameters you like, such as the flot options, a title, etc.)
When Angular draws that flotWrapper directive, it first creates the DOM element(s) in the directive template, and then runs whatever is in its link function against the template's root element. (The flot library itself can be included via a plain old <script> tag so its plot function is available when the directive needs it.)
(Note that this wouldn't update automatically if the contents of theChartData change; a more elaborate example which also watches for changes and responds appropriately can be seen here.)
As you are using ngRoute, your controller will run when the page is loaded.
You can call an init() method when the controller starts to do whatever you want.
function MyCtrl($scope) {
function init() {
console.log('controller started!');
}
init();
}
As a side note, it is a best practice recommanded in John Papa Angular's guide.
Another possibily is to use the ng-init directive, for example:
<div ng-controller="MyCtrl" ng-init="myFunction()">...</div>
There is a lifecycle hooks named $postLink() in recent version of angular.js, Called after this controller's element and its children have been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation.Check the guide
This is related to angular digest cycle, it's about how angular works underneath the hood, data binding etc. There are great tutorials explaining this.
To solve your problem, use $timeout, it will make the code execute on the next cycle:
app.controller('Controller', function ($scope, $timeout) {
$scope.$on('$viewContentLoaded', function(event) {
$timeout(function() {
init_sparklines(), init_flot_chart(), init_sidebar(), init_wysiwyg(), init_InputMask(), ...
},0);
});
});
$document.ready(function() {
$scope.$on('$viewContentLoaded', function() {
$timeout(function() {
init_sparklines(), init_flot_chart(), init_sidebar(), init_wysiwyg(), init_InputMask(), ...
})
})
})
update Notice: sry guys, this is not a rigorous solution, plz see another answer I wrote

Can we use directives dynamically in AngularJS app

I am trying to call (or use) few custom directives in ionic framework, dynamic is like <mydir-{{type}} where {{type}} will come from services and scope variable, having values radio, checkbox, select etc, and created my directives as mydirRadio, MydirCheckbox, mydirSelect, But its not working.
Is their any good approach to get the dynamic html as per {{type}} in scope?
Long story short; no you can't load directives dynamically in that way.
There are a few options for what you can do. You can, as other answers have mentioned, pass your context as an attribute (mydir type="checkbox"). You could make a directive that dynamically loads another directive, as also mentioned by others. Neither of these options are imo every good.
The first option only works if you write the directive yourself, not when using something like ionic. It also requires you to write multiple directives as one, which can get very messy very quickly. This mega directive will become hard to test and easy to mess up when maintaining it in the future. Note that this is the correct way to pass data to a directive from the view, it's just not good for this specific use case.
The second option is problematic because obfuscates things a bit too much. If someone reads your html and sees a directive called dynamic that is given dynamic data... they have no idea what is going to happen. If they see a directive called dropdown that is given a list they have a fair idea of what the result will be. Readability is important, don't skimp on it.
So I would suggest something simpler that requires much less work from you. Just use a switch:
<div ng-switch="type">
<mydir-select ng-switch-when="select"></mydir-select>
<mydir-checkbox ng-switch-when="checkbox"></mydir-checkbox>
</div>
I dont understand why do you need dynamic directives.
Simple use single directive and change the template accordingly.
For example -
angular.module('testApp')
.directive('dynamicDirective', function($compile,$templateCache,$http) {
return {
restrict: 'C',
link: function($scope,el) {
//get template
if(radio){
$http.get('radio.html', {cache: $templateCache}).success(function(html){
//do the things
el.replaceWith($compile(html)($scope));
});
} else if(checkbox){
//load checkbox template
} //vice-versa
}
};
});
You can inject service variable in directive also.
a bit more code would help. I don't know, if its possible to do dynamic directives like the ones in a tag
<{dyntag}></{dyntag}>
but you also can use an expression like
<your-tag dynamic_element="{type}">...</your-tag>
which should have exactly the same functionality. In your case it would be like:
Your JSObject ($scope.dynamics):
{"radio", "checkbox", "select"}
and your HTML:
<div ng-repeat="dyn in dynamics">
<your-tag dynamic_element="{dyn}"></your-tag>
</div>
Yes, that's not a problem. You can interpolate your data using {{}} and in your directive compile a new element using that data:
myApp.directive('dynamic', function($compile, $timeout) {
return {
restrict: "E",
scope: {
data: "#var" // say data is `my-directive`
},
template: '<div></div>',
link: function (scope, element, attr) {
var dynamicDirective = '<' + scope.data + ' var="this works!"><' + scope.data + '>';
var el = $compile(dynamicDirective)(scope);
element.parent().append( el );
}
}
});
HTML:
<div ng-controller="MyCtrl">
<dynamic var="{{test}}"></dynamic>
</div>
Fiddle

Angular Directive with Options, Callbacks like jQuery Widgets?

I'm creating a custom control. I'm thinking of using Angular Directives.
But I see that directives require you to initialize your widget with html for example:
<mywidget></mywidget>
And in order to pass variables you also do this in html:
<mywidget title="my widget"></mywidget>
But I don't want this. I want to the initialization of my object programatically. Think jQuery widgets:
$('#container').mywidget({
title: 'my widget',
somecallback: function(event, ui){
// do stuff
}
});
Basically, I'd like to set options and set callbacks programatically. Is this available using angular directives or am I barking up the wrong tree?
Yes. You can not only pass just a string to a directive, but any kind of object or functions that are "programmatically" defined in a controller. For instance:
Your HTML:
<mywidget callback="someFunction()"></mywidget>
Your directive:
directive('mywidget', function() {
return {
scope: { callback: '&' }
};
});
Your upper controller:
$scope.someFunction = function() {
// programatically do something
}
Angular is declarative not imperative by nature which means you don't get to do that in an easy way and is for a good reason(separation of concerns)
making your directives clearly bound to your html helps readbility, maintenability and testability, now, you still get to configure your directive using the different binding types in your directives scope definition you can do this inside your directive
scope:{
configs:'='
}
which will create 2 way data binding with the config object which you can use to configure your directive and comunicate with the config object owner, or you can do
scope:{
configs:'&'
}
and then on your direcitves controller or link function do
scope.configs=scope.configs();
to get the object and then you can use it to configure, notice this doesn't create a 2way binding wit the object but it returns and object representation instead some thing similar goes to callback functions or events callback using the 2 way biding operator '='(not recommended but possible) or you can set it using the evaluation operation '&'
scope:{
onClick:'&'
}
and then on your controller/link function do
scope.onClick=scope.onClick();
and use it as
scope.onClick(params)
then when you declare your directive you do
more on this you can find here
https://docs.angularjs.org/guide/directive
https://docs.angularjs.org/api/ng/service/$compile

Angular.js template parsing hook

I'm wondering if there is a template parsing hook in Angular, that you can use globally or in specific controllers.
What I want to do is to implement a language and device specific (multidimensional) theme loader, that will dynamically grab any ressource link (img-tags, inline-styles) and redirect to the specific resource.
For example:
Someone implemented a template that shows some image:
<img src="images/my-super-image.jpg">
Now I want to grab the template and change the ressource to it's language specific correspondant:
<img src="theme/en_us/lowres/my-super-image.jpg">
The following things are important for me:
The one who generates the template doesn't need to take care of the themes, just uses the ressource as given in the first example
I don't want to use a directive, I want a global (App specific) solution --> best would be to have it in the run()-function of the app
I don't want to use look-up tables for the ressources, just want it to be highly dynamical
At the moment I'm not quite sure where to place such a parse-hook-function, nor how to get access to the current templates used on a page, to manipulate them before Angular provides them to the DOM.
I used some dirty hack, but I'm unhappy with it, because it will only be applied, when templates are already rendered and provided:
$(document).bind('DOMNodeInserted', function(event) {
if(angular.isDefined(event.originalEvent.originalTarget.innerHTML)) {
event.originalEvent.originalTarget.innerHTML = String(event.originalEvent.originalTarget.innerHTML).replace('src="images','src="' + imgPath);
}
});
Do you have any idea of how to do it? Thank you, guys!
Btw. I'm pretty new to Angular, so if you'd please be very descriptive, that would be kind. Thanks again.
You can use compile, Since angular only allow directives to modify DOM you need to create a directive
Here is an example
app.directive('myApp', function() {
return {
restrict: 'A',
replace: true,
link: function(scope, element, attrs) {
},
compile: function(tElement, tAttrs, transclude) {
tElement.find('img')[0]['src'] = "theme/en_us/lowres/" + tElement.find('img')[0] ['src'].split('/')[tElement.find('img')[0]['src'].split('/').length - 1];
}
};
});
Plunker

Add AngularJS directive at runtime

I am creating a game where the first thing that needs to happen is some state is loaded in from an external JSON file - the contents of one of my directives are dependent on this data being available - because of this, I would like to delay applying the directive until after the data has loaded. I have written the following:
window.addEventListener('mythdataLoaded', function (e) {
// Don't try to create characters until mythdata has loaded
quest.directive('character', function() {
return {
restrict: 'A',
scope: {
character: '#'
},
controller: 'CharacterCtrl',
templateUrl: 'partials/character.html',
replace: true,
link: function(scope, element) {
$(document).on('click', '#'+scope.character, function () {
$('#'+scope.character+'-popup').fadeToggle();
});
}
};
});
});
// Load in myth data
var myth_data;
$.getJSON("js/mythdata_playtest.json", function(json) {
myth_data = json;
window.dispatchEvent(new Event('mythdataLoaded'));
});
However, it appears that my directive's link function never runs - I'm thinking this is because angular has already executed the part of it's cycle where directives are compiled/linked by the time this directive gets added. Is there some way to force angular to compile this directive after it is created? I googled around a bit, and some people suggested adding $compile to the link function for similar issues - but the link function is never run, so that doesn't work for this case. Thanks!
It seems to me it would be better to always configure the directive, to do the JSON call in the directive, and attach logic to the element in the JSON call's success handler. This would, if I understand you correctly, do what you want.
AngularJS is meant as a framework, not a library, so using it in the way you mentioned is not recommended. Exactly as you mentioned, AngularJS does a lot of things for you when it runs. AngularJS, by default, runs on document loaded, and your $.getJSON callback arrives after that. When AngularJS runs it does all its magic with compiling the content and all that.
As a sidenote, it's also more the Angular way to use $http over $.getJSON.
I think you're thinking about this the wrong way. A major ideology in angular is that you set up declarative elements and let it react to the state of the scope.
What I think you might want to do is pass in what you need through the directive scope, and use other angular built in directives to hide or show your default ("non directive") state until the scope gets set from the controller for example.
Example:
You want a box to be hidden until an api call comes back. Your directive sets special styles on your element (not hidden). Instead of delaying to dynamically set your directive, you can pass in a scope var with a default value and use something like ng-show="data.ready" in your directive template to handle the actual dom stuff.

Categories

Resources