AngularJS Custom-Directives replace option guide

AngularJS Directive’s replace option can be used to replace the container element itself by directive content. By default, the directive content inserted as the child of the element directive is applied on. But using replace, that container element altogether can be replaced by directive’s actual content HTML.

This post is a part of AngularJS Directives Tutorial Series.

Let’s understand this with help of few examples. Below is the live application from previous example:

Live Example:

If you inspect each item of this demo-example in Developer tools of your browser, you will find something like this:

<div ng-repeat="bla in ctrl.items" class="ng-scope">
	<item-widget item="bla" promo="Christmas-Sale" on-select="ctrl.onItemSelect(selectedItem)" class="ng-isolate-scope">
		<div class="panel panel-default">
			<div class="panel-heading ng-binding">
				Published at:<span ng-bind="item.published | date" class="ng-binding">01/11/2015</span> &nbsp;Promotion: Christmas-Sale<button class="pull-right" ng-click="pickMe({selectedItem:item.name})">Buy me</button>
			</div>
			<div class="panel-body">
				Name:<span ng-bind="item.name" class="ng-binding">Computer</span>
				Condition:<span ng-bind="item.condition" class="ng-binding">New</span>
				Price:<span ng-bind="item.price | currency" class="ng-binding">$500.00</span>
				Brand:<span ng-bind="item.brand" class="ng-binding">Lenovo</span>
			</div>
		</div>
	</item-widget>
</div>

You can see that there is an item-widget element for each item [or a specific ‘div’ if item-widget were used as an attribute of that div].

Whenever AngularJS encounters a directive, it fetches the content of that directive and insert it as a child of the element the directive was applied on. In this case it is the item-widget element itself. This is the default behavior. But sometimes, you may prefer the original element [ the directive is applied on] to be completely replaced by directive content.

AngularJS provides replace key as part of directive definition. It’s default value is false, means the container element will remain there. By setting it to true, you can replace the container element by actual content of directive. All attributes of the container element will then be migrated on the top level element in directive content.

Let’s add replace key in previous example, like shown below:

			.directive('itemWidget', [function() {
				return{
					replace:true,
					....//other elements as in above example.
			}]);

Live Example using replace option:

Now if you check each item in Developer tools, you will find following content:

<div ng-repeat="bla in ctrl.items" class="ng-scope">
	<div class="panel panel-default ng-isolate-scope" item="bla" promo="Christmas-Sale" on-select="ctrl.onItemSelect(selectedItem)">
		<div class="panel-heading ng-binding">
			Published at:<span ng-bind="item.published | date" class="ng-binding">01/11/2015</span> &nbsp;Promotion: Christmas-Sale<button class="pull-right" ng-click="pickMe({selectedItem:item.name})">Buy me</button>
		</div>
		<div class="panel-body">
				Name:<span ng-bind="item.name" class="ng-binding">Computer</span>
				Condition:<span ng-bind="item.condition" class="ng-binding">New</span>
				Price:<span ng-bind="item.price | currency" class="ng-binding">$500.00</span>
				Brand:<span ng-bind="item.brand" class="ng-binding">Lenovo</span>
		</div>
	</div>
</div>

The <item-widget> element is removed altogether , and all it’s attributes are migrated to top level div of actual content.

Remark: If you see carefully in developer tools, you will find that <div ng-repeat=”bla in ctrl.items” class=”ng-scope”> itself is repeated for each item. This is because for ng-repeat directive, the replace key is false.

Complete Code:

<html>
	<head>  
		<title>Directive Demo</title>  
		<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
	</head>
	<body class="jumbotron container" ng-app="myApp">
		<div ng-controller="AppController as ctrl">
			<h3>List of Sale Items.</h3>
			<div ng-repeat="bla in ctrl.items">
				<item-widget item="bla" promo="Christmas-Sale" on-select="ctrl.onItemSelect(selectedItem)"></item-widget>
			</div>
		</div>
      
		<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.js">
		</script>  
		<script>
			angular.module('myApp', [])
			.controller('AppController', [function() {
				var self = this;
				self.items = [
							{name: 'Computer', price: 500, condition:'New',brand : 'Lenovo', published:'01/11/2015'},
							{name: 'Phone', price: 200, condition:'New',brand : 'Samsung', published:'02/11/2015'},
							{name: 'Printer', price: 300, condition:'New',brand : 'Brother', published:'06/11/2015'},
							{name: 'Dishwasher', price: 250, condition:'Second-Hand',brand : 'WhirlPool', published:'01/12/2015'},
							];
				self.onItemSelect = function(name) {
					console.log('Congrats. You have just bought a', name);
				};							
			}])
			.directive('itemWidget', [function() {
				return{
					restrict: 'E',
					replace:true,
					scope: {
						item: '=',
						promo: '@',
						pickMe : '&onSelect'
					},
    				templateUrl: 'saleItem3.html',
					link : function(scope, element, attrs){
					    //Add event listener for 'click' event					
						element.on('click', function(event) {
						
								element.html('Thanks for buying this item.');
								element.css({
									color: 'green'
								});
						  });
					}
				}
			}]);
		</script>
	</body>
</html>

Shown below is the directive’s content[saleItem3.html].

<div class="panel panel-default">
	<div class="panel-heading">
		Published at:<span ng-bind="item.published | date"></span> &nbsp;Promotion: {{promo}}<button class="pull-right" ng-click="pickMe({selectedItem:item.name})">Buy me</button>
	</div>
	<div class="panel-body">
			Name:<span ng-bind="item.name"></span>
			Condition:<span ng-bind="item.condition"></span>
			Price:<span ng-bind="item.price | currency"></span>
			Brand:<span ng-bind="item.brand"></span>
	</div>
</div>

That’s it for replace option. Let’s move to next configuration option [transclude with ngTransclude] of Directive Definition Object, which can be used to create directive that wraps other elements.

Next Topic : AngularJS Directives transclude option with ngTransclude


Please note that if your AngularJS application is using templateUrl to access HTML partials, you will need an HTTP server on front to serve them. This is due to the fact that Browser’s does not allow serving or requesting files on the file:// protocol.

You can use popular Apache HTTP server to serve files. If you need assistance setting-up server,
this post can help you.

Download Source Code

References