For the context I'm building syntax highlighting for my angular (1.5.8) application. For syntax highlighting I use Prism.js which unfortunately can't highlight my code when I use ng-include in my HTML. Quite understandable since it introduces asynchronity. So to overcome the problem I'm creating an angular directive so that I can write something like this:
<prism lang="html">
<md-toolbar layout layout-align="end center"></md-toolbar>
</prism>
Then in my directive I'm runnin the contents of the directive through Prism.highlight(transclusion, Prism.languages[this.lang]) so far so good. It works, but the only problem is, that angular parses my transclusion beforehand and modifies my input html that it adds additional classes because of my used layout and layout-align directives.
Here lies my question. Can I tell angular that "do not parse this chunk of code"?
Edit: I tried to wrap the input in <pre></pre> but that didn't help. Angular still parsed it and added classes.
Edit2: While I'm writing this I have an idea to put html elements outside of angular context giving them unique id. Then writing <prism code-id="some-unique-id"> then the directive could fetch the dom elem referenced by that uid and include it in the dom. Well, ugly af but could work am I right?
Edit3: I'm extending the post with more code so that you get the whole picture
1: In styleguide.html
<!-- MATERIAL DESIGN -->
<div id="material">
<h1>Material design assets</h1>
<div ng-include="'./material.html'"></div>
</div>
2: In material.html
<section>
<h2>Dialog</h2>
<md-button class="md-accent">Open sample dialog</md-button>
<prism lang="html">
<md-toolbar class="md-primary">
<header class="md-toolbar-tools">
<h3 class="md-headline">{{ 'Dialog title' | translate }}</h3>
<!-- SPACER -->
<span flex></span>
<md-button class="md-icon-button" ng-click="ctrl.close()"><i class="material-icons">close</i></md-button>
</header>
</md-toolbar>
<md-dialog-content>
<div class="md-dialog-content">
<!-- Content here -->
</div>
</md-dialog-content>
<md-dialog-actions layout-padding layout layout-align="end center">
<!-- stuff here -->
</md-dialog-actions>
</prism>
</section>
3: In the component
class PrismHighlighter {
static get $descriptor() {
return {
controller: PrismHighlighter,
template: `
<pre>
<code class="language-{{$ctrl.lang}}">
<ng-transclude class="transclusion"></ng-transclude>
</code>
</pre>
`,
transclude: true,
bindings: {
lang: '#'
}
}
}
static get $inject() {
return ['$element'];
}
constructor($element) {
this.element = $element;
}
$postLink() {
const codeElem = this.element.find('code');
const transclusion = $(this.element).find('ng-transclude').html();
const hCode = Prism.highlight(transclusion, Prism.languages[this.lang]);
codeElem.html(hCode);
}
}
module.component('prism', PrismHighlighter.$descriptor);
4: And the output
Now you can clearly see that there are a lot of angular added things there what I don't want :/
Use ng-non-bindable directive around it.
For Angular 1.x you can just use:
<div ng-non-bindable>
</div>
For angular 2.x this check this post that shows how to do the same.
Reference:
https://docs.angularjs.org/api/ng/directive/ngNonBindable
Related
I'm having some issues getting my new Angular project off the ground. I'm going to break off pieces into services and such later on, but for now I just have a controller called DashCtrl in a module called sandpiper.
I want to make a list of div.card elements from the $scope.results array within DashCtrl, additional items append to $scope.results when add-button is clicked, and also to append its $scope.test to the header. What happens, after clicking the button a few times, is this: (Screenshot link because I'm new and don't have reputation points T_T)
$scope.pushit() works and ng-repeat winds up spitting out the proper number of items, but the titles are absent. $scope.test isn't getting read either. I've banged my head against the wall for a couple of hours now and even coded up a little bare-bones Angular test to make sure I wasn't out of my gourd... but I just cannot figure this one out.
Here's my JS (two scripts, minified into /build/sandpiper.min.js later)
var app = angular.module('sandpiper', []);
app.controller('DashCtrl',['$scope',function($scope){
$scope.test = "Header"
$scope.results = [
{
title: "Test Item 1",
file: "12345978-My-Test-Document.pdf",
type: "PDF",
tags: ['pdf','test','foo','bar'],
image: "static/img/pdf.png"
}
]
$scope.pushit = function(){
$scope.results.push({
title: "Test Item 1",
file: "12345978-My-Test-Document.pdf",
type: "PDF",
tags: ['pdf','test','foo','bar'],
image: "static/img/pdf.png"
})
}
}])
And here's my HTML (unrelated portions are omitted)
<!DOCTYPE html>
<html lang="en">
<head>
(...)
<!-- bower:js -->
<script src="../bower_components/jquery/dist/jquery.js"></script>
<script src="../bower_components/materialize/bin/materialize.js"></script>
<script src="../bower_components/angular/angular.js"></script>
<!-- endbower -->
<script src="/build/sandpiper.min.js"></script>
</head>
<body ng-app="sandpiper">
<main>
<div class="container" id="content-root">
<div id="dash-wrapper" ng-controller="DashCtrl">
<h3>Test {{ test }}</h3>
<a ng-click="pushit()" class="btn-floating btn-large waves-effect waves-light" id="add-button">
<i class="material-icons">add</i>
</a>
<nav>
(...)
</nav>
<div class="row" id="results-container">
<div class="col s12 m4 l3" ng-repeat="item in results">
<div class="card">
<div class="card-content">
<span class="black-text">{{ item.title }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
(...)
</body>
</html>
Any help would be greatly appreciated; thanks in advance. Hope it's not something really stupid...
(Also this is my first post here, so if I'm doing anything wrong let me know)
Well... the issue was obvious enough. I singled out my CSS includes (I'm using Materialize) and determined that that wasn't the issue-- it was actually that I was using Twig, a PHP template engine that also uses mustache notation for variables. My bindings were being stripped right out by the backend before they even reached the browser. Cannot believe I didn't realize that.
For anyone else who's having this issue, try using a different Angular delimiter or using a different tag_variable pattern for Twig.
So I am using ng-repeat to repeat some divs which show images out of my JSON file. What I want to do is that when I click on that image (whether its desktop or mobile) the image will scale. Now my problem is that when I want to create a click event on my image tag (which is inside that div that holds the ng-repeat), he doesn't do anything. He cant see the click.
I red something on the internet about issues with jquery and angular, but for me as a beginner its hard to understand what I have to do to make it work how I pleased. I just want to be able to put a jquery function on a image tag inside the ng-repeated divs, so I can manipulate the css from there.
I have a piece of the code posted below here, maybe I have to add something to my controller? I dont know, I am clueless at the moment. :-)
<section class="words">
<div class="colored-sidebar"></div>
<!-- content -->
<div class="previous-button"></div>
<div class="word-container" ng-controller="imageController as imageCtrl">
<h1><span>noun</span>words</h1>
<div class="category-body">
<p><span>noun</span>travel</p><hr>
<div class="category-section" ng-repeat="icon in imageCtrl.imageList.travel">
<!-- <div class="category-image" ng-include="icon.src"></div> -->
<div class="category-image">
<img src="{{icon.src}}" />
</div>
</div>
</div>
</section>
The angular file
(function() {
app.controller('imageController', function(){
this.imageList = imageJson;
});
var imageJson = {
//ALOT OF JSON DATA HERE//
};
})();
I hope this piece of code would be enough to help me :-)
Any tips are welcome, I love to learn this language better and also understand it better.
Thanks!
jQuery is not suitable here, because by the time you run your jQuery code inside jQuery.ready(), the elements in "category-image" class are not created yet.
For solution of your problem you can use two methods:
1) Use the "ng-click", as proposed before. You can also pass "$index" to function inside ng-click. This way you will know index of icon in imageList.travel that was clicked. But this way you will have no information about dom element.
2) Create a directive. The main difference between directives and controllers is that directive have information about dom object. You can treat element as typical jQuery object
JS:
app.directive('imageClick', [function () {
return {
link: function (scope, element, attr) {
element.on("click", function(e){
//do some stuff here
})
}
}
}]);
HTML
<section class="words">
<div class="colored-sidebar"></div>
<!-- content -->
<div class="previous-button"></div>
<div class="word-container" ng-controller="imageController as imageCtrl">
<h1><span>noun</span>words</h1>
<div class="category-body">
<p><span>noun</span>travel</p><hr>
<div class="category-section" ng-repeat="icon in imageCtrl.imageList.travel">
<!-- <div class="category-image" ng-include="icon.src"></div> -->
<div class="category-image">
<img image-click src="{{icon.src}}" />
</div>
</div>
</div>
</section>
I have the following directive in my project:
app.directive('eventSessionsList', function() {
return {
restrict: 'AEC',
scope: {
input: '=data'
},
templateUrl: 'directives/event-sessions-list.html'
};
});
The template looks like this:
<ul class="event-sessions-list">
<li ng-repeat="session in input.eventSessions">
<span class="date">{{ session.date }}</span>
<p class="info">
{{ session.length }} hr session # {{ session.venue }}</p>
</li>
</ul>
When I try to load the page it crashes with no errors (tested in both Safari and Chrome).
The mistake was a simple one, but to help you avoid it here's what I did wrong: The name of my CSS class on the UL element is the same as the name of my directive (angular equates hyphenated words and camel case). This means that angular interpreted the CSS class as a call to instance the directive. This created an infinite nesting loop.
To fix this problem I changed the name of the class from "event-sessions-list" to "sessions-list".
I hope this saves you tearing your hair out!
I had a very similar problem but instead of a clash with CSS classes (OP's case), I had it with HTML tags.
Just posting in case someone runs into this slightly different variation of the same root problem.
HTML
<div id="header">
<div id="header_main">
<nav></nav>
</div>
</div>
Nav Template
<div class="dark-blue-section main-color">
<div class="container">
<nav class="navbar" role="navigation">
Nav Component
angular
.module('common')
.component('nav', {
templateUrl: './nav.html',
});
Like OP said, simply renaming it will solve it.
NOTE: I did first try renaming from nav to navbar which is ALSO an HTML class if you look at the Nav Template HTML, but this did not seem to confuse AngularJS.
Not sure why CSS classes cause confusion, but HTML classes don't so maybe someone else can chime in there.
I have an AngularJs app with start up page as index.html, by default the projects view will be displayed and on top of the page I am showing a icon to show the todo items (for the logged-in user) which I am using bootstrap's data-toggle dropdown. The issue is whenever I click the todo link the partial view (todo.html) is not showing. BTW, I am new to the angular world so please forgive me if there is anything silly. Please see the code below:
Index.html
<!DOCTYPE html>
<html lang="en" ng-app="myApp">
<head></head>
<body>
<a data-toggle="dropdown" class="dropdown-toggle" ui-sref=".todo">
<i class="icon-tasks"></i>
<span class="badge badge-grey">4</span>
</a>
<div ng-view></div>
</body>
app.js
// For any unmatched url, redirect to /projects
$urlRouterProvider.otherwise("/projects");
//
// Now set up the states
$stateProvider
.state('projects', {
url: "/projects",
templateUrl: "/app/views/projects/projects.html",
controller: "projectController"
})
.state('projects.todo', {
url: "/todo",
templateUrl: "/app/views/todo/todo.html"
});
First of all replace ng-view with ui-view in the root template, cause it seems you want to use ui-router instead of ng-router.
Wrap the content of your template files with div of ui-view as a parent element.
/app/views/projects/projects.html
/app/views/todo/todo.html
<div ui-view>
... previously defined content ...
</div>
Let's say your view was
<div class="container">
<div class="page-header">
<h1>Title: {{title}}</h1>
</div>
</div
you need to add ui-view to the div
<div class="container" ui-view>
<div class="page-header">
<h1>Title: {{title}}</h1>
</div>
</div
or wrap your view with div containing ui-view descriptor in case your vie contains several tags.
I cannot show you an example since you did not provide content of view files.
/app/views/projects/projects.html
/app/views/todo/todo.html
The issue is that after fist template applying angular does not see the place to put new template anymore.
ui-router isn't really supposed to be used in this way. To integrate bootstrap with angular you want to look at UI Bootstrap - http://angular-ui.github.io/bootstrap/
Then to achieve your drop down, look at their basic examples. If you want to use separate view files to define your drop down content, you can use <div ng-include="'mycontent.html'"></div>.
ui-router is useful if you have a complex view hierarchy, where you are for example, looking for dynamic loading of children, while keeping parent states static.
In ui-router you defined all of this in the $stateProvider, so there you should define that you have a view that has another view belonging to it, example:
<!-- In index.html the main view that will have all the page views-->
<div id="main" ui-view="main"></div>
<!-- In todo.html with a partial with the dropdown code in dropdown.html -->
<h1> This is a nice todo drop down </h1>
<div id="todoDropDown" ui-view="todoDropDown"></div>
//In your app file
.state('projects.todo', {
url: '/todo',
views: {
'main#': {
templateUrl: '/app/views/todo/todo.html',
controller: 'TodoCtrl'
},
'todoDropDown#projects.todo': {
templateUrl: '/app/views/partials/dropdown.html'
}
}
})
"todoDropDown#projects.todo" This does the magic, it tells that this view has another view inside. And you can add controller to it and all other options you have in ui-router. In this way you can break up as much as possible reusable parts.
I have an angular template which looks like this...
<div ng-repeat="message in data.messages" ng-class="message.type">
<div class="info">
<div class="type"></div>
<div class="from">From Avatar</div>
<div class="createdBy">Created By Avatar</div>
<div class="arrowTo">
<div class="arrow"></div>
<div class="to">To Avatar</div>
</div>
<div class="date">
<div class="day">25</div>
<div class="month">Dec</div>
</div>
</div>
<div class="main">
<div class="content">
<div class="heading2">{{message.title}}</div>
<div ng-bind-html="message.content"></div>
</div>
</div>
<br />
<hr />
<br />
</div>
I have set up a JSfiddle to show the data being bound.
What I need to do is make the "from", "to" and "arrowTo" divs show conditionally, depending on the content of the data.
The log is is this...
If there is a "from" object in the data then show the "from" div and bind the data but don't show the "createdBy" div .
If there is no "from" object but there is a "createdBy" object then show the "createdBy" div and bind the data.
If there is a "to" object in the data then show the "arrowTo" div and bind it's data.
Or in plain English, if there is a from address, show it, otherwise show who created the record instead and if there is a to address then show that too.
I have looked into using ng-switch but I think I'd have to add extra markup which would leave an empty div if there was no data. Plus I'd need to nest switch directives and I'm not sure if that would work.
Any ideas?
UPDATE:
If I were to write my own directive (If I knew how!) then here is some pseudo code to show how I would want to use it...
<div ng-if="showFrom()">
From Template Goes Here
</div>
<div ng-if="showCreatedBy()">
CreatedBy Template Goes Here
</div>
<div ng-if="showTo()">
To Template Goes Here
</div>
Each of these would disappear if the function/expression evaluated to false.
Angular 1.1.5 introduced the ng-if directive. That's the best solution for this particular problem. If you are using an older version of Angular, consider using angular-ui's ui-if directive.
If you arrived here looking for answers to the general question of "conditional logic in templates" also consider:
1.1.5 also introduced a ternary operator
ng-switch can be used to conditionally add/remove elements from the DOM
see also How do I conditionally apply CSS styles in AngularJS?
Original answer:
Here is a not-so-great "ng-if" directive:
myApp.directive('ngIf', function() {
return {
link: function(scope, element, attrs) {
if(scope.$eval(attrs.ngIf)) {
// remove '<div ng-if...></div>'
element.replaceWith(element.children())
} else {
element.replaceWith(' ')
}
}
}
});
that allows for this HTML syntax:
<div ng-repeat="message in data.messages" ng-class="message.type">
<hr>
<div ng-if="showFrom(message)">
<div>From: {{message.from.name}}</div>
</div>
<div ng-if="showCreatedBy(message)">
<div>Created by: {{message.createdBy.name}}</div>
</div>
<div ng-if="showTo(message)">
<div>To: {{message.to.name}}</div>
</div>
</div>
Fiddle.
replaceWith() is used to remove unneeded content from the DOM.
Also, as I mentioned on Google+, ng-style can probably be used to conditionally load background images, should you want to use ng-show instead of a custom directive. (For the benefit of other readers, Jon stated on Google+: "both methods use ng-show which I'm trying to avoid because it uses display:none and leaves extra markup in the DOM. This is a particular problem in this scenario because the hidden element will have a background image which will still be loaded in most browsers."). See also How do I conditionally apply CSS styles in AngularJS?
The angular-ui ui-if directive watches for changes to the if condition/expression. Mine doesn't. So, while my simple implementation will update the view correctly if the model changes such that it only affects the template output, it won't update the view correctly if the condition/expression answer changes.
E.g., if the value of a from.name changes in the model, the view will update. But if you delete $scope.data.messages[0].from, the from name will be removed from the view, but the template will not be removed from the view because the if-condition/expression is not being watched.
You could use the ngSwitch directive:
<div ng-switch on="selection" >
<div ng-switch-when="settings">Settings Div</div>
<span ng-switch-when="home">Home Span</span>
<span ng-switch-default>default</span>
</div>
If you don't want the DOM to be loaded with empty divs, you need to create your custom directive using $http to load the (sub)templates and $compile to inject it in the DOM when a certain condition has reached.
This is just an (untested) example. It can and should be optimized:
HTML:
<conditional-template ng-model="element" template-url1="path/to/partial1" template-url2="path/to/partial2"></div>
Directive:
app.directive('conditionalTemplate', function($http, $compile) {
return {
restrict: 'E',
require: '^ngModel',
link: function(sope, element, attrs, ctrl) {
// get template with $http
// check model via ctrl.$viewValue
// compile with $compile
// replace element with element.replaceWith()
}
};
});
You can use ng-show on every div element in the loop. Is this what you've wanted: http://jsfiddle.net/pGwRu/2/ ?
<div class="from" ng-show="message.from">From: {{message.from.name}}</div>