I'm working with a form that needs to bind HTML to a Rich Text Editor. The best way to store this HTML content would be an HTML file.
I can't quite figure out how to load an HTML template from a file and assign it to a variable.
Directives seem to do be able to do this when working with templateUrl. Was wondering if this there is any low level api in angular to achieve the same thing inside of a controller
Using $templateRequest, you can load a template by it’s URL without having to embed it into your HTML page. If the template is already loaded, it will be taken from the cache.
app.controller('testCtrl', function($scope, $templateRequest, $sce, $compile){
// Make sure that no bad URLs are fetched. You can omit this if your template URL is
// not dynamic.
var templateUrl = $sce.getTrustedResourceUrl('nameOfTemplate.html');
$templateRequest(templateUrl).then(function(template) {
// template is the HTML template as a string
// Let's put it into an HTML element and parse any directives and expressions
// in the code. (Note: This is just an example, modifying the DOM from within
// a controller is considered bad style.)
$compile($("#my-element").html(template).contents())($scope);
}, function() {
// An error has occurred
});
});
Be aware that this is the manual way to do it, and whereas in most cases the preferable way would be to define a directive that fetches the template using the templateUrl property.
All templates are loaded into a cache. There is an injectable $templateCache service you can use to get access to the templates:
app.controller('testCtrl', function($scope, $templateCache){
var template = $templateCache.get('nameOfTemplate.html');
});
Related
I am using HTML5 mode in AngularJS and I have a <base href="/some/base/path" /> defined in the <head> section of my HTML file.
What is the best way of reading this value in AngularJS? I understand that you can just read this HTML tag with jQuery, but something tells me this is not the most elegant way of doing it.
What I want to achieve
I would like to compose URL dynamically from controller. For instance, current page is http://localhost/myapp/some/custom/page/ where http://localhost/myapp is a base url. I want to build http://localhost/myapp/another/page url in angular controller by doing the following
baseUrl + `/another/page`
AngularJS doesn't really have any functions for extracting attributes from tags. However, there are a workaround. But the pure javascript way I added at the bottom of the answer might be the best solution for you.
Let's say you change the base tag:
<base ng-controller="MyController" data-href="/some/base/path">
In your controller you can do the following to access data-href:
app.controller('MyController', function ($scope, $attrs) {
console.log($attrs.href); // Prints '/some/base/path'
});
Or you can do it in pure javascript with:
var url = document.getElementsByTagName('base')[0].getAttribute('href');
I know that if I want to create a reusable item, such as a date picker, then creating it as a Directive is recommended.
However, let's say that on my homepage, I have a Welcome section that displays the quote of the day with a background image that comes from a Rest service. Should this be a Directive that can encapsulate the markup and controller logic? Or should it be a simple AngularJs Controller that binds to markup in my index.html?
What constitutes whether or not something should be created as a Directive?
Directive is only a wrapper for a controller. It means if you have a controller you can use it. But you also may use the same controller as a controller of a directive for example instead of link function use controller.
This allow as to draw clear line where to use directive and where to use a controller.
We have to use Controller if we want to reproduce logic of piece of HTML markup. When we want to use the same $scope assignments, the same functions inside $scope, ... but HTML markup is always different for every other place where we use this controller.
We have to use directive when we have same logic in a controller of a directive and same HTML markup.
So in your case it is definitely a directive.
This is my own common sense of course, and may not be ideal.
There are three things you will require to implement this functionality:
AngularJS Template a.k.a. Markup to display quote with an image next to it.
AngularJS service to encompass the REST call in order to fetch above details from the server.
AngularJS controller to consume the AngularJS service to feed the data back the template (point 1) to update it accordingly after every rest call.
So the fact is you can achieve this without even writing an AngularJS Directive but what if you need to replicate the same feature in many places. In that sense, you will probably have to copy the same template somewhere else which will again need a separate controller to consume the same service (as using the same controller multiple times in the DOM is not recommended and a bad practice).
With the Directive API, you can put the markup in a directive template and consume the service in a directive controller to render the UI. So the next time if you want multiple instance of the widget, you just have to inject the directive, that's it - rest will work without any issue.
App = angular.module('App', []);
App.directive('welcomeQuote', function(QuoteService) {
return {
restrict: 'E',
template: '<div><img ng-src="{{quote.img}}" /><span ng-bind="quote.title"></span></div>',
controller: function(scope) {
// returns {img: 'angular.png', title: 'AngularJS';
QuoteService.fetch().then(function(data) {
scope.quote = data;
});
}
}
});
App.factory('QuoteService', function($http) {
return function() {
fetch: function() {
return $http.get('http://quote-server.com/new')
}
};
});
Finally you can use the widget as:
<welecome-quote></welcome-quote>
I want to modify web pages' behavior using angularjs and greasemonkey. I want to know, what's the best way to do it? Should I use jquery to inject attributes like "ng-*" to DOM elements before I can write some angular code? Or can I solely stick to angularjs?
Thanks.
There's a general answer about dynamically modifying AngularJS content in the DOM from JavaScript code here:
AngularJS + JQuery : How to get dynamic content working in angularjs
To sum up, when you put ng-* attributes into the DOM from JavaScript code, they won't automatically get hooked up; but AngularJS provides the $compile function for hooking up new HTML content with AngularJS attributes from JavaScript.
So what does this mean when it comes to Greasemonkey/Userscript?
For the purposes of this I'm assuming that your Greasemonkey script is modifying an existing page that already uses AngularJS, and the AngularJS content you want to add uses some of the variables or functions in AngularJS scopes already on that page.
For those purposes:
Get a reference to $compile from AngularJS' dynamic injection system
Get a reference to the AngularJS scope that you want your HTML code to be connected to
Put your HTML code with ng-* attributes in a string and call $compile on it and the scope.
Take the result of that and put it into the page using the usual jQuery-style ways.
To illustrate, here's a little script for CERN's Particle Clicker game, which adds a stat under the 'workers' section.
$(function () { // Once the page is done loading...
// Using jQuery, get the parts of the page we want to add the AngularJS content to
var mediaList = $('ul.media-list');
var medias = $('li.media', mediaList);
// A string with a fragment of HTML with AngularJS attributes that we want to add.
// w is an existing object in the AngularJS scope of the
// <li class="media"> tags that has properties rate and cost.
var content = '<p>dps/MJTN = <span ng-bind="w.rate / w.cost * 1000000 | number:2"></span></p>';
// Invoke a function through the injector so it gets access to $compile **
angular.element(document).injector().invoke(function($compile) {
angular.forEach(medias, function(media) {
// Get the AngularJS scope we want our fragment to see
var scope = angular.element(media).scope();
// Pass our fragment content to $compile,
// and call the function that $compile returns with the scope.
var compiledContent = $compile(content)(scope);
// Put the output of the compilation in to the page using jQuery
$('p', media).after(compiledContent);
});
});
});
** NB: Like any AngularJS function that uses its dependency injection,
.invoke uses the parameter names of the function you pass to it
determine what to inject, and this will break if you're using a minifier that changes the parameter names.
To avoid this you can replace
.invoke(function($compile) { ... });
with the form
.invoke(['$compile', function($compile) { ... }]);
which won't break if the minifier changes the parameter name to something other than $compile.
Little brief: I'm using AngularJs with Meteor+Blade without using Meteor_angularjs package for meteor. Blade constructs the body of the page in the server then I manually bootstrap angular in the client.
Blade has template files available under Template['template_name'] so they can be easily rendered on the client. I would like to do something like:
div(ng-include='content.blade')
// HTML => <div ng-include='content.blade'></div>
and somehow make it work.
To keep compatibility and not creating new directives I thought it could be possible to intercept the XHR requests angular makes to static templates and add the condition
if(URI ends with '.blade') then
name <- strip '.blade' in URI
return Template[name]()
Which should return the compiled HTML for that template.
UPDATE:
Coincidentally I ran into $templateCache and now I think it's the way to go.
I created a 'ngMeteor' module that I'll use for meteor-angular integration.
angular.module 'ngMeteor',[], ->
throw 'Meteor object undefined.' unless Meteor? # Is it fine to put here?
angular.module('ngMeteor.blade',['ngMeteor']).
run ($templateCache) ->
$templateCache.put "#{name}.blade", render() for own name, render of Template
In my app:
angular.element(document).ready ->
angular.bootstrap document, ['app']
app = angular.module 'app', ['ngMeteor.blade'], ->
app.controller 'mainCtrl', ($scope,$templateCache) ->
$scope.content = $templateCache.get "content.blade" # Works!!
Blade(body.blade):
#main(ng-controller='mainCtrl') {{ content }}
Now it works, I can get the rendered template from the controller after injecting $templateCache and geting the template by its name but ng-include still won't work.
My previous update in the question was actually the correct answer, ngInclude didn't work for me because of div(ng-include="'content.blade'") ... yes, the inner quotes! its like the Nth time I have that problem.
In resume the answer is:
angular.module('blade').
run ($templateCache) ->
$templateCache.put "#{name}.blade", render() for own name, render of Template
Template is the meteor global variable where blade will store templates ready to be rendered, then with $templateCache I put the rendered templates with the corresponding names/ids, that way Angular can use them.
EDIT: Based on this question I created a meteor package ng-meteor for braceless angular development in meteor.
I've been using directives in AngularJS which build a HTML element with data fetched from the $scope of the controller. I have my controller set a $scope.ready=true variable when it has fetched it's JSON data from the server. This way the directive won't have to build the page over and over each time data is fetched.
Here is the order of events that occur:
The controller page loads a route and fires the controller function.
The page scans the directives and this particular directive is fired.
The directive builds the element and evaluates its expressions and goes forward, but when the directive link function is fired, it waits for the controller to be "ready".
When ready, an inner function is fired which then continues building the partial.
This works, but the code is messy. My question is that is there an easier way to do this? Can I abstract my code so that it gets fired after my controller fires an event? Instead of having to make this onReady inner method.
Here's what it looks like (its works, but it's messy hard to test):
angular.module('App', []).directive('someDirective',function() {
return {
link : function($scope, element, attrs) {
var onReady = function() {
//now lets do the normal stuff
};
var readyKey = 'ready';
if($scope[readyKey] != true) {
$scope.$watch(readyKey, function() {
if($scope[readyKey] == true) {
onReady();
}
});
}
else {
onReady();
}
}
};
});
You could use $scope.$emit in your controller and $rootScope.on("bradcastEventName",...); in your directive. The good point is that directive is decoupled and you can pull it out from project any time. You can reuse same pattern for all directives and other "running" components of your app to respond to this event.
There are two issues that I have discovered:
Having any XHR requests fire in the background will not prevent the template from loading.
There is a difference between having the data be applied to the $scope variable and actually having that data be applied to the bindings of the page (when the $scope is digested). So if you set your data to the scope and then fire an event to inform the partial that the scope is ready then this won't ensure that the data binding for that partial is ready.
So to get around this, then the best solution is to:
Use this plugin to manage the event handling between the controller and any directives below:
https://github.com/yearofmoo/AngularJS-Scope.onReady
Do not put any data into your directive template HTML that you expect the JavaScript function to pickup and use. So if for example you have a link that looks like this:
<a data-user-id="{{ user_id }}" href="/path/to/:user_id/page">My Page</a>
Then the problem is that the directive will have to prepare the :user_id value from the data-user-id attribute, get the href value and replace the data. This means that the directive will have to continuously check the data-user-id attribute to see if it's there (by checking the attrs hash every few moments).
Instead, place a different scope variable directly into the URL
My Page
And then place this in your directive:
$scope.whenReady(function() {
$scope.directive_user_id = $scope.user_id;
});