Angular directive crashing browser - javascript

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.

Related

Problem with ng-if in ng-include html template from root controller

I come from Angular9 and am really used to it, and I have been asked to work a bit on an AngularJS project, which I have never experienced. So I am really struggling with the whole app structure.
My issue is simple: I have a sub-navbar.html template directly injected in my root.html template using ng-include, and I would like to condition the display of one section of this sub-navbar with 'ng-if' (not just hide the section, I don't want it there at all).
I have a backend call which sends me a boolean according to whether the connected user can see the section or not.
The problem I have is that my section is actually never active even when the boolean is 'true'.
Things I tried:
Change the priority of the ng-if and 'ng-controller' directives ---> Broke the app
Add a new 'subnavbar-controller' and declare it as a .state in the app.js ---> Didn't work
Create a custom directive ---> Can't figure out hw the work apparently, didn't work
I unfortunately can't copy all my code, but here are the main pieces I'm working on:
app.js: (I wrote nothing concerning this '$rootScope.adminSection' in the '.run()' function and also tried the same approach directly calling the service without the '$onInit')
$urlRouterProvider.otherwise("/orders");
$stateProvider
.state('root', {
abstract: true,
url: '',
views: {
'': {
templateUrl: "view/root.html",
controller: ['$rootScope', 'AdministratorService', function ($rootScope, AdministratorService) {
const vm = this;
vm.$onInit = function() {
AdministratorService.getAdminSection().then(function (result) {
$rootScope.adminSection = result;
}
)
};
}]
}
}])
root.html:
<div ui-view="root_header"></div>
<div class="row" style="min-height: 600px">
<div class="col-md-2">
<br/>
<div ng-include="'view/subnavbar.html'"></div>
</div>
<div class="col-md-10">
<div ui-view></div>
</div>
</div>
<div ng-include="'view/footer.html'"></div>
subnavbar.html:
<ul class="nav nav-pills nav-stacked" role="tablist">
<li></li>
<li ui-sref-active="active"><a ui-sref="root.contracts"></a><div class='arrow' aria-hidden='true'></div>
</li>
<li ng-if="$rootScope.adminSection" ui-sref-active="active"><a ui-sref="root.administrator">
</a><div class='arrow' aria-hidden='true'></div></li>
<li ui-sref-active="active"><a ui-sref="root.users"></a><div class='arrow' aria-hidden='true'>
</div></li>
</ul>
Any help welcome, thanks in advance !
There are several ways this can work. The reason it is not working now is that $rootScope in the template is not defined. Try, in your template, to replace $rootScope.adminSection with just adminSection.
ngInclude directive creates a new scope which inherits from $rootScope. Therefore, variables in $rootScope should be directly accessible from the template.

Telling angular not to parse certain HTML block

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

angularjs vs twitter bootstrap tabs javascript

I'm making my first steps with angularJS. In the "code school" video (http://campus.codeschool.com/courses/shaping-up-with-angular-js/level/2/section/2/video/1) there is a sample code that makes tabs in angular:
HTML
<section class="tab" ng-controller="TabController as tab">
<ul class="nav nav-pills">
<li ng-class="{active:tab.isSet(1)}">
<a href ng-click="tab.setTab(1)">Description</a></li>
<li ng-class="{active:tab.isSet(2)}">
<a href ng-click="tab.setTab(2)">Specs</a></li>
<li ng-class="{active:tab.isSet(3)}">
<a href ng-click="tab.setTab(3)">Reviews</a></li>
</ul>
<div ng-show="tab.isSet(1)">
<h4>Description</h4>
<blockquote>{{product.description}}</blockquote>
</div>
<div ng-show="tab.isSet(2)">
<h4>Specs</h4>
<blockquote>Shine: {{product.shine}}</blockquote>
</div>
<div ng-show="tab.isSet(3)">
<h4>Reviews</h4>
<blockquote></blockquote>
</div>
</section>
JavaScript:
app.controller('TabController', function(){
this.tab = 1;
this.setTab = function(newValue){
this.tab = newValue;
};
this.isSet = function(tabName){
return this.tab === tabName;
};
});
I know that twitter bootstrap has its own JavaScript for managing dynamic tabs (http://getbootstrap.com/javascript/#tabs).
My question is: is angularjs using bootstrap javascript here? I guess not. And if not (while this means angular is using only bootstrap's CSS), then why is angular reinventing the wheel = implementing new code that does the same thing as bootstrap's javascript code? I mean, why writing different code that does the same stuff, why not to use existing code?
Maybe it's just a matter of this tutorial - but is there a way to make angular use native bootstrap's javascript?
bootstrap provides one-way binding data. Howerver, angularJS supplies two-way binding. I suggest you should take a look at angular-ui. If you want use bootstrap with angularjs, you shoud search keyword "custom directive angularjs".
Reinventing the wheel? They are only using ng-show, which just changes the display style in your element to none.
Angular has no problem to use anyone scripts, you only have to do it the angular way. In this case it is called directives (most cases when you are going to manipulate DOM this is the way).
So for angular directives you can use template or templateUrl, template you give a string, templateUrl you give a file path. In your case I recommend you to place a new html file and write there your tabs content.
so in you tabs.html file
<div class="tab-content">
<div class="tab-pane active" class="home">...</div>
<div class="tab-pane" class="profile">...</div>
<div class="tab-pane" class="messages">...</div>
<div class="tab-pane" class="settings">...</div>
</div>
your directive should look something like this, according to bootstrap docs
myapp.directive('theNameOfMyDirective', function () {
return {
restrict: 'E',
replace: true,
templateUrl: 'the path to my html file', you can use only template too and writte your html as string, but in your case I think it is more clean if you do it in a diferent file
link: function (scope,element){
// angular.element() this is similar to $() in Jquery or Jquery()
angular.element(element).find('.profile').on('click', function (e){
e.preventDefault()
$(this).tab('show');
});
angular.element(element).find('.home').on('click', function (e){
e.preventDefault()
$(this).tab('show');
});
//some other tabs
}
}
});
since we restrict our directive to be E (Element), we have to add this html to render our tabs wherever we need them
<theNameOfMyDirective></theNameOfMyDirective>
for more info of custom directives http://tutorials.jenkov.com/angularjs/custom-directives.html

angularjs: why is my directive as class specified by ng-class not recognized?

plunker
i need to include a directive as a class:
and i need to use this pattern:
ng-class='{"some-directive":1'}
but unfortunately the directive can only be registered in the pattern below (try it in the plunker link above):
class='some-directive'
The main problem is I want to register(include) the directive only to the "rename" option of my context menu:
<li ng-class="{'renameDirective':(menu.name == 'Rename')}" ng-repeat="menu in contextmenu
How would I achieve that?.. what's tougher is that I want to add an argument in renameDirective.. maybe like this :
ng-class="{'renameDirective: directiveArg':(menu.name == 'Rename')}"
is something like this: <div renameDirective="directiveArg"></div>
UPDATE:
As a wordy workaround, the code below can temporarily solve the issue. It is open for more improvements (I guess taking advantage of the ng-class directive is best, shortest/ cleanest approach).
<ul>
<li ng-repeat="menu in contextmenu">
<div ng-switch on="menu">
<div ng-switch-when="Rename">
<button some-directive="Rename">button{{$index}}</button>
</div>
<div ng-switch-default>
<button>button{{$index}}</button>
</div>
</div>
</li>
</ul>
I had this semi-duplicate post as my reference here
<button class="{{menu.name=='Rename' && 'some-directive'}}">button2</button>

Conditional logic in AngularJS template

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>

Categories