ng-repeat data binding with custom directive - angularjs

I have a list and on each item in list I am calling a modal window (custom directive) which should have details about that item being clicked , but the data does not change and remains same across each item. Please find the code below.
angular
.module('Testapp')
.directive('testDirective', function () {
return {
restrict: "AE",
templateUrl: "/Apps/templates/mytem/testdir.html",
translucent: true,
scope: {item:'=data'},
link: function (scope, element, attribute) {
console.log(scope.sequence);
}
};
});
Directive
<div class="modal fade" id="modalAddFilters">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body tree">
{{item}}
</div>
</div>
</div>
</div>
Calling Template
<div>
<div ng-repeat="items in TestList>
<test-Directive id="directive_modalAddFilters" data="items"></test-Directive>
</div>
I am able to see the data correctly loaded in DOM but directive template doesnt change the data.

You code works fine, except that you forget to close you ng-repeat with a quotation mark.
I think you just didn't properly resolved you data into the modal view.
I have made a plunk based on your (partial) code, I added a modal and everything works fine. I've used ui-bootstrap to show the modal with the repeated data injected.

Related

Angularjs compile a directive inside ng-repeat with isolated scope

I have a directive in the form of a dropdown, pretty simple. The user can click a button to add as many as they need to in a ul, make their selections, and save it off. This is all inside of several ng-repeats.
I'm having trouble mastering the scope. As I expected, this works:
<div ng-repeat="group in groups" question-group="group" class="question-group">
<div ng-repeat="question in questions">
<ul>
<li ng-repeat="case in question.cases"></li>
<li><new-case group='group'></new-case></li>
</ul>
</div>
</div>
When I say "works", I mean that group is properly scoped (the data of the entire group is necessary for the resulting input).
When I switch it to "click to add":
<div ng-repeat="group in groups" question-group="group" class="question-group">
<div ng-repeat="question in questions">
<ul>
<li ng-repeat="case in question.cases"></li>
<li>add case</li>
</ul>
</div>
</div>
group is undefined in the scope. Here is my createNewCase function:
function createNewCase($event) {
var thisLi = angular.element($event.target).closest('li');
var listItem = $compile('<li><new-case group=\'group\'></new-case></li>');
var html = listItem($scope);
thisLi.before(html);
}
$scope.createNewCase = createNewCase;
And the newCase directive:
angular.module('groups.directives.newCaseDirective', [])
.directive('newCase', ['$window', function() {
return {
restrict: 'EA',
scope: { group: '=' },
templateUrl: 'groups/views/newcase.tpl.html'
};
}]);
I've been reading for days and I've tried a few other derivatives but I'm ultimately just not getting it. Help is greatly appreciated.
Thanks!
The issue is that group is created by ng-repeat and is only available in child scopes of ng-repeat.
Each repeated element is in it's own child scope. So your directive version works but your other one doesn't because the controller doesn't see those child scopes.
You would have to pass group as argument of the function if you want to access it in controller
<a href="#" ng-click="createNewCase($event, group)">

AngularJS: Using directives as templating

I'm using a directive to provide a basic template for many of the pages in my Angular app. It looks like this:
angular.module('app.basicLayout', [])
.directive('basicLayout', function () {
return {
restrict: 'E',
transclude: true,
templateUrl: '/assets/views/templates/basicLayout.html'
}
});
And HTML:
<basic-layout>
<h1>My layout goes here</h1>
</basic-layout>
On some of these pages I would like to add a sidebar and still be able to use the layout from my <basic-layout> directive. Is it possible to make something like the following?
<basic-layout>
<h1>My content goes here</h1>
<the-sidebar>
<h2>Sidebar content here</h2>
</the-sidebar>
</basic-layout>
Update
My template file of the directive currently look like this:
<div class="container basic-layout">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div ng-transclude></div>
</div>
</div>
</div>
If <the-sidebar> is set, I would like to change the template file to something like this:
<div class="container basic-layout">
<div class="row">
<div class="col-md-8">
<!-- The content here -->
<div ng-transclude></div>
</div>
<div class="col-md-4">
<!-- The sidebar here -->
<div ng-transclude></div>
</div>
</div>
</div>
That's the exact case for transclusion. You can parametrize your directive layout with some variable layout (sidebar in this case). To do this your have to set the transclude property in the directive config object to true and also specify where in your directive's layout the changing content should be injected by using the ng-transclude directive. Like this:
return {
...
transluce: true,
...
}
and now in the directive template:
//other layout here
<div ng-transclude></div>
This way all the content you put inside the <basic-layout> directive will be transfered into the element on which you use ng-transclude.
For this to work, you need to manually transclude using the transclude function passed as a 5th parameter to the link function. To make it easier, I would change the template to have placeholders:
<div>
<content-placeholder></content-placeholder>
<div>
<sidebar-placeholder></sidebar-placeholder>
</div>
</div>
Then, place each content where it belongs:
transclude: true,
link: function(scope, element, attrs, ctrls, transclude){
transclude(function(clone){
var sidebar = clone.find("the-sidebar").remove();
element.find("content-placeholder").replaceWith(clone);
element.find("sidebar-placeholder").replaceWith(sidebar);
});
}
This should work for you, but it's not clear to me why you want to build a directive for a general layout.
If you have many pages in the Web app (in a classical non-SPA sense), then it's probably better to create the scaffolding in a "master page" on the server.
Otherwise, if you mean that you have many "views" of the same app, the I suggest looking into ui-router - specifically into a section of Nested States and Nested Views.

Hide/show an element in Angularjs using custom directive, ng-show and $scope

When a link is clicked in the app navigation a dropdown with ui-view content shows below each respective link.
The HTML:
<div class="sc-dash-header">
<ul>
<li>
<a class="navbar-brand" show-nav-popup href="">download</a>
<div id="nav-download-progress" class="dash-hdr-popup" ng-show="showPopup">
<div ui-view="hdr-download-progress"></div>
</div>
</li>
<li>
<a class="navbar-brand" show-nav-popup href="">add</a>
<div id="nav-add" class="dash-hdr-popup" ng-show="showPopup">
<div ui-view="hdr-add-content"></div>
</div>
</li>
<li>
<a class="navbar-brand" show-nav-popup href="">enter pin</a>
<div id="nav-unlock" class="dash-hdr-popup" ng-show="showPopup">
<div ui-view="hdr-unlock"></div>
</div>
</li>
</ul>
</div>
I've included an ng-show attribute to open the dropdown when $scope.showPopup is set to true.
To achieve this I've created a custom directive with an on click called show-nav-popup.
The JS:
.directive('showNavPopup', function () {
return {
restrict: 'A',
// scope: {},
link: function(scope, el, attrs) {
el.on('click', function(){
scope.$apply(function () {
scope.showPopup = true;
});
console.log(scope);
});
}
};
});
The above works, but the dropdown opens on each element.
Question: I need to isolate the scope, so on each click, only the respective dropdown appears. I uncomment the line // scope: {} - but this doesn't work..
Angularjs n00b here - any help would be much appreciated
Having an isolate scope in this situation wouldn't fix the problem. There are a ton of ways to achieve what you want though. One of which is to assign each show-popup-nav an id, turn $scope.showPopup into an array, and keep an individual true/false for each id. Then for each ng-show, you look at the index corresponding to each id for the true/false value.
I coded it up on that guy's Plunker, working as you expect: http://plnkr.co/edit/CSikLIiuPNT9dfsfZfLk
EDIT: I should say, you COULD use an isolate scope to fix this, but that would require a lot of changes to your DOM, as the ng-show directive is a sibling to your show-popup-nav, and not a child.
When you create the isolate scope, the scope applies to the element that your directive is applied to, and it's child elements. In this case that's just the anchor tag:
<a class="navbar-brand" show-nav-popup href="">download</a>
You are using an ng-show on a tag that is a sibling to the anchor tag:
<div id="nav-download-progress" class="dash-hdr-popup" ng-show="showPopup">
The sibling is not part of the isolate scope, and so it never notices that the value of showPopup has changed.
The ng-show would work if it were applied to a DOM element that was a child of the anchor tag.
EDIT
One way to make this work would be to wrap your two siblings in a parent tag, and use the directive on the parent:
<div show-nav-popup>
Download
<div ng-show="showPopup"></div>
</div>
Then you'd need to modify your directive's code to find the anchor tag and apply the click handler.
You might instead try a completely different approach as suggest in the other answer by #Bill Bergquist

Using AngularJS, how do I set $scope properties in the controller of the parent page

Thanks for looking.
I have the following markup for a modal which shares the same angular controller as it's parent page:
<!-- START Add Event Video -->
<script type="text/ng-template" id="EventVideo.html">
<div class="event-modal">
<div class="modal-header"><h3>Event Video</h3></div>
<div class="modal-body">
<p>Please enter the URL of either a <strong>YouTube</strong> or <strong>Vimeo</strong> video.</p>
<span ng-if="!Event.VideoUrlIsValid" style='color:#9f9f9f;'>This doesn't look like a valid YouTube or Vimeo Url. Your video may not work.</span>
<div class="row" ng-controller="EventCreateController">
<div pr-form-input span="12" name="videoUrl" ng-model="Event.Item.VideoUrl" placeholder="YouTube or Vimeo URL" isRequired="false" no-asterisk></div>
</div>
</div>
<div class="modal-footer"><button class="btn btn-primary" ng-click="Event.UI.EventVideoModal.Close()">Done</button></div>
</div>
</script>
<!-- END Add Event Video -->
And here is the relevant JavaScript:
EventVideoModal: {
Open: function () {
$scope.EventVideoModal = $modal.open({
templateUrl: 'EventVideo.html',
controller: 'EventCreateController',
scope: $scope
});
},
Close: function () {
$scope.EventVideoModal.close();
}
}
Please note the Event.Item.VideoUrl model reference.
The modal allows a user to set the URL of a video, and the goal is to have that set $scope.Event.Item.VideoUrl in the controller and then close the modal. The parent page and the modal both share the same controller, so I had hoped that this would work.
The modal behavior is fine (opens and closes as it should), but the $scope.Event.Item.VideoUrl property is not getting set.
Any advice is appreciated.
Problem Solved!
Thanks to Bogdan Savluk, I realized that I had a scope inheritance problem. So, removing both the explicit reference to the controller in the modal HTML as well as in the JavaScript constructor, resolved my problem:
<!-- START Add Event Video -->
<script type="text/ng-template" id="EventVideo.html">
<div class="event-modal">
<div class="modal-header"><h3>Event Video</h3></div>
<div class="modal-body">
<p>Please enter the URL of either a <strong>YouTube</strong> or <strong>Vimeo</strong> video.</p>
<span ng-if="!Event.VideoUrlIsValid" style='color:#9f9f9f;'>This doesn't look like a valid YouTube or Vimeo Url. Your video may not work.</span>
<!-- <div class="row" ng-controller="EventCreateController"> <--REMOVE THIS! -->
<div class="row">
<div pr-form-input span="12" name="videoUrl" ng-model="Event.Item.VideoUrl" placeholder="YouTube or Vimeo URL" isRequired="false" no-asterisk></div>
</div>
</div>
<div class="modal-footer"><button class="btn btn-primary" ng-click="Event.UI.EventVideoModal.Close()">Done</button></div>
</div>
</script>
<!-- END Add Event Video -->
And here is the relevant JavaScript:
EventVideoModal: {
Open: function () {
$scope.EventVideoModal = $modal.open({
templateUrl: 'EventVideo.html',
//controller: 'EventCreateController', <--REMOVE THIS!!
scope: $scope
});
},
Close: function () {
$scope.EventVideoModal.close();
}
}
If you are passing scope to $modal.open() than scope for modal would be created as child scope from passed scope... - so you will have access to all properties from it.
But in case when you are passing the same controller to it - that controller would be applied to new scope and will override all properties from parent.
So in general, as I see the only thing you need to do to achieve desired result is to remove controller from configuration passed to $modal.open() or replace it with something that is specific only for that modal.

Accessing controller scope from transcluded content

I am working on my first Angular app (yay!). I'm trying to have a generic modal component which can declare a title, custom content and any number action buttons. Background setup follows, then my questions at the bottom.
Here's how I'd like to use the directive.
<!-- in app/views/library/index.html -->
<!-- outside <modal> #i_work works great -->
<select id="i_work">
<option value="{{libraryType.id}}" ng-repeat="libraryType in libraryTypes">{{libraryType.name}}</option>
</select>
<modal id="library_new" title="My Custom Title">
<form>
<!-- inside <modal> #i_dont_work doesn't work -->
<select id="i_dont_work">
<option value="{{libraryType.id}}" ng-repeat="libraryType in libraryTypes">{{libraryType.name}}</option>
</select>
</form>
<modal-buttons>
<!-- [toggle-modal] is another directive that shows/hides a given <modal> -->
<button type="button" toggle-modal="library_new">cancel</button>
<button type="submit" ng-click="addLibrary()">save</button>
</modal-buttons>
</modal>
Here's the directive code:
// in app/scripts/directives/modal.js
'use strict';
angular.module('sampleAngularApp')
.directive('modal', [function () {
return {
templateUrl: '/scripts/directives/modal.html',
restrict: 'E',
transclude: true,
link: {
pre: function preLink(scope, element, attrs) {
scope.id = attrs.id;
scope.title = attrs.title;
},
post: function preLink(scope, element, attrs) {
// hacky-hack to transclude specific content into other targets.
// bound event handlers should be preserved (as implemented they are).
// this actually works
var buttonWrap = element.find('modal-buttons');
buttonWrap.children().each(function (index, button) {
element.find('.modal-footer').append(button);
});
buttonWrap.remove();
}
}
};
}]);
... and the directive template:
<!-- in app/scripts/directives/modal.html -->
<div class="modal" role="dialog">
<div class="modal-header">
<span class="title">{{title}}</span>
<button type="button" class="close" toggle-modal="{{id}}">close</button>
</div>
<!-- the contents of .modal-body should be everything inside <modal> above, -->
<!-- except for <modal-buttons> -->
<div class="modal-body" ng-transclude />
<!-- the contents of .modal-footer should be the contents of <modal-buttons> -->
<div class="modal-footer" />
</div>
Questions:
1) Consider the <select> elements in the controller's view above. #i_work renders correctly with <option>s just fine. #i_dont_work renders a <select> with no <option>s. libraryTypes seems to not be in scope inside <modal>. Why is that, and how can I fix it?
2) Is there a better way to transclude specific content into multiple targets? Google's Polymer project provides <content /> with an optional [select] attribute to provide multiple insert targets. Does Angular have anything like this? (See Polymer's website for more information.)

Resources