How to dynamically load controller to a directive - angularjs

So I have a directive that Will be acting as a side panel in my app. When a user clicks a button the side panel will open. In said side panel I need the controller and view for this area to be dynamic based on which button the users clicks. I have found a way to load up the template dynamically but I am running into issues with loading the controller dynamically.
Enough talking here is the code.
Directive Code
app.directive('itemForm', function($compile) {
var item1Template = '<div ng-include="view"></div>';
var item2Template = '<h1> Hello item2 Form </h1>';
var getTemplate = function(contentType) {
if(contentType === 'item1') {
return item1Template;
} else if(contentType === 'item2') {
return item2Template;
}
};
return {
restrict: 'E',
replace: 'true',
scope: {
formType: '#formType'
},
//templateUrl: scope.template,
link: function(scope, element) {
if(scope.formType === 'item1') {
scope.view = '/views/item1.html';
}
element.html(getTemplate(scope.formType)).show();
$compile(element.contents())(scope);
}
};
});
Html
<item-form form-type='{{form.type}}'> </item-form>
Controller for view that directive lives in
$scope.form = {};
$scope.openItemOneDlg = function() {
$scope.isFormOpen = !$scope.isFormOpen; // this opens the side panel
$scope.form.type = 'item1';
};
$scope.openItemTwoDlg = function() {
$scope.isFormOpen = !$scope.isFormOpen; // this opens the side panel
$scope.form.type = 'item2';
};

You can broadcast (using $broadcast) an event on click of the button. And have a listener (using $on) in the directive. This way, whenever the event is fired, directive logic will get executed.
You can refer the answer on this link for the usage of $broadcast and $on:
On-and-broadcast-in-AngularJS

Related

re-use google-places autocomplete input after page navigation

I need in a angularjs single page application a google-places autocomplete input, that shall run as a service and shall be initialized once at runtime. In case of navigation, the with goolge-places initialized element and the appropriate scope are destroyed.
I will re-use the places input field after navigate to the page containing the places autocomplete input field. With the method element.replaceWith() it works well.
After replacing the element, I can not reset the input by the "reset" button. How can I bind the new generated scope to the "reset" button and the old scope variables. Because the old scope and elements are destroyed by the navigation event?
.factory('myService', function() {
var gPlace;
var s, e;
var options = {
types: [],
componentRestrictions: {country: 'in'}
};
function init() {
}
function set(element, scope) {
console.log('set');
if (!gPlace) {
e = element;
gPlace = new google.maps.places.Autocomplete(element[0], options);
google.maps.event.addListener(gPlace, 'place_changed', function() {
scope.$apply(function() {
scope.place.chosenPlace = element.val();
});
});
} else {
element.replaceWith(e);
}
}
init();
return {
'init':init,
'set':set
};
});
the navigation (element and scope destroying) will be simulated in this plunk by the ng-if directive that will be triggered by the "remove" button.
see here plunk
If you want you can create a service that holds the last selected place and shares it among controllers and directives:
.service('myPlaceService', function(){
var _place;
this.setPlace = function(place){
_place = place;
}
this.getPlace = function(){
return _place;
}
return this;
});
Then create a directive that uses this service:
.directive('googlePlaces', function(myPlaceService) {
return {
restrict: 'E',
scope: {
types: '=',
options: '=',
place: '=',
reset: '='
},
template: '<div>' +
'<input id="gPlaces" type="text"> <button ng-click="resetPlace()">Reset</button>' +
'</div>',
link: function(scope, el, attr){
var input = document.querySelector('#gPlaces');
var jqEl = angular.element(input);
var gPlace = new google.maps.places.Autocomplete(input, scope.options || {});
var listener = google.maps.event.addListener(gPlace, 'place_changed', function() {
var place = autocomplete.getPlace();
scope.$apply(function() {
scope.place.chosenPlace = jqEl.val();
//Whenever place changes, update the service.
//For a more robust solution you could emit an event using scope.$broadcast
//then catch the event where updates are needed.
//Alternatively you can $scope.$watch(myPlaceService.getPlace, function() {...})
myPlaceService.setPlace(jqEl.val());
});
scope.reset = function(){
scope.place.chosenPlace = null;
jqEl.val("");
}
scope.$on('$destroy', function(){
if(listener)
google.maps.event.removeListener(listener);
});
});
}
}
});
Now you can use it like so:
<google-places place="vm.place" options="vm.gPlacesOpts"/>
Where:
vm.gPlacesOpts = {types: [], componentRestrictions: {country: 'in'}}

Making popver disappear on click of anywhere else than popover

I am made one popover.
i want to make sure that the popover should close if the user click anywhere else than popover.
HTML code-
New quote1
New quote
New quote
Js code-
angular.module('myApp', [])
.controller('myCtrl', function($scope){
$scope.selectedItems = {quote : {label : 'You ngModal now works'}};
$scope.newQuote = function(){
alert('It works');
}
})
.directive('popover', function($compile){
return {
restrict : 'A',
link : function(scope, elem){
var content = $("#popover-content").html();
var compileContent = $compile(content)(scope);
var title = $("#popover-head").html();
var options = {
content: compileContent,
html: true,
title: title
};
$(elem).popover(options);
}
}
});
Here is the link to jsfiddle-JsFiddle Link
If you don't want to include entire BootstrapUI for only popover then you need to do two things. First of all, you need to set up click event listener on the body or document level and check if bubbled event originates from popover container. Then it's also important the you prevent event bubbling in case of the directive element click, so popover doesn't get closed right after opening.
Entire directive code:
.directive('popover', function($compile) {
$('body').on('click', function (e) {
if (!$(e.target).parents('.popover.in').length) {
$('.popover').popover('hide');
}
});
return {
restrict : 'A',
link : function(scope, elem){
var content = $("#popover-content").html();
var compileContent = $compile(content)(scope);
var title = $("#popover-head").html();
var options = {
content: compileContent,
html: true,
title: title
};
$(elem).popover(options).click(function(e) {
e.stopPropagation();
});
}
}
});
Btw, don't put $('body').on('click' in directive's link function: you don't want to bind one more handler each time directive is linked.
Demo: http://jsfiddle.net/qkwdnjdy/5/
you can add data-trigger="focus" attribute to your anchor tag . it will do the needful. Here is the Fiddle

Accessing parent directive scope

So, I am trying to have 2 directives (techincally 3) on one page, which looks like this:
<div kd-alert newsletter></div>
<div kd-alert cookie></div>
this is on the index page, so there are no controllers.
I have been playing around with isolating scopes with directives and I have found that even though within the link function, scopes are isolated, if your directives use controllers the templates can see both controllers and if both controllers have a property with the same name they can be overwritten by the other controller, which is a nightmare so I decided to create a parent directive with one controller that serves the other 2 directives.
The parent directive in this case is called kd-alert and looks like this:
.directive('kdAlert', function () {
return {
restrict: 'A',
controller: 'AlertController',
link: function (scope, element, attr, controller) {
// Have to use a watch because of issues with other directives
scope.$watch(function () {
// Watch the dismiss
return controller.dismiss;
// If the value changes
}, function (dismiss) {
// If our value is false
if (dismiss === false || dismiss === 'false') {
// Remove the class from the element
element.removeClass('ng-hide');
// Else, if the value is true (or anything else)
} else {
// Add the class to the element
element.addClass('ng-hide');
}
});
// Get our buttons
var buttons = element.find('button');
// Binds our close button
scope.bindCloseButton = function (cookieName) {
// If we have a button
for (var i = 0; i < buttons.length; i++) {
// Get our current button
var button = angular.element(buttons[i]);
// If our button is the close button
if (button.hasClass('close')) {
// If the button is clicked
button.on('click', function (e) {
console.log('clicked');
// Prevent any default actions
e.preventDefault();
// dismiss the alert
controller.dismissAlert(cookieName);
// Remove our element
element.remove();
});
}
}
};
}
};
})
The controller handles methods for both child directives but is still pretty thin. It looks like this:
.controller('AlertController', ['$cookies', 'SubscriberService', 'toastr', function ($cookies, subscriverService, toastr) {
var self = this;
// Set our dismiss to false
self.dismiss = false;
// Set the flag
self.getDismissValue = function (cookieName) {
// Set our cookie
self.dismiss = $cookies[cookieName] || false;
};
// Set the flag
self.dismissAlert = function (cookieName) {
// Set our cookie
self.dismiss = $cookies[cookieName] = true;
};
// Saves our email address
self.subscribe = function (email, cookieName) {
// Subscribe
subscriverService.subscribe(email).success(function () {
// If we succeed, display a message
toastr.success('You will now recieve occasional newsletters.');
// Dismiss the alert
self.dismissAlert(cookieName);
});
};
}])
Now I have a cookie directive which works fine...
.directive('cookie', function () {
return {
restrict: 'A',
require: '^kdAlert',
templateUrl: '/assets/tpl/directives/cookie.html',
link: function (scope, element, attr, controller) {
console.log(scope);
// Get our cookie name
var cookieName = 'cookieAlert';
// Get our dismiss value
controller.getDismissValue(cookieName);
// Bind our close button
scope.bindCloseButton(cookieName);
}
};
})
When I refresh my page I can clearly see the scope with the bindCloseButton method within that scope. So far so good.
The problem is with the newsletter directive, it looks like this:
.directive('newsletter', function () {
return {
restrict: 'A',
require: '^kdAlert',
templateUrl: '/assets/tpl/directives/newsletter.html',
link: function (scope, element, attr, controller) {
console.log(scope);
// Get our cookie name
var cookieName = 'newsletterAlert';
// Get our dismiss value
controller.getDismissValue(cookieName);
// Bind our close button
scope.bindCloseButton(cookieName);
// Saves our email address
scope.subscribe = function (valid) {
// If we are not valid
if (!valid) {
// Return from the function
return;
}
// Subscribe
controller.subscribe(scope.email, cookieName);
};
}
};
})
Again, if I refresh the page I can clearly see the bindCloseButton method within that scope, but for some reason I get this error:
scope.bindCloseButton is not a function
And that appears on the line within the newsletter directive.
If I remove the cookie directive off the page, I still get the error.
Can anyone explain why?
use controller.bindCloseButton instead of scope.bindCloseButton .
This is happening because of the isolation of scope. you are doing in this approach and that's why you are losing scopes here.

Append a popup to body on click with Angular (and then remove)

I'm trying to wrap my head around the directive concept in Angular.
I want to show a modal box when clicking on a link. The contents of the modal box is dynamic. In jQuery it would be an easy $("body").append(myModal) and then simply remove() it from the DOM when closed.
Now I'd like to do the same in pure Angular. This is what I have so far:
A controller function:
$scope.userLogout = function() {
notification.show();
};
A service:
.service('notification', ['$rootScope',
function($rootScope) {
var notification = {
open: false,
show : function() {
this.open = true;
},
hide: function() {
this.open = false;
}
};
return notification;
}
])
A directive:
.directive('notification', ['notification',
function(notification){
return {
restrict: 'E',
replace: true,
template: (notification.open) ? '<div class="myModal"></div>' : ''
}
}])
How do I update the directive when the value in my service changes? Or is this the right approach at all?
For what it's worth, with something like Angular, it's possible to simply use data-ng-show and data-ng-hide on an element styled like a modal. Depending on your use case, you may not need to create a directive to achieve what you want. Consider the following:
HTML:
...
<div data-ng-show="notification.open" class="modalPopup">
...
{{notification.my_modal_message}}
...
<button data-ng-click="closeModal()">Close</button>
</div>
JS (simplified):
function myCtrl ($scope) {
$scope.notification = {
my_modal_message: "Bender's back, baby!",
open: false
}
$scope.logout = function () {
// logout stuff
logout().success(function () {
// open the modal
$scope.notification.open = true;
}
}
$scope.close = function () {
$scope.notification.open = false;
}
}
At times, it's much better to make a full directive to do something like this for you. However, again - depending on your use case - this may be all you need. Just something to keep in mind.

Why is this ng-show directive not working in a template?

I am trying to write a directive that will do a simple in-place edit for an element. This is my code so far:
directive('clickEdit', function() {
return {
restrict: 'A',
template: '<span ng-show="inEdit"><input ng-model="editModel"/></span>' +
'<span ng-show="!inEdit" ng-click="edit()">{{ editModel }}</span>',
scope: {
editModel: "=",
inEdit: "#"
},
link: function(scope, element, attr) {
scope.inEdit = false;
var savedValue = scope.editModel;
var input = element.find('input');
input.bind('keyup', function(e) {
if ( e.keyCode === 13 ) {
scope.save();
} else if ( e.keyCode === 27 ) {
scope.cancel();
}
});
scope.edit = function() {
scope.inEdit = true;
setTimeout(function(){
input[0].focus();
input[0].select();
}, 0);
};
scope.save = function() {
scope.inEdit = false;
};
scope.cancel = function() {
scope.inEdit = false;
scope.editModel = savedValue;
};
}
}
})
The scope.edit function sets inEdit to true, and that works well - it hides the text and shows the input tag. However, the scope.save function, which sets scope.inEdit to false does not work at all. It does not hide the input tag and show the text.
Why?
You are calling scope.save() from a event handler reacting to the keyup event. However this event handler is not called by/through the AngularJS framework. AngularJS will only scan for changes of the model if it believes that changes might have occured in order to lessen the workload (AngularJS as of now does dirty-checking with is computational intensive).
Therefore you must make use of the scope.$apply feature to make AngularJS aware that you are doing changes to the scope. Change the scope.save function to this and it shall work:
scope.save = function(){
scope.$apply(function(){
scope.inEdit = false;
});
});
Also it appears that there is actually no need to bind this save function to a scope variable. So you might want to instead define a "normal" function or just integrate the code into your event handler.

Resources