I am making an Angular App. I want the main menu to look like the tiles in Windows 8.
Now I want an algorithm that will automatically generate a pattern for tiles where some tiles are bigger than others.
So basically I would like to create a component to whichI will provide a dataSource as an array of Json objects containg menu-items.
and then the component should layout tiles in a fashion that it accommodates all of them.
How should I go about it?
I am learning angularjs. Can I avoid using jQuery plugins and do without it ?
I'm not sure you can handle this without an external JS library (mansory, isotope). But there is another trap you may step into. Angular is going to populate your DOM with promises, not tiles. Trouble is that Mansory/Isotope is trying to (re)calculate the new layout without having all data (width, height) necessary. You should use $timeout to sync Angular scope with external library.
isotopeApp.directive('isotope', function ($timeout) {
return {
link: function (scope, element, attrs) {
scope.$on('postAdded', function () {
$timeout(function () {
element.isotope('reloadItems').isotope({
sortBy: 'original-order'
});
})
})
}
};
});
http://jsfiddle.net/8Qmry/
Coming late to the party here, but if you don't want the DOM littered with a whole bunch of unnecessary digest cycles and spped up the reactiveness of the effect, adding 'false' to the timeout stops the digest cycles from going nuts and with larger datasets, speeds things up exponentially:
isotopeApp.directive('isotope', function ($timeout) {
return {
link: function (scope, element, attrs) {
scope.$on('postAdded', function () {
$timeout(function () {
element.isotope('reloadItems').isotope({
sortBy: 'original-order'
});
}, false)
})
}
};
});
Not changing much but a big performance boost.
Related
I'm trying to implement the "infinite-scroll" feature in a list using a directive, which should load progressively a new set of "orders" when the scroll of the html element reaches or exceeds 75% of the scrollable height and append it to the existing list.
Unfortunately, the watcher doesn't trigger when i scroll the list.
The directive is located in the right tag and the watcher triggers the listener function only the first time, when the element is rendered by the browser.
The strange thing is that if i change path and then i return to the path where the list is, the watcher start behaving correctly and trigger the listener function everytime i perform a scroll.
<ol orders-loader class="orders-list">...</ol>
angular:
(function () {
angular.
module('myApp')
.directive('ordersLoader', ['$window', '$timeout', 'ordersResource', ordersloaderDirective])
function ordersloaderDirective($window, $timeout, loading, ordersResource) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.orders = ordersResource; /*ordersResource use $resource to api calls
and then stocks the data in a array exposed in the scope*/
$timeout(function () {
scope.$watch(function () { return element[0].scrollTop }, function () {
if (*the scroll exceedes more or less 75% of the total scrollHeight*/) {
/*asking for more orders*/
}
});
}, 0);
}
}
}
I can't figure out where is the problem.
Solved
As yeouuu suggested, there was no digest cycle after the list scroll event, so i added:
element.bind('scroll', function () {
scope.$apply();
});
just before the $timeout function.
Whenever using plugins outside of angularJs that should trigger watcher you need to explicitly apply them. Otherwise Angular won't be aware of these changes/events.
In your case that means adding scope.$apply(); after the event.
Your edited solution:
element.bind('scroll', function () {
scope.$apply();
});
More information can be found here about the scope life cycle: https://docs.angularjs.org/guide/scope#scope-life-cycle
I'm building an app in AngularJS that uses LeafletJS for interacting with a map, offering different possible interactions separated across what I call phases. For each of those phases there is a UIRouter state, with its controller and template.
I'm currently providing the leaflet functionality through a service. The idea was for that service to initialise a Leaflet map and provide some limited access to the state's controller. Those controllers would thus call service functions such as setupMarkersInteractions to setup callbacks that enable marker placement on the map, for example.
However, I'm running into a problem when initialising the map through Leaflet's leaflet.map() function, namely: Error: Map container not found. This is related to Leaflet's inability to find the HTML element with which the map should be associated.
Currently, I'm kinda doing this:
function mapService() {
var map;
return {
initializeMap : initializeMap,
setupMarkersInteractions : setupMarkersInteractions
};
function initializeMap() {
map = leaflet.map('map');
}
function setupMarkersInteractions() {
map.on('click', markerPlacementCallback);
}
}
The initializeMap function tells leaflet.map to look for a HTML element with id='map', which is declared on the state's template.
Now, for the actual question, is this related to some kind of AngularJS services' inability to access the HTML template? I couldn't find anything on the matter, but I thought that it would make sense for services to not directly access the view...
If it is, what kind of workaround should I explore? I've looked into leaflet-directive, but it doesn't seem to offer the possibility to add and remove custom callbacks with the flexibility I would like to (things get complex when I add free draw functionality with Leaflet-Freedraw, for example).
I considered using leaflet.map directly with an HTMLElement argument for the element but still I couldn't make it work - although there is a probability that I'm not passing what is supposed to.
What's happening is that at the moment L.Map tries to access the DOM from your service, the template is available yet. Normally services get loaded and injected into controllers, controllers initialize their scopes, after that the templates get initialized and added to DOM. You'll see if you'll put a large timeout on your map initialization that it will find it's DOM element. But that's a very ugly hack. In Angular you should use a directive to add logic to DOM elements.
For example, a template: <leaflet></leaflet> and it's very basic directive:
angular.module('app').directive('leaflet', [
function () {
return {
replace: true,
template: '<div></div>',
link: function (scope, element, attributes) {
L.map(element[0]);
}
};
}
]);
You can hook that up to your service and pass the element to your initialization method:
angular.module('app').directive('leaflet', [
'mapService'
function (mapService) {
return {
replace: true,
template: '<div></div>',
link: function (scope, element, attributes) {
mapService.initializeMap(element[0]);
}
};
}
]);
That way the initializeMap method will only be called once the actual DOM element is available. But it presents you with another problem. At the moment your controller(s) are initialized, your service is not ready yet. You can solve this by using a promise:
angular.module('app').factory('leaflet', [
'$q',
function ($q) {
var deferred = $q.defer();
return {
map: deferred.promise,
resolve: function (element) {
deferred.resolve(new L.Map(element));
}
}
}
]);
angular.module('app').directive('leaflet', [
'leaflet',
function (leaflet) {
return {
replace: true,
template: '<div></div>',
link: function (scope, element, attributes) {
leaflet.resolve(element[0]);
}
};
}
]);
If you want to use the map instance in your controller you can now wait untill it's resolved:
angular.module('app').controller('rootController', [
'$scope', 'leaflet',
function ($scope, leaflet) {
leaflet.map.then(function (map) {
var tileLayer = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap',
maxZoom: 18
}).addTo(map);
map.setView([0, 0], 1);
L.marker([0, 0]).addTo(map);
});
}
]);
Here's an example of the concept on Plunker: http://plnkr.co/edit/DoJpGqtR7TWmKAeBZiYJ?p=preview
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.
I have a relatively large Angular application built on top of version 1.2.25. In my recent upgrade to 1.3.0 I discovered only one major issue which seems to change when html class changes are made to the DOM.
In this jsfiddle example, notice the watch that gets the size of the DOM element which is based off a class that another directive changes:
http://jsfiddle.net/travisgosselin/sx7jponj/1/
This example uses Angular 1.2.25.
scope.$watch(function () {
return {
width: element.closest('.checkSize').width(),
height: element.closest('.checkSize').height()
};
}, function (size) {
scope.height = size.height;
}, true);
Another jsfiddle that is exactly same but with Angular 1.3.0, you will notice that the watch does not fire the update since the class appears to be applied after the $watch has occurred, rendering no update to the size of the element.
http://jsfiddle.net/travisgosselin/t80segou/1/
scope.$watch(function () {
return {
width: element.closest('.checkSize').width(),
height: element.closest('.checkSize').height()
};
}, function (size) {
scope.height = size.height;
}, true);
The idea behind this is to allow one of my directives to resize independently of whats happening in the other component.
In the Angular change log the only mildly related change I can see is that relating to ngAnimate. See breaking changes under 1.3.0-rc.5:
The $animate CSS class API will always defer changes until the end of the next digest. This allows ngAnimate to coalesce class changes which occur over a short period of time into 1 or 2 DOM writes, rather than many. This prevents jank in browsers such as IE, and is generally a good thing.
If you find that your classes are not being immediately applied, be sure to invoke $digest().
Any idea's how I can best modify this scenario to get the watch on the dom element size to fire? My only alternative at this point is using a series of events that are fired from many different locations that force the resize (pub/sub).
There is a way, if you can include ngAnimate in your application.
$animate.enabled(false, element) -- Trigger this method inside the changeclass directive by passing element reference on which ng-class is applied
app.directive("changeClass", function ($animate) {
return {
restrict: 'A',
scope: true,
template: '<div><div ng-click="changeSize()">Change</div><div ng-class="{ \'biggerSize\': biggerSize }" class="smallerSize checkSize"><div read-class></div></div></div>',
replace: true,
link: function (scope, element, attrs) {
$animate.enabled(false, angular.element(element.children()[1]));
scope.biggerSize = false;
scope.changeSize = function () {
scope.biggerSize = !scope.biggerSize;
};
}
};
});
Working Fiddle
I am relatively new to Angular.
I have a html document in which angular creates a html table with ng-repeat. When this table has been built, I would like to apply to it a Jquery function. How can I do that ?
function : $("#creneaux").footable()
If I apply the function in the controller when it is instantiated, nothing happens. when I apply it in the javascript console when the page has been displayed, it works.
Firstly, I would move the $("#creneaux").footable() into a directive.
Solution:
Use $timeout without a delay to (a bit simplified) put the action at the end of the browser event queue after the rending engine:
app.directive('tableCreator', function($timeout) {
return {
link: function(scope, element, attrs) {
$timeout(function() {
$("#creneaux").footable();
});
}
};
});
Demo: http://plnkr.co/edit/b05YKhipeVmrVHu2Xzsm?p=preview
Good to know:
Depending on what you need to perform, you can instead use $evalAsync:
app.directive('tableCreator', function($timeout) {
return {
link: function(scope, element, attrs) {
scope.$evalAsync(function() {
$("#creneaux").footable();
});
}
};
});
The difference is that now the code will run after the DOM has been manipulated by Angular, but before the browser re-renders.
This can in certain cases remove some flickering that might be apparent between the rendering and the call to for example the jQuery plugin when using $timeout.
In the case of FooTable, the plugin will run correctly, but the responsiveness will not kick in until the next repaint, since the correct dimensions are not available until after rendering.
Try writing a directive.
app.directive('sample', function() {
return {
restrict: 'EA',
link: function(scope, element, attrs) {
// your jquery code goes here.
},
};
});
Learn to write everything in angular instead jquery. This may help you "Thinking in AngularJS" if I have a jQuery background?