I want to generate a full standalone HTML page.
So far I've tried:
$compile(
angular.element('
<html>
<body>
<div>{{stuff}}</div>
<ul>
<li data-ng-repeat="item in list">
{{item}}
</li>
</ul>
</body>
</html>
')
)({
stuff: 'Things',
list: [
'item1',
'item2'
]
});
But that only returns a text element.
I've successfully used $interpolate for the variables alone, but that won't work for the ng-repeat and other directives.
How would I generate a fully compiled HTML page on the frontend?
If your question is "why would you do this?", think of a page creator interface, where the user might input some of the variables and expect an HTML page in response.
I'm not sure I completely understand your question, but I've created the snippet below. Is this what you want to achieve? An important thing I changed about your example, is that I created the scope for the link function by using $rootScope.$new(). It doesn't work by using simply a plain javascript object.
angular.module('test', [])
.controller('test', function($rootScope, $rootElement, $compile) {
var element = angular.element('<html><body><div>{{stuff}}</div><ul><li ng-repeat="item in list">{{item}}</li></ul></body></html>');
var scope = $rootScope.$new();
scope.stuff = 'Things';
scope.list = [
'item1',
'item2'
];
var result = $compile(element)(scope);
$rootElement.append(result);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="test"></div>
Related
I am using node and angularjs. I have a frame like page inside an ejs that is passed content to load into the includes dynamically.
<div ng-app="thisApp">
<div ng-controller='MainCtrl'>
{{ firstMessage }}
<div id='contentFromNode' ng-include='<%= pageContent %>'></div>
</div>
</div>
<script>
var thisApp = angular.module('thisApp', []);
thisApp.controller('MainCtrl', [ '$scope', function($scope) {
$scope.firstMessage = "Main Controller Working Fine";
}])
</script>
and then the passed content might be just an html page containing something like this:
<div ng-controller='NestedCtrl' id='content-type-container'>
{{ nestedMessage }}
</div>
<script>
thisApp.controller('NestedCtrl', [function(){
var nested = this;
nested.nestedMessage = "Nested Won't Work";
}])
</script>
So I have tried $scope within the NestCtrl instead of referencing this, I have tried moving the script tag above and below (ideally this get separated eventually anyway). I have tried aliasing the controllers, however my problem is the in registration of the controller itself as I get that great Error: [$controller:ctrlreg] error. The page is loading the content fine? Any ideas what I am doing wrong here?
Seems JQlite doesn't support this. You have to include jquery or lazy load the script. Refer
AngularJS: How to make angular load script inside ng-include?
I am using Angular 1.5.7 and am trying to see if I can push the value of an attribute within a directive used on several different pages to an array that lives in the controller.
I am pretty sure that I need to used transclusion in order to do this but I am stuck. Below is a simplified version of what I have so far:
about.html
<div ng-controller="MainCtrl as ctrl"
<div cd-header mypage="About">
<div>About Page</div>
</div>
</div>
contact.html
<div ng-controller="MainCtrl as ctrl"
<div cd-header mypage="Contact">
<div>Contact Page</div>
</div>
</div>
header.html
<div>{{mypage}}
<div ng-transclude></div>
</div>
cd-header.js
var cdHeader = function() {
return {
scope: {
mypage: "#"
},
transclude: true,
templateUrl: 'header.html',
link: function(scope) {
// Not sure but I think I might need a function here
}
}
}
module.exports = cdHeader;
MainCtrl.js
var MainCtrl = function($scope) {
var nav = [];
// Not sure how items that are pushed to the nav get to this point
}
module.exports = MainCtrl;
main.js
var app = angular.module("myapp", [
'about',
'contact',
])
.controller('MainCtrl', MainCtrl)
.directive('cdHeader', cdHeader)
I am able to get the value of the mypage attribute in the directive as well as its transcluded <div> to appear in the header but only for the current page in view.
The part I am missing is how to get all of the mypage values from each page into the header regardless of the current page in view. I am somewhat new to Angular and have read a lot but have not come across anything that explains how this can be done. Maybe this is achieved with a service? If so, I'm not sure how to go about it.
To clarify with a visual. This is what I see:
But this is what I want to see:
As you rightly pointed out, there are several ways to do it.
Perhaps you can pass the array from the MainCtrl as an attribute to the directive. For instance, nav-array="nav". However, before that, you need to set the array nav as such
var $scope = this;
this.nav = [];
The second option is to consume a service. Create an angular service, pass it as a dependency in the directive, and consume it.
Lets create an array in MainCtrl as $scope.headers = ['about':'About','contact':'Contact','home':'Home'] or create a factory/service to share the headers data and in header.html use ng-repeat to display the header name according to myPage value like below
<div ng-repeat="page in headers[myPage]">{{page}}
<div ng-transclude></div>
</div>
I need to render a angular directive, selecting it by appealing to a string previously defined at a variable (usually declared in the controller). Despite such a variable is accessible as an Angular expression, when I try to use for selecting a directive it doesn't work:
<!DOCTYPE html>
<html ng-app="app">
<body ng-controller="TextController">
<!-- item.dir is accessible: -->
<div>Value of item: {{item.dir}}</div>
<!-- It works. the directive "hello" is rendered -->
<div class="hello"></div>
<hello></hello>
Here you should see additional text:
<!-- Doesn't work item.dir is not recognized-->
<!-- as a class -->
<div class="{{item.dir}}"></div>
<!-- as an attribute-->
<div {{item.dir}}></div>
<!-- trying ng-class (it fails)-->
<div ng-class="item.dir"></div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.5/angular.min.js"></script>
<script>
var appModule = angular.module('app', []);
// The directive to render
appModule.directive('hello', function() {
return {
restrict: 'ACE',
template: '<div>works: Transcoded text</div>',
replace: true
};
});
appModule.controller('TextController', function ($scope) {
$scope.item = {dir: 'hello'}; // the name of the directive, the idea is to use it for referring to many future directives.
});
</script>
</body>
</html>
Here's a plunker of the code: http://plnkr.co/edit/tM73sY3vTfPFHmn6iCYE?p=preview
So, what I am missing? How do I achieve to get Angular using string interpolation when using a directive? Thanks!
For directives to work, Angular needs to compile your html (something automatically done when a page is loaded).
Having a way to control freely which directive to instantiate is a bit like pulling the rug under your feet and is atypical. One of the issue is that the compilation "destroy" the internal binding/watchers data and some of the original DOM and thus there is not enough information to "recompile" a DOM node.
NOTE: you cannot change attribute or element names (only attribute values) with angular using this type of binding: {{ }} But ng-class="..." and class="{{...}}" works.
I do not understand what you are trying to achieve exactly. If the intention is really about modifying the item.dir's value and having Angular "reconfigure" your application, it is "possible" but I highly suspect it will induce "state" drawbacks.
Nevertheless, here's a working "hack" that "remembers" the original DOM html and recompiles it when needed. This is done in 2 compilation phases: First phase is to restore the original bindings and a second phase that runs after the $digest cycle so the original bindings finished populating the class name from the scope (i.e. to have item.dir in effect). The drawback of course is if you made modifications to the enclosing DOM, this will wipe them! Alternatively, it might be possible to remember only specific attributes and revert "that" only while keeping other portion of the DOM intact (but might create other issues).
appModule.directive('forceRecompilation', ['$timeout', '$compile', function($timeout, $compile) {
return {
restrict: 'A',
link: function(scope, element, attr) {
var originalHtml = element.html();
scope.$watch(attr.forceRecompilation, function(){
// restore original HTML and compile that
element.html(originalHtml);
$compile(element.contents())(scope);
// wait for all digest cycles to be finished to allow for "binding" to occur
$timeout(function(){
// recompile with bounded values
$compile(element.contents())(scope);
});
});
}
};
}]);
...to be used as an enclosing tag of the section of the DOM to be acted upon. It will "revert & recompile" everything under it when the expression changes. (here "item.dir"):
<div force-recompilation="item.dir">
<div class="{{item.dir}}">
</div>
Plunker: http://plnkr.co/edit/TcMhzFpErncbHSG6GgZp?p=preview
In the plunker, there are 2 directives "hello" and "hello2". Change the text to "hello" and back to "hello2" to see the effect.
EDIT: The following is a directive that allows inserting markup to be compiled as described in a comment below. This is just a slightly modified version of Angularjs - inline directives with ng-bind-html-unsafe
angular.module('bindHtmlExample', ['ngSanitize'])
.controller('ExampleController', ['$scope',
function($scope) {
$scope.test = false;
$scope.dir = "ng-click";
$scope.clicked = function() {
$scope.test = !$scope.test
}
$scope.myHTML =
'I am an <b ng-show="test">invisible</b> HTML string with ' +
'links! and other <em>stuff</em>';
}
])
// modified plunker taken from https://stackoverflow.com/questions/18063280/angularjs-inline-directives-with-ng-bind-html-unsafe
//
// Allows an attribute's value to be evaluated and compiled against the scope, resulting
// in an angularized template being injected in its place.
//
// Note: This directive is prefixed with "unsafe" because it does not sanitize the HTML. It is up
// to the developer to ensure that the HTML is safe to insert into the DOM.
//
// Usage:
// HTML: <div unsafe-bind-html="templateHtml"></div>
// JS: $scope.templateHtml = '<a ng-onclick="doSomething()">Click me!</a>';
// Result: DIV will contain an anchor that will call $scope.doSomething() when clicked.
.directive('unsafeBindHtml', ['$compile',
function($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.unsafeBindHtml);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM element
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);
}
);
};
}
]);
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.5/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.5/angular-sanitize.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="bindHtmlExample">
<div ng-controller="ExampleController">
<p unsafe-bind-html="myHTML"></p>
(click on the link to see <code>ng-click</code> in action)
</div>
</body>
</html>
Apparently, the tg-dynamic-directive does the trick.
I have followed the documentation on setting up ngInclude and have it working (loading in HTML partials) and changing the partial when a select option is changed.
$scope.templates = [
{ name: 'Overview', url: 'partials/project/overview.html' },
{ name: 'Tasks', url: 'partials/project/tasks.html' }
];
$scope.template = $scope.templates[0];
----
<div ng-controller="Tasks">
<select ng-model="template" ng-options="t.name for t in templates">
<option value="">(blank)</option>
</select>
<section ng-include="template.url"></section>
</div>
However, I don't want to use a select menu to navigate. I want to use an unordered list. I tried using ngHref, but that doesn't seem to work. I can't find much documentation on binding an element to change an ngInclude partial, but maybe I'm searching for the wrong thing.
Any help on how I could use anchor tags to change the partial being loaded in on click would be great.
Here's the structure I was playing around with:
<ul class="header-menu">
<li><a ng-modal="template" ng-href="{{template[0]}}" class="selected">Overview</a></li>
<li><a ng-modal="template" ng-href="{{template[1]}}">Tasks</a></li>
</ul>
You just need to store the path to your template, and change it based on an action in your list. ngHref isn't related here, because it would load the template itself which isn't what you want.
See this demo plunker:
HTML:
<body ng-controller="MainCtrl">
<ul>
<li ng-repeat="template in templates">
{{template.name}}
<button ng-click="selectTemplate(template)">Go!</button>
</li>
</ul>
{{selectedTemplate}}
<div ng-include="selectedTemplate"></div>
</body>
Controller:
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.templates=[{
name:'First Template',
url:'template1.html'
}
,{
name:'Second Template',
url:'template2.html'
}
]
$scope.selectedTemplate=$scope.templates[0].url;
$scope.selectTemplate=function(template){
$scope.selectedTemplate=template.url;
}
});
But I really suggest you to check out to check out ui-router, as it sounds like you are trying to implement some tab system, and changing states for your app, and ui-router does that perfectly.
suppose I have this bit of html and javascript:
$scope.test = "hello<br/>world";
<div>{{ test }}</div>
angular will obviously render it as:
<div>hello<br/>world</div>
and that is exactly how it is supposed to work but, what if I would like it to actually render it as html markup rather than text?
I know this can lead to security problems, but I would just like to know out of curiousity.
Thanks in advance.
You can use the ng-bind-html directive in AngularJS.
<div ng-controller="ngBindHtmlCtrl">
<p ng-bind-html="trustedHtml"></p>
</div>
where
$scope.myHTML = "I am an <code>HTML</code>string with links!"
will be rendered accordingly.
For the security concerns involved in this, before you pass the HTML content to your scope variable myHTML, sanitize it with:
$scope.trustedHtml = $sce.trustAsHtml($scope.html);
and then use $scope.trustedHtml in your ng-bind-html directive as listed above.
You can create a filter as suggested here:
HTML :
<div ng-controller="MyCtrl">
<div ng-bind-html="my_html | to_trusted"></div>
</div>
This is how your javascript would appear:
var myApp = angular.module('myApp',[]);
angular.module('myApp')
.filter('to_trusted', ['$sce', function($sce){
return function(text) {
return $sce.trustAsHtml(text);
};
}]);
function MyCtrl($scope) {
$scope.my_html = '<div>hello<br/>world</div>';
}