Angular. Prepend element to body, change from controller to directive - angularjs

I'm doing a gallery in angular and this gallery has a light-box view that shows the image with a dark background when a gallery item is clicked.
Right now I have code inside a controller that does the trick like this:
$scope.modal = function (iElement) {
var darkDiv = angular.element('<div class="modal__dark-background"></div>');
var body = angular.element(document).find('body');
var overlay = $compile(darkDiv)($scope);
body.prepend(overlay);
}
I also have a directive to insert my main template in a custom element:
myApp.directive('gallery', function() {
return {
restrict: 'E',
templateUrl: 'partials/gallery.template.html'
}
})
My problem is that I don't know how to make a directive that has the functionality of the code inside the controller. I want to do this because I was toll that is never a good idea to put DOM-related code inside a controller.

I don't have enough rep to comment, unfortunately.
But I don't know how to do this for the case of the modal element.
Do what exactly? Do you want to have the directive execute the above code? Try this:
myApp.directive('gallery', function($scope) {
return {
restrict: 'E',
templateUrl: 'partials/gallery.template.html',
controller: function(){
$scope.modal = ...
}
}
})
Also I would consider using ui.bootstrap or something similar for tasks like this.

Related

Is there a way to list a directive as a requirement for another directive?

I have a directive that I use for date-time input and I'd like to use it in the template for another directive.
The problem is that if I use the parent directive anywhere and I forget to include the child directive in the page, it fails silently.
Is there a way to make a directive require another directive?
I tried listing it in the require attribute, but then it fails even when DateTimeInput is included.
angular
.module('main')
.directive("newTransferPane", ["TransferService", function(TransferService){
'use strict';
return {
restrict: "A",
templateUrl: "Templates/NewTransferPane.html",
scope: true,
require: "DateTimeInput",
link: function ($scope){
$scope.secondTransfer = false;
$scope.transfer_date = new Date();
$scope.$watch("secondTransfer", function(){
TransferService.secondTransfer = $scope.secondTransfer;
});
}
};
}]);

Change templateURL of directive dynamically after $http.get()

I'm working on 'skeleton' loading the UI in different components. I have a directive that I'm loading a template initially (this template has low opacity and looks like a mock table). Once I get the data I need in an http.get() then I want to change the templateUrl of the directive. Below is what I've tried so far.
function registers($log, $state, $templateCache, currentCountData, storeFactory) {
var directive = {
restrict: 'A',
scope: true,
templateUrl: '/app/shared/tableMock/tableMock.html',
link: link
};
return directive;
function link(scope, element, attrs) {
storeFactory.getRegisters().then(function (data) {
scope.registers = data.registers;
$templateCache.put('/app/dashboard/registers/registers.html');
});
}
}
I'm not sure I'm on the right track. I can step through and see the storeFactory return the correct data from my factory. How can I then change the templateUrl of the directive?
For cases like this I usually do something like this in my directive template:
<div ng-switch="viewState">
<div ng-switch-when="gotRegisters">
Content for when you get the registers
</div>
<div ng-switch-default>
For when you don't have the registers
</div>
</div>
This way you could just change a variable to show your content ie scope.viewState = 'gotRegisters'; instead of waiting for it to download after you already downloaded your registers.
With a help from this question I was able to come up with this
function link(scope, element, attrs) {
storeFactory.getRegisters().then(function (data) {
scope.registers = data.registers;
$http.get('/app/dashboard/registers/registers.html', { cache: $templateCache }).success(function (tplContent) {
element.replaceWith($compile(tplContent)(scope));
});
});
}
tplContent correlates to the response of the $http.get(). It's the html in the registers.html file. element represents the directive itself.

Passing a parent directive attribute to a child directive attribute

I'm creating directives for a library that customers can use. I need to let the customers create their own templates for a directive and pass the absolute url value of that template into my directives. One of my directives will have another custom directive inside of it, and it's template will be figured out based upon the value of one of the parent directive's attributes. Here's an example:
<parent-dir menu-template="this.html" item-template="that.html"></parent-dir>
I have a template for this directive that looks like this:
<ul style="list: none" ng-repeat="item in menu">
<child-dir template="{{itemTemplate}}"></child-dir>
</ul>
My directives look like this:
angular.module('myApp')
.directive('parentDir', function () {
return {
restrict: 'E',
scope: {
menuTemplate: '#',
itemTemplate: '#',
menuType: '#',
menuName: '#',
menuId: '#',
},
templateUrl: function (element, attrs) {
alert('Scope: ' + attrs.menuTemplate);
return attrs.menuTemplate;
},
controller: function ($scope, $element, $attrs) {
$scope.defaultSubmit = false;
alert('Menu: '+$attrs.menuTemplate);
alert('Item: ' + $attrs.itemTemplate);
$scope.itemTemplate = $attrs.itemTemplate;
if ($attrs.$attr.hasOwnProperty('defaultSubmit')) {
alert('It does');
$scope.defaultSubmit = true;
}
}
};
})
.directive('childDir', function () {
return {
restrict: 'E',
require: '^parentDir',
templateUrl: function (element, attrs) {
alert('Item Template: ' + attrs.template);
return attrs.template;
},
controller: function ($scope, $element, $attrs) {
$scope.job;
alert('Under job: ' + $scope.itemTemplate);
}
};
});
I'm not showing all of the code but this is the main piece of my problem. When I run this, I keep getting undefined for the template on the childDir.
What is the best practice in perpetuating the value of itemTemplate from the parentDir so that the childDir can use it as it's template?
The reason you're running into problems is because the function that generates the templateUrl is running before a scope has been assigned to your directive - something that has to be done before interpolated data can be replaced.
In other words: at the point that the templateUrl function runs, the value of the template attribute is still "{{itemTemplate}}". This will remain the case until the directive's link (preLink to be precise) function runs.
I created a plunker to demonstrate the point here. Be sure to open the console. You'll see that templateUrl runs before both the parent and child linking functions.
So what do you do instead?
Fortunately, angular provides a $templateRequest service which allows you to request the template in the same way it would using templateUrl (it also uses the $templateCache which is handy).
put this code in your link function:
$templateRequest(attrs.template)
.then(function (tplString){
// compile the template then link the result with the scope.
contents = $compile(tplString)(scope);
// Insert the compiled, linked element into the DOM
elem.append(contents);
})
You can then remove any reference to the template in the directive definition object, and this will safely run once the attribute has been interpolated.

Angular - How to use a dynamic templateUrl for a directive?

So, for whatever reason, I am trying to create a slider, where the contents of each slide are different HTML templates. So instead of an image slider, you could say it's a HTML slider.
So in my HTML I just have this code, and the controls for the slider are also inside this HTML template:
<slide-template></slide-template>
And here is my entire slide module:
(function() {
'use strict';
angular
.module('slideCtrl', [])
.directive('slideTemplate', function() {
return {
restrict: 'E',
templateUrl: 'views/slides/slide-1.html',
replace: true,
controller: 'slideController',
controllerAs: 'slides'
}
})
.controller('slideController', function() {
var vm = this;
});
})();
I'm not sure how to move forward with this, I've tried looking around but haven't found anything that I felt I could use. Inside the controller, I would like to have an array of slide template URLs and a corresponding variable to indicate the current slide:
slideUrl = [ 'views/slides/slide-1.html', 'views/slides/slide-2.html'];
slideNum = 0;
Ideally, I would then like my directive to use these variables to determine what variable it will use for templateUrl. So by default, you can see that slideNum is 0, meaning that I want to use slide1.html or slideUrl[0]. So, my templateUrl would be something like slideUrl[slideNum]. Of course, this can't be done as the directive wouldn't be able to access that data, so I'm not sure how to do this.
The end result would be that if you clicked one of the slide navigation icons, you would be updating the slideNum variable, which would instantly change the templateUrl used.
I guess I am essentially wanting a slider which doesn't rely on some images or something like that for content, but instead is a slider of actual HTML content.
Any ideas? If I haven't explained myself well enough, please let me know.
hi I would solve it like this by creating a "main.html" template and in that:
//main.html
<div ng-if="slide == 1">
<ng-include src="'slide1.html'"/>
</div>
<div ng-if="slide == 2">
<ng-include src="'slide2.html'"/>
</div>
<div ng-if="slide == 3">
<ng-include src="'slide3.html'"/>
</div>
//controller
.controller('slideController', function() {
$scope.slide = 1
//logic to switch slides
});
for animations on the slide transitions take a look at this code pen
animations
I would suggest a main directive, where you would place the different slides on one page.
For instance, the main directive:
<div ng-include src="'slider0.html'" ng-if="slider%4==0"></div>
<div ng-include src="'slider1.html'" ng-if="slider%4==1"></div>
<div ng-include src="'slider2.html'" ng-if="slider%4==2"></div>
<div ng-include src="'slider3.html'" ng-if="slider%4==3"></div>
And then in the controller of the directive you set:
$scope.slider = 0;
// Some more logic like:
$scope.slider++;
You could move this to a link function and replace your compiled slide dynamically by adding them to the slideUrl array. This method is flexible enough to allow you to manage the slides in the controller, also you could potentially pass the slide urls to the directive through an scoped attribute.
.directive('slideTemplate', function($http, $compile, $templateCache) {
return {
restrict: 'E',
replace: true,
controller: 'slideController',
controllerAs: 'slides',
link : function(scope, el, attrs) {
// Bind active slide number to controller scope
scope.slides.num = 0;
// Declare slide urls
var slideUrl = [
'views/slides/slide-1.html',
'views/slides/slide-2.html'
];
// Load a slide and replace the directives inner html
// with the next slide.
function loadSlide(template) {
// Get the template, cache it and append to element
$http.get(template, { cache: $templateCache })
.success(function(content) {
el.replaceWith($compile(content)(scope));
}
);
}
// Progress to the next slide, this is bound to the
// controllers scope and can be called from there.
scope.slides.next = function() {
var next = scope.slides.num + 1;
var slide = slideUrl[next] ? next : slide;
scope.slides.num = slide;
loadSlide(slideUrl[slide]);
}
}
}
});

Using Angular, how do I bind a click event to an element and on click, slide a sibling element down and up?

I'm working with Angular and I'm having trouble doing something that I normally use jQuery for.
I want to bind a click event to an element and on click, slide a sibling element down and up.
This is what the jQuery would look like:
$('element').click(function() {
$(this).siblings('element').slideToggle();
});
Using Angular I have added an ng-click attribute with a function in my markup:
<div ng-click="events.displaySibling()"></div>
And this is what my controller looks like:
app.controller('myController', ['$scope', function($scope) {
$scope.events = {};
$scope.events.displaySibling = function() {
console.log('clicked');
}
}]);
So far this is working as expected but I don't know how to accomplish the slide. Any help is very much appreciated.
Update
I have replaced what I had with a directive.
My markup now looks like this:
<div class="wrapper padding myevent"></div>
I have removed what I had in my controller and have created a new directive.
app.directive('myevent', function() {
return {
restrict: 'C',
link: function(scope, element, attrs) {
element.bind('click', function($event) {
element.parent().children('ul').slideToggle();
});
}
}
});
However, I still can't get the slide toggle to work. I don't believe slideToggle() is supported by Angular. Any suggestions?
I'm not sure exactly on the behaviour that you're talking about, but I would encourage you to think in a slightly different way. Less jQuery, more angular.
That is, have your html something like this:
<div ng-click="visible = !visible"></div>
<div ng-show="visible">I am the sibling!</div>
You can then use the build in ng-animate to make the sibling slide - yearofmoo has an excellent overview of how $animate works.
This example is simple enough that you can put the display logic in the html, but I would otherwise encourage you to as a rule to put it into the controller, like this:
<div ng-click="toggleSibling()"></div>
<div ng-show="visible"></div>
Controller:
app.controller('SiblingExample', function($scope){
$scope.visible = false;
$scope.toggleSibling = function(){
$scope.visible = !$scope.visible;
}
});
This kind of component is also a prime candidate for a directive, which would package it all up neatly.
app.directive('slideMySibling', [function(){
// Runs during compile
return {
// name: '',
// priority: 1,
// terminal: true,
// scope: {}, // {} = isolate, true = child, false/undefined = no change
// controller: function($scope, $element, $attrs, $transclude) {},
// require: 'ngModel', // Array = multiple requires, ? = optional, ^ = check parent elements
restrict: 'A', // E = Element, A = Attribute, C = Class, M = Comment
// template: '',
// templateUrl: '',
// replace: true,
// transclude: true,
// compile: function(tElement, tAttrs, function transclude(function(scope, cloneLinkingFn){ return function linking(scope, elm, attrs){}})),
link: function($scope, iElm, iAttrs, controller) {
iElm.bind("click", function(){
$(this).siblings('element').slideToggle();
})
}
};
}]);
Usage would be something like
<div slide-my-sibling><button>Some thing</button></div><div>Some sibling</div>
Note all the "code" above is just for the sake of example and hasn't been actually tested.
http://plnkr.co/edit/qd2CCXR3mjWavfZ1RHgA
Here's a sample Plnkr though as mentioned in the comments this isn't an ideal setup since it still has the javascript making assumptions about the structure of the view so ideally you would do this with a few directives where one requires the other or by using events (see $emit, $broadcast, $on).
You could also have the directive create the children "programmatically" via JavaScript and circumvent the issue of not knowing what context the directive is being used in. There are a lot of potential ways to solve these issues though none of the issues will stop it from functionally working they are worth noting for the sake of re-usability, stability, testing, etc.
As per this link : https://docs.angularjs.org/api/ng/function/angular.element
AngularJs element in your code snippet represents JQuery DOM object for related element. If you want to use JQuery functions, you should use JQuery library before angular loads. For more detail, please go through above link.
Best practice:
<div ng-if="view"></div>
$scope.view = true;
$scope.toggle = function(){
$scope.view = ($scope.view) ? false : true;
}

Resources