JSFiddle - React, Tailwind, and code Playground

HTML

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.js"></script>
<div ng-app="app" ng-controller="MainCtrl as mc">
  <input-person ng-model="mc.data.person1"></input-person>
	<br/>
  <input-person ng-model="mc.data.person2"></input-person>
  <button ng-click="mc.resetPerson2()">Reset Person 2</button>
	<br/>
	<pre><code>{{ mc.data | json }}</code></pre>
</div>

CSS

* {
  box-sizing: border-box;
}

input-person {
  display: block;
}
input-person > div > label {
  display: block;
  width: 280px;
  margin-bottom: 3px;
}
input-person > div > label > span {
  display: inline-block;
  width: 120px;
}
input-person > div > label > input {
  display: inline-block;
  width: 160px;
}


input-address {
  display: block;
}
input-address > div > label {
  display: block;
  width: 280px;
  margin-bottom: 3px;
}
input-address > div > label > span {
  display: inline-block;
  width: 120px;
}
input-address > div > label > input {
  display: inline-block;
  width: 160px;
}

JavaScript

var app = angular.module('app', []);

function Person(data) {
	data = data || {};
	this.name = data.name;
	this.address = data.address ? new Address(data.address) : null;
}

function Address(data) {
	data = data || {};
	this.street = data.street;
	this.number = data.number;
}

app.controller('MainCtrl', function() {
	this.data = {
		person1: null,
		person2: new Person({
			name: 'Bob',
			address: new Address({
				street: 'Pineapple',
				number: '7'
			})
		})
	};
	
	this.resetPerson2 = function() {
		this.data.person2 = new Person({
			name: 'Bob',
			address: new Address({
				street: 'Pineapple',
				number: '7'
			})
		});
	}
});

app.directive('inputPerson', function() {

	InputPersonController.$inject = ['$scope'];
	function InputPersonController($scope) {
		this.$scope = $scope;
		this._ngModel = null;
		this.value = null;
		this._unwatch = angular.noop;
	}

	InputPersonController.prototype.setNgModel = function(ngModel) {
		this._ngModel = ngModel;
		
		if( ngModel ) {
			ngModel.$render = this._render.bind(this);
		}
	};
	
	InputPersonController.prototype._makeWatch = function() {
		this._unwatch = this.$scope.$watchCollection(
			(function() {
				return this.value;
			}).bind(this),
			(function(newval, oldval) {
				if( newval !== oldval ) { // skip the initial trigger
					this._ngModel.$setViewValue(newval !== null ? new Person(newval) : null);
				}
			}).bind(this)
		);
	};
	
	InputPersonController.prototype._render = function() {
		this._unwatch();
		this.value = this._ngModel.$viewValue ? new Person(this._ngModel.$viewValue) : null;
		this._makeWatch();
	};

	return {
		restrict: 'E',
		scope: {},
		bindToController: true,
		controllerAs: 'ctrl',
		controller: InputPersonController,
		require: ['inputPerson', 'ngModel'],
		link: function(scope, elem, attrs, ctrls) {
			ctrls[0].setNgModel(ctrls[1]);
		},
		template:
			'<div>' +
				'<label><span>Name:</span><input type="text" ng-model="ctrl.value.name" /></label>' +
				'<input-address...