For example, I have a partial in car-list.html
, and I want to render it in several places with different collections of cars. Maybe something like this:
<h1>All New Cars</h1>
<div ng-include="car-list.html" ng-data-cars="allCars | onlyNew"></div>
<h1>All Toyotas</h1>
<div ng-include="car-list.html" ng-data-cars="allCars | make:toyota"></div>
The main difference from a normal include is that the partial doesn't need to know anything about which list of cars it's displaying. It's given an array of cars, and it displays them. Possibly like:
<!-- car-list.html -->
<div ng-repeat="car in cars" ng-controller="CarListControl">
{{car.year}} {{car.make}} {{car.model}}
</div>
You can achieve that easily with a directive
.
Something like that:
angular.module('myModule')
.directive('cars', function () {
return {
restrict: 'E',
scope: { 'cars': '=data' },
template: "<div ng-repeat='car in cars'>\n" +
" {{car.year}} {{car.make}} {{car.model}}\n" +
"</div>"
};
});
Then you can use it like that:
<h1>All New Cars</h1>
<cars data="allCars | onlyNew"></cars>
<h1>All Toyotas</h1>
<cars data="allCars | make:toyota"></cars>
You can find more info about directives here.
This directive provides 2-way data-binding between the parent scope and renamed "local" variables in the child scope. It can be combined with other directives like ng-include
for awesome template reusability. Requires AngularJS 1.2.x
<div with-locals locals-cars="allCars | onlyNew"></div>
What's going on:
ngInclude
directive to allow you to pass renamed variables in from the parent scope. ngInclude
is NOT required at all, but this directive has been designed to work well with it.locals-*
attributes, which will all be parsed & watched for you as Angular expressions.
$scope.locals
object.locals-cars="..."
defines an expression that becomes available as $scope.locals.cars
.data-cars="..."
attribute would be available via jQuery using .data().cars
EDIT I've refactored to make use of (and be independent of) the native ngInclude
directive, and move some of the calculations into the compile function for improved efficiency.
angular.module('withLocals', [])
.directive('withLocals', function($parse) {
return {
scope: true,
compile: function(element, attributes, transclusion) {
// for each attribute that matches locals-* (camelcased to locals[A-Z0-9]),
// capture the "key" intended for the local variable so that we can later
// map it into $scope.locals (in the linking function below)
var mapLocalsToParentExp = {};
for (attr in attributes) {
if (attributes.hasOwnProperty(attr) && /^locals[A-Z0-9]/.test(attr)) {
var localKey = attr.slice(6);
localKey = localKey[0].toLowerCase() + localKey.slice(1);
mapLocalsToParentExp[localKey] = attributes[attr];
}
}
var updateParentValueFunction = function($scope, localKey) {
// Find the $parent scope that initialized this directive.
// Important in cases where controllers have caused this $scope to be deeply nested inside the original parent
var $parent = $scope.$parent;
while (!$parent.hasOwnProperty(mapLocalsToParentExp[localKey])) {
$parent = $parent.$parent;
}
return function(newValue) {
$parse(mapLocalsToParentExp[localKey]).assign($parent, newValue);
}
};
return {
pre: function($scope, $element, $attributes) {
// setup `$scope.locals` hash so that we can map expressions
// from the parent scope into it.
$scope.locals = {};
for (localKey in mapLocalsToParentExp) {
// For each local key, $watch the provided expression and update
// the $scope.locals hash (i.e. attribute `locals-cars` has key
// `cars` and the $watch()ed value maps to `$scope.locals.cars`)
$scope.$watch(
mapLocalsToParentExp[localKey],
function(localKey) {
return function(newValue, oldValue) {
$scope.locals[localKey] = newValue;
};
}(localKey),
true
);
// Also watch the local value and propagate any changes
// back up to the parent scope.
var parsedGetter = $parse(mapLocalsToParentExp[localKey]);
if (parsedGetter.assign) {
$scope.$watch('locals.'+localKey, updateParentValueFunction($scope, localKey));
}
}
}
};
}
};
});
I'd like to offer my solution, which is in a different design.
The ideal usage for you is:
<div ng-include-template="car-list.html" ng-include-variables="{ cars: (allCars | onlyNew) }"></div>
ng-include-variables's object is added to the local scope. Therefore, it doesn't litter your global (or parent) scope.
Here's your directive:
.directive(
'ngIncludeTemplate'
() ->
{
templateUrl: (elem, attrs) -> attrs.ngIncludeTemplate
restrict: 'A'
scope: {
'ngIncludeVariables': '&'
}
link: (scope, elem, attrs) ->
vars = scope.ngIncludeVariables()
for key, value of vars
scope[key] = value
}
)
(It's in Coffeescript)
IMO, ng-include is a little bit strange. Having access to the global scope decreases its reusability.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With