Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS - How to render a partial with variables?

Tags:

angularjs

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>
like image 679
colllin Avatar asked Jul 25 '13 16:07

colllin


3 Answers

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.

like image 148
maxdec Avatar answered Dec 26 '22 16:12

maxdec


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

jsFiddle: AngularJS - Include a partial with local variables


The Markup

<div with-locals locals-cars="allCars | onlyNew"></div>

What's going on:

  • This is basically an extension of the 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.
  • You can attach any number of locals-* attributes, which will all be parsed & watched for you as Angular expressions.
    • Those expressions become available to the included partial, attached as properties of a $scope.locals object.
    • In the example above, locals-cars="..." defines an expression that becomes available as $scope.locals.cars.
    • Similar to how a data-cars="..." attribute would be available via jQuery using .data().cars

The Directive

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));
                        }

                    }
                }
            };
        }
    };
});
like image 30
11 revs Avatar answered Dec 26 '22 15:12

11 revs


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.

like image 34
Tanin Avatar answered Dec 26 '22 16:12

Tanin