AngularJS directive not firing (partial views and .remove()) - angularjs

My main overview page has a few partial views in it (that run on different links). Each of these partial views has an element that refers to a directive (expander).
My problem is if I go to a partial view that has that directive, and then go back to the main screen and to a different partial view, the directive won't run on the newest screen.
The partials are loaded with ajax calls, and removed using .remove() on the parent in the view.
I'm not sure where the problem would be, so here are some bits of code that might be relevant.
My expander directive:
angular.module("toolkit").directive("expander", ['$timeout', function($timeout)
{
function determine(scope, heightValue) {
if (scope.isExpanded) collapse(heightValue);
else expand(heightValue);
}
function expand(heightValue) {
//does expanding things
}
function collapse(heightValue) {
//does collapsing things
}
return {
restrict: 'A',
link: function (scope, elm, attrs) {
var height = 0;
scope.$watch(attrs.expanderHeight, function (value) {
height = value;
});
$timeout(
function () {
$(elm[0]).on('click', function () {
scope.$apply(determine(scope, height));
});
}
)
}
};
}]);
Html for partial views (they all have this same thing):
<div class="acct-sum round-a accordion-top">
<div expander expander-height="170" class="title">
<span class="ui-icon ui-icon-circle-plus"></span>
<div class="accordion-inner">
Irrelevant things here.
</div>
</div>
</div>
This directive runs in every partial view as long as its the first I go to. But after switching back from one view to another, it stops working. I've put breakpoints into it and it never hits it at all.
Any ideas? I can post more code if anyone will find it relevant.
Edit:
The partial views are loaded on click like so:
// Bind account summary links to load the accountHistory partial view.
$(".view-history").on("click", function (e) {
e.preventDefault();
hide();
var acctId = $(this).data("account");
var t = $("#body_content_wrapper");
t.append("<div class='progress' />");
// load the template from the server in lue of client-routing
util.load({
viewId: "accounthistory",
target: t,
action: util.config.baseUri + "accountHistory",
type: "POST",
data: { id: $(this).data("account") }
})
.complete(function (html) {
$(".progress").remove();
util.publish("telnot.views.showAccountHistory", { account: acctId });
});
});

Related

Angular and preloader for an ajax template

I have a dashboard with several templates.
In one of the templates, I have a simple list. I'm using ng-repeat on my li's and that way I keep my list dynamic.
But here's the thing -
Since I'm getting the data for this list using $http, it is likely to have an empty list for a second or two.
A good solution for this would be to add a preloader for my list by default, but how would you suggest to add the logic for that? The easiest way would be to add it like so:
$http({
method: 'GET',
url: './data/websites.json'
}).then(function successCallback(response) {
// hide preloader, etc
});
Would it be the right way to go?
Also - is there anyway to have control on the template transitioning? for example, when a user left a page I want to show a preloader for X milliseconds, and only then move to the requested page
It is better to have a directive does everything for you:
angular.module('directive.loading', [])
.directive('loading', ['$http' ,function ($http)
{
return {
restrict: 'A',
link: function (scope, elm, attrs)
{
scope.isLoading = function () {
return $http.pendingRequests.length > 0;
};
scope.$watch(scope.isLoading, function (v)
{
if(v){
elm.show();
}else{
elm.hide();
}
});
}
};
}]);
With this directive, all you need to do is to give any loading animation element an 'loading' attribute:
<div class="loading-spiner-holder" data-loading ><div class="loading-spiner"><img src="..." /></div></div>

AngularJs toggle template with directive to create notification like stackoverflow

I'm trying to create a notification system in AngularJs just like the notification used here. When there is a new comment, answer, etc.. The archive icon shows a red sign with the number of activities, and when I click on it, it opens up a box with the last notifications.
To do this, I built this simple directive to dynamic loads a templateUrl:
html:
<li test-alert ref="msg">
<i class="fa fa-envelope-o"></i>
</li>
<li test-alert ref="bell">
<i class="fa fa-bell-o"></i>
</li>
directive:
angular
.module('agApp')
.directive('testAlert', testAlert)
;
/* #ngInject */
function testAlert() {
var templateA = '<div>Test template A</div>';
var templateB = '<div>Test template B</div>';
return{
restrict: 'A',
scope: {
ref: '#'
},
link: function(scope,element,attrs,controller){
scope.showAlert = false;
element.on("click", function() {
if (scope.ref == 'bell') {
scope.showAlert = true;
element.append(templateA);
scope.$apply();
} else {
scope.showAlert = true;
element.append(templateB);
scope.$apply();
};
console.log(scope.ref);
});
element.addEventListener("keyup", function(e) {
if (e.keyCode === 27) {
scope.showAlert = false;
}
});
}
};
}; //end test alert
But I'm with some problems..
If i click on the icon to open the template it will open, but every time i click on it, it will append another template. Id' like it to change (if it's the other template) or do nothing.
When it is opened, I can't make it close. I can use a 'close' button, but I'd like to close/remove the template when the user click on the document or press esc;
The code I tried to use to close on 'Esc' key, doesn't work.
My main objective is to create a notification system just like the one in stackOverflow, so is this the best way to do it? Should I use a controller instead?
Edit:
Close mechanism I'm using at the momment. It's working, but maybe it can be improved.
run.js
angular
.module('agApp')
.run(runApp);
/* #ngInject */
function runApp($rootScope) {
document.addEventListener("keyup", function(e) {
if (e.keyCode === 27) {
$rootScope.$broadcast("escapePressed", e.target);
};
});
document.addEventListener("click", function(e) {
$rootScope.$broadcast("documentClicked", e.target);
});
}; //end run
controller.js
$rootScope.$on("documentClicked", _close);
$rootScope.$on("escapePressed", _close);
function _close() {
$scope.$apply(function() {
vm.closeAlert();
});
};
Since I wasn't able to use it as a directive, I moved the open/close function inside a controller. But it can be used in any other way, as long as it works, there is no problem.
First off, key events only fire on the document and elements that may receive focus.
Directives are really nice for things you need to use multiple times. But even if you implement your notification system as a directive and only use it once - you will have it isolated, which is often good.
Hard to give the best solution without knowing more but here is one example that implements the messages and the notifications as one directive:
app.directive('notifications',
function() {
return {
restrict: 'E',
templateUrl: 'template.html',
scope: {},
link: function(scope, element, attrs, controller) {
scope.viewModel = {
showTemplateA: false,
showTemplateB: false
};
scope.toggleTemplateA = function() {
scope.viewModel.showTemplateA = !scope.viewModel.showTemplateA;
scope.viewModel.showTemplateB = false;
};
scope.toggleTemplateB = function() {
scope.viewModel.showTemplateB = !scope.viewModel.showTemplateB;
scope.viewModel.showTemplateA = false;
};
}
};
});
It simply contains logic for showing and hiding the templates. The directive uses a template that looks like this:
<div>
<i class="fa fa-envelope-o" ng-click="toggleTemplateA()"></i>
<div ng-show="viewModel.showTemplateA">
Template A
</div>
<br>
<i class="fa fa-bell-o" ng-click="toggleTemplateB()"></i>
<div ng-show="viewModel.showTemplateB">
Template B</div>
</div>
The template uses ng-show and ng-click to bind to our scope functions. This way we let Angular do the job and don't have to mess around with element.append etc.
Usage:
<notifications></notifications>
Demo: http://plnkr.co/edit/8M1D5uENjpDoIbb1ZuMR?p=preview
To implement your closing mechanism you can add the following to the directive:
var close = function () {
scope.$apply(function () {
scope.viewModel.showTemplateA = false;
scope.viewModel.showTemplateB = false;
});
};
$document.on('click', close);
$document.on('keyup', function (e) {
if (e.keyCode === 27) {
close();
}
});
scope.$on('$destroy', function () {
$document.off('click', close);
$document.off('keyup', close);
});
Note that you now have to inject $document into the directive:
app.directive('notifications', ['$document',
function($document) {
In the toggle functions you can call stopPropagation() to prevent the global closing handler to execute when you click the icons (might not be needed in this example, but good to know. Might want it on the actual templates in the future?):
scope.toggleTemplateA = function($event) {
$event.stopPropagation();
And:
ng-click="toggleTemplateA($event)"
Demo: http://plnkr.co/edit/LHS4RBE7qtY4yNyEdR16?p=preview

AngularJS directives and require

I have this directive that is getting more and more complicated. So I decided to split it up into parts.
The directive itself loaded a garment SVG graphic, when the SVG loaded it then ran a configure method which would apply a design, applied picked colours (or database colours if editing) and other bits and pieces.
As I said, it was all in one directive, but I have now decided to separate the logic out.
So I created my first directive:
.directive('configurator', function () {
// Swap around the front or back of the garment
var changeView = function (element, orientation) {
// If we are viewing the front
if (orientation) {
// We are viewing the front
element.addClass('front').removeClass('back');
} else {
// Otherwise, we are viewing the back
element.addClass('back').removeClass('front');
}
};
return {
restrict: 'A',
scope: {
garment: '=',
onComplete: '&'
},
require: ['configuratorDesigns'],
transclude: true,
templateUrl: '/assets/tpl/directives/kit.html',
link: function (scope, element, attrs, controllers) {
// Configure our private properties
var readonly = attrs.hasOwnProperty('readonly') || false;
// Configure our scope properties
scope.viewFront = true;
scope.controls = attrs.hasOwnProperty('controls') || false;
scope.svgPath = 'assets/garments/' + scope.garment.slug + '.svg';
// Apply the front class to our element
element.addClass('front').removeClass('back');
// Swaps the design from front to back and visa versa
scope.rotate = function () {
// Change the orientation
scope.viewFront = !scope.viewFront;
// Change our view
changeView(element, scope.viewFront);
};
// Executes after the svg has loaded
scope.loaded = function () {
// Call the callback function
scope.onComplete();
};
}
};
})
This is pretty simple in design, it gets the garment and finds the right SVG file and loads it in using ng-transclude.
Once the file has loaded a callback function is invoked, this just tells the view that it is on that it has finished loading.
There are a few other bits and pieces that you should be able to work out (changing views, etc).
In this example I am only requiring one other directive, but in the project there are 3 required directives, but to avoid complications, one will suffice to demonstrate my problem.
My second directive is what is needed to apply the design. It looks like this:
.directive('configuratorDesigns', function () {
return {
restrict: 'A',
controller: 'ConfiguratorDesignsDirectiveController',
link: function (scope, element, attrs, controller) {
// Get our private properties
var garment = scope.$eval(attrs.garment),
designs = scope.$eval(attrs.configuratorDesigns);
// Set our controller designs array
controller.designs = designs;
// If our design has been set, watch it for changes
scope.$watch(function () {
// Return our design
return garment.design;
}, function (design) {
// If we have a design
if (design) {
// Change our design
controller.showDesign(element, garment);
}
});
}
}
})
The controller for this directive just loops through the SVG and finds the design that matches the garment design object. If it finds it, it just hides the others and shows that one.
The problem I have is that this directive is unaware of the SVG loading or not. In the "parent" directive I have the scope.loaded function which is executed when the SVG has finished loading.
The "parent" directive's template looks like this:
<div ng-transclude></div>
<div ng-include="svgPath" onload="loaded()"></div>
<span class="glyphicon glyphicon-refresh"></span>
So my question is this:
How can I get the required directives to be aware of the SVG loaded state?
If I understand your question correctly, $rootScope.broadcast should help you out. Just broadcast when the loading is complete. Publish a message from the directive you are loading the image. On the directive which needs to know when the loading is complete, listen for the message.

How to get angular directive to execute each time a view is accessed without refreshing the page

I have created a custom directive that wraps a jquery function which transforms html into a a place where users can enter tags (similar to SO tagging functionality).
Here's my directive code:
App.directive('ngTagItWrapper', function ($resource, rootUrl) {
$("#myTags").tagit({
allowSpaces: true,
minLength: 2,
tagSource: function (search, showChoices) {
$.ajax({
url: rootUrl + "/api/Tag?searchTerm=" + search.term,
data: search,
success: function (choices) {
choices = $.map(choices, function (val, i) {
return val.Name;
})
showChoices(choices);
}
});
}
});
return {}
});
When I first navigate to the view containing the directive, the directive fires which transforms the ul html element into what I need. However, if I click onto another part of the site and then click back to the part of the site that contains the tag entry screen, the directive never fires and the html is not transformed into a nice place where I can enter tags.
Here's the view code:
<p>Hint: <input ng-model="knowledgeBit.Hint" /></p>
<p>Content: <input ng-model="knowledgeBit.Content"/></p>
<ul id="myTags" ng-tag-it-wrapper>
</ul>
<button ng-click="saveKnowledgeBit()">Save</button>
If I refresh the page, the directive fires and I get the tag entry area. So basically angular doesn't know to fire the directive again unless I completely reload the site. Any ideas?
A directive constructor only run once and then $compile caches the returned definition object.
What you need is to put your code inside a linking function:
App.directive('ngTagItWrapper', function ($resource, rootUrl) {
return {
link: function(scope, elm){
elm.tagit({
allowSpaces: true,
minLength: 2,
tagSource: function (search, showChoices) {
$.ajax({
url: rootUrl + "/api/Tag?searchTerm=" + search.term,
data: search,
success: function (choices) {
choices = $.map(choices, function (val, i) {
return val.Name;
})
showChoices(choices);
}
});
}
});
}
}
});

How do I use an Angular directive to show a dialog?

Using Angular, I'm trying to create a directive that will be placed on a button that will launch a search dialog. There are multiple instances of the search button, but obviously I only want a single instance of the dialog. The dialog should be built from a template URL and have it's own controller, but when the user selects an item, the directive will be used to set the value.
Any ideas on how to create the dialog with it's own controller from the directive?
Here's what I've go so far (basically just the directive)...
http://plnkr.co/edit/W9CHO7pfIo9d7KDe3wH6?p=preview
Here is the html from the above plkr...
Find
Here is the code from the above plkr...
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
var person = {};
person.name = 'World';
$scope.person = person;
$scope.setPerson = function(newPerson) {
person = newPerson;
$scope.person = person;
}
});
app.directive('myFind', function () {
var $dlg; // holds the reference to the dialog. Only 1 per app.
return {
restrict: 'A',
link: function (scope, el, attrs) {
if (!$dlg) {
//todo: create the dialog from a template.
$dlg = true;
}
el.bind('click', function () {
//todo: use the dialog box to search.
// This is just test data to show what I'm trying to accomplish.
alert('Find Person');
var foundPerson = {};
foundPerson.name = 'Brian';
scope.$apply(function () {
scope[attrs.myFind](foundPerson);
});
});
}
}
})
This is as far as I've gotten. I can't quite figure out how to create the dialog using a template inside the directive so it only occurs once and then assign it a controller. I think I can assign the controller inside the template, but first I need to figure out how to load the template and call our custom jQuery plugin to generate the dialog (we have our own look & feel for dialogs).
So I believe the question is, how do I load a template inside of a directive? However, if there is a different way of thinking about this problem, I would be interested in that as well.
I will show you how to do it using bootstrap-ui. (you can modify it easily, if it does not suit your needs).
Here is a skeleton of the template. You can normally bound to any properties and functions that are on directive's scope:
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
... // e.g. <div class="button" ng-click=cancel()></div>
</div>
<div class="modal-body">
...
</div>
<div class="modal-footer">
...
</div>
</div>
</div>
Here is how to create/declare directive in your module:
.directive("searchDialog", function ($modal) {
return {
controller: SearchDialogCtrl,
scope : {
searchDialog: '=' // here you will set two-way data bind with a property from the parent scope
},
link: function (scope, element, attrs) {
element.on("click", function (event) { // when button is clicked we show the dialog
scope.modalInstance = $modal.open({
templateUrl: 'views/search.dialog.tpl.html',
scope: scope // this will pass the isoleted scope of search-dialog to the angular-ui modal
});
scope.$apply();
});
}
}
});
Then controller may look something like that:
function SearchDialogCtrl(dep1, dep2) {
$scope.cancel = function() {
$scope.modalInstance.close(); // the same instance that was created in element.on('click',...)
}
// you can call it from the template: search.dialog.tpl.html
$scope.someFunction = function () { ... }
// it can bind to it in the search.dialog.tpl.html
$scope.someProperty;
...
// this will be two-way bound with some property from the parent field (look below)
// if you want to perform some action on it just use $scope.$watch
$scope.searchDialog;
}
Then it your mark-up you can just use it like that:
<div class="buttonClass" search-dialog="myFieldFromScope">search</div>
I recommend this plugin:
https://github.com/trees4/ng-modal
Demo here:
https://trees4.github.io/ngModal/demo.html
Create a dialog declaratively; and it works with existing controllers. The content of the dialog can be styled however you like.

Resources