I've checked various ressources and all leads to a bug that was closed long ago. Here is a similar issue, but I think mine is slightly different and persists up to at least angular.js 1.5.
I'd like to validate, if this is a new angular bug or if I missed something, since I'm relatively new to angular.
My problem
Check this plunker for a short demo of the issue
I've got a directive <infos> rendered inside a ng-repeat block
<body ng-controller="MainCtrl" class="container">
<div ng-repeat="item in items">
<h2>{{item.title}}</h2>
<infos></infos>
</div>
</body>
The <infos> directive has a ng-repeat block on its first element
<div ng-repeat="(infoId, color) in item.infos">
<p style="background: {{color}}">{{color}}</p>
</div>
The directive is loaded with templateUrl and replace:true.
.directive('infos', function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'infos.html'
};
})
The unexpected result:
When rendered, the directives of all iterations of the surrounding ng-repeat are rendered on the last ng-repeat of the outside loop and not as expected on each iteration of the surrounding ng-repeat. Check the plunker demo for a better understanding, what's going wrong.
Workarounds
I found several workarounds to the problem:
Wrapping the ng-repeat element in the directive by any other element
<div>
<div ng-repeat="(infoId, color) in item.infos">
<p style="background: {{color}}">{{color}}</p>
</div>
</div>
Leaving away the replace: true
Using template instead of templateUrl. Inlining the template code.
Using <script type="text/ng-template" id="infos.html">... to load the template
There might be another solution, using the $templateCache but I couldn't figure out how to preload the template, yet.
Related
Having a hard time grasping AngularJs.. How would you build something like this simple textfields and animations in AngularJS? I had been looking into using directives but it just isn't adding up to me much. I had been trying to base this from what I learned online but was not having much luck
http://codepen.io/yusufbkr/pen/RPBQqg
HTML:
<div class="materialContainer">
<div class="title">LOGIN</div>
<div class="input">
<label for="name">Username</label>
<input type="text" name="name" id="name">
<span class="spin"></span>
</div>
<div class="input">
<label for="pass">Password</label>
<input type="password" name="pass" id="pass">
<span class="spin"></span>
</div>
<div class="button login">
<button><span>GO</span> <i class="fa fa-check"></i></button>
</div>
Forgot your password?
<div class="title">REGISTER</div>
<div class="input">
<label for="regname">Username</label>
<input type="text" name="regname" id="regname">
<span class="spin"></span>
</div>
<div class="input">
<label for="regpass">Password</label>
<input type="password" name="regpass" id="regpass">
<span class="spin"></span>
</div>
<div class="input">
<label for="reregpass">Repeat Password</label>
<input type="password" name="reregpass" id="reregpass">
<span class="spin"></span>
</div>
<div class="button">
<button><span>NEXT</span></button>
</div>
just use the codepen link ( http://codepen.io/yusufbkr/pen/RPBQqg ), stackoverflow won't let me input the rest of the code...
Thanks! Any help would be incredible
I'm just learning Angular myself. From what I know, you're on the right track to be thinking of directives for swapping out interactive parts of the DOM. I suspect you'll also be needing ui-router for dealing with the widget outcomes (I hear it's the industry standard, vs the built-in ngRouter).
I'm partly writing this out to make sure I understand it, so I hope other, more knowledgeable people will come by and answer any questions you have about it (or point out where I'm misundertanding something.)
So you bring the webpage into the Angular world by, for instance, making the <html></html> tags into <html ng-app="yourApp"></html>. The unchanging html that will be the same on every view is in the index.html file. Put the html for the boxes is in another html file, say box.html. On the index.html file in the place where you want to have your boxes appear, put <div ui-view></div>. The ui-view connects to ui-router. Down at the bottom of the index.html right before closing the body tag, put
<script src="lib/angular/angular.js"></script>
<script src="lib/angular-ui-router/release/angular-ui-router.js"></script>
<script src="app/app.js"></script>.
In app.js, you put something like,
angular.module('yourApp', [
'yourApp.box',
'ui.router'
])
.config(function($stateProvider, $urlRouterProvider, $httpProvider) {
$urlRouterProvider
.otherwise('/signin/child');
$stateProvider
.state('box', {
url: '/signin',
templateUrl: 'app/box/box.html',
controller: 'BoxController'
})
.state('box.child', {
url: '/child',
template: '<span>{{ definedProperty }}</span>'
});
})
});
Starting at the top, you name angular, call its module function, and give it the parameters ('theNameYouChose', ['dependencies','required'])
In the config bit there, you're telling it about dependencies config needs. $stateProvider and $urlRouterProvider are ui-router things, and $httpProvider is, I believe, an angular thing that is still necessary.
In the urlRouterProvider, you're providing the default address the website goes to: in this case it happens to be a template within a template, /signin from the signin state, and /child from the specifics pasted into signin
In the states, you are giving names and properties to the URLs that will be part of your app: like, if someone goes to index.html/signin, the 'signin' state is summoned.
The template within a template might be how you'd get the box variability that you're looking for. In box.html you will have another <div ui-view></div> and that's where the varying thing described in 'signin.child' gets put in.
box.html is also the place you put your specially created html tags, the ones you will make with the directive. I'll talk through those below.
The parent template, 'signin', talks about BoxController. Let's say you built that in box.js. It would look like this:
angular.module('yourApp.box', [])
.controller('BoxController', function ($scope) {
$scope.definedProperty = 'reRegPass',
$scope.arrayOfObjects = [{prop: 'red'},{prop: 'blue'}]
})
.directive('specificBox', function(){
return {
restrict: 'EA',
templateUrl: 'app/box/box-guts.html',
replace: true,
scope: {
source: '='
},
link: function(scope, element, attribute) {
element.on('click', function() {
alert('Functionality in specificBox is working');
}
}
}
});
The first line is again summoning angular module, and then naming it: note we already listed this, 'yourApp.box', as a requirement in app.js. Then we have the controller-naming function, and the appearance of specific properties in their $scope.
The directive is taking the more useful of the two forms a directive can take. They can either return functions (with signatures like the one in .link), or they can return objects that describe new HTML entities. This one is named specificBox, which Angular will translate into <specific-box></specific-box>. This object (technically called a 'directive definition object') can be translated into HTML tags because of the restrict property... 'E' is for element. 'A' is for attribute. I don't know how something that can be an element could also be an attribute, but having both options works, so I'm going with it. (Maybe for your various signin boxes you want a directive that has just 'A' to make new types of attributes.)
You put these specifically-crafted tags into box.html, and everything in box-guts.html will be between those tags... In fact, replacing those tags because of the replace: true.
The .link property is where you would put a function that would do something interesting to that element; turn it red if clicked, whatever. I have an alert example. Uses jQuery Lite for events.
The .scope property is odd. I believe if left off, the specific-box stuff would have the same controller as box (ie, BoxController), with box's scope and dependencies. But here, instead, we're making an isolate scope. box-guts will not depend on anything or have access to anything built before. How will it get interesting changeable data, then..? Every property in the scope object becomes an attribute on specific-box.
The '=' means that you will pass it objects from somewhere, and they will have 2-way data binding (changes made in box-guts will be reflected in the object collection in your app). Other options are '#', one-way data binding, which means you're passing box-guts a string or something that, if it changes in the DOM, you don't care to reflect in your app; or '&' to give it a function from a controller somewhere else in your app.
So ultimately, in box.html, you will have something like
<div class="box-holder">
<div ui-view></div>
<specific-box source="thing" ng-repeat="thing in arrayOfObjects"></specific-box>
</div>
Like I said above, ask questions and maybe we can sort this out.
Say in my controller somewhere I have:
$scope.elements = [document.getElementById('a'), document.getElementById('b')];
and I have valid elements somewhere in the document with IDs of a and b.
I'd like to interpolate these elements directly in an HTML template, without writing JavaScript. I tried the following, and it did not work.
<div ng-repeat="e in elements">
{{ e }}
</div>
Is this possible?
More information about what I'm doing:
I have content (several custom directive elements which load up their own data via AJAX) that I want to disperse between several columns. The column of the content elements will change, and the number of columns will change.
<column-resizer options="columnResizerOptions">
<content1></content1>
<content2></content2>
...
</column-resizer>
The template for columnResizer currently looks like this:
<ng-transclude></ng-transclude>
<div ng-repeat="column in columns">
<div ng-repeat="element in column">
{{ element }}
</div>
</div>
columnResizerOptions is information about how to resize the columns and where to place the content in the columns. In the link function for the columnResizer, I use transclude to grab content1-contentn and place them in arrays corresponding to the column they should be in, which I ngRepeat through in my template (above).
Not sure why you wouldn't treat your whole app the "Angular way", but you could write a directive to do this:
angular.module('demoApp', [])
.directive('interpolateHtml', function() {
return {
restrict: 'A',
scope: {
htmlElement: '='
},
link: function postLink(scope, element) {
element.append(scope.htmlElement);
}
}
})
And use it in your HTML like this:
<div ng-repeat="e in elements">
<div interpolate-html html-element="e"></div>
</div>
Here's a working plnkr: http://plnkr.co/edit/5NvMA1x0C8TcwdLO2FNK?p=preview
It is possible.
$scope.elements = [ (document.getElementById('a') || {}).outerHTML ];
and
<div ng-repeat="e in elements">
<div ng-bind-html="e"></div>
</div>
You won't get data binding this way. You can use innerHTML or jqLite html() instead to get rid of extra wrappers.
get them into the DOM without using append, as it would be cleaner.
It wouldn't. A directive with nested directives or with DOM modifications in link is proper way in this case, you don't have to use data binding everywhere just because Angular promotes it.
here is my testing page :
<div ng-controller="test">
<input ng-keyup="asIwrite($event)" />
<p id="entityContent"></p>
</div>
and my controller :
EntitiesApp.controller('test', ['$scope', function ($scope) {
$scope.asIwrite = function ($event) {
$('#entityContent').html($event.srcElement.value);
}
}]);
this is actually working, if i write click in the input, the paragraph will hold the clickable url (processed html).
I am using jQuery to update the content of the paragraph as to show html element as they render in the page..
But using jQuery is a work-around in my opinion.
I want to achieve that using just Angular. How can I do ?
note : sure I can sweep jQuery off my project and use innerHTML of the
element but I want to know the way of doing that in Angular with
bindings. I used ng-bind on a paragraph element but that just
render the value of the textarea as is, no html process is performed.
See working example below.
You are right by doubting using jQuery is the right thing to do, and as you would expect it is not. The angular way to do that is register your input into the scope using ng-model, and the way to display it is using the ng-bind-html directive. (or simply ng-bind if it was simple text with no HTML)
However, Angular will not allow HTML binding by default as it could be a security issue. If you are sure about what you write, you can use $scope.trustAsHtml as showed in my example.
angular.module('test', [])
.controller('test', ['$scope', '$sce', function ($scope, $sce) {
$scope.trust = function(content) {
return $sce.trustAsHtml(content);
}
}]);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="test">
<input ng-model="content"/>
<p ng-bind-html="trust(content)"></p>
</div>
You probably have tried to do something like this but the content doesn't come out as html
<input ng-model="input" />
<p id="entityContent" ng-bind-html="input"></p>
You need to ensure strict context escaping as described here:
https://docs.angularjs.org/api/ng/service/$sce
Please keep in mind what you're saying you want to do is explicitly prevented by angular developers as a way to mitigate XSS attacks
You don't need to do that, angular is simplier than that.
<div ng-controller="test">
<input ng-model="variable1" />
<p id="entityContent">{{variable1}}</p>
</div>
Angular binds variables automatically. When the input's value change, the new value will be printed.
I'm quite new to angular and frontend in general, but what i'd like to see is something similar to what routing with ngView gives, but without routing, i.e just load a template on some event. To be more specific, let's say i have an input field somewhere in the header and when i click/focus on this field a special panel with different input options shows up. The trick is that this input field and other elements are already a part of a template which is loaded into ngView, so as i understand i can't use another ngView for options pane.
use ngIf, ngShow, ngHide, ngSwitch for stuff like that
<button ng-click="showStuff = true">Show Stuff</button>
<button ng-click="showStuff = false">Hide Stuff</button>
<div ng-show="showStuff">Showing Stuff</div>
<div ng-hide="showStuff">Hiding Stuff</div>
Have a look at this plunker for a quick and dirty, working example.
Note that the showStuff variable is just magically created by angular on the root scope, since I'm not using a controller.
You can load templates with ng-if and ng-include like this example:
<body ng-app="app">
<div class='container'>
<button ng-click='tmpl = true' class='btn btn-info'>Load template!</button>
<div ng-if='tmpl'>
<div ng-include="'template.html'"></div>
</div>
</div>
</body>
The ngIf directive will add element to the DOM when the argument expression is true. Then, the angular will compile the inner directive ngInclude, loading the template.
I have to use a framework that modifies my DOM. Here is some example HTML that gets used:
<div id="testID" ng-show="example === 'show'">Some Content</div>
wil be modified by the Framework to something like this:
<div id="wrapperOne">
<div id="wrapperTwo">
<div id="testID" ng-show="example === 'show'">Some Content</div>
</div>
</div>
This gets done by calling a JS function like this one:
framework.wrap($("#testID"));
As you see, the framework wraps the old HTML with some other elements.
My problem is, that Angular does not show the div, when $scope.example becomes 'show'. Without using the framework, it works fine, I think the problem is, that angular compiles the DOM before the framework changes it. The framework has to wait for an ajax request, so just including the angular scripts after it does not work.
I am searching for a solution to force angular to "recompile" the whole DOM, better some parts of it.
Is this the right proposal, or is there any other way to do this? Just omitting the framework isn't the solution as I have to use it.
Thank you very much,
-Lukas
Use a custom directive with ng-transclude
I've created a fiddle - http://jsfiddle.net/zghuoxhb/2/
directive:
.directive('myWrapper', function () {
return {
restrict: 'E',
templateUrl: "my-wrapper-template.html",
replace: true,
transclude: true,
scope: false
};
})
html:
<my-wrapper>
<div id="testID" ng-show="example === 'show'">Some Content</div>
</my-wrapper>
output:
BTW excellent question, this is a good use case to showcase the awesomeness of angular