AngularJs Primitive Data Tipli Değişkenlerde Two-Way Binding Sorunu

streaming of binary code numbers technology background

Scope Nedir?

Scope, ifadelerin (expression) yürütüldüğü bağlamı (context) temsil eden nesnelerdir (object). Yani bir ifade çalıştırılacağı zaman (obj.prop == 1 gibi) bu ifadede kullanılan özelliklerin (property) arandığı bağlamdır (scope ile ilgili detaylı bilgiye https://docs.angularjs.org/guide/scope adresi üzerinden ulaşılabilir). Scope sonuç olarak bir Javascript nesnesidir ve bir Javascript nesnesinin özelliklerini taşır.

Javascript Prototipsel Kalıtım

Javascript’te her nesnenin bir prototipi (prototype) bulunur. Bu prototip bir nesne veya null olabilir ve nesnenin içerisindeki __proto__ alanı içerisinde bulunur. Bu alanın isminin __proto__ olması zorunlu değildir ancak bu alana __proto__ isminin verilmesi de-facto standarttır. Bir nesne oluşturup bu nesneyi yazdırırsak bizim tarafımızdan böyle bir alan tanımlamamış olmasına rağmen bu nesne içerisinde __proto__ isimli bir alan bulunduğu görürüz.

let obj1 = {
	prop1: "val1",
	prop2: "val2"
}

console.log(obj1)

Prototipler kalıtım (inheritance) sağlayan bir mekanizma olarak düşünülebilir. Bir nesnenin bir alanına erişilmeye çalışıldığı zaman bu alan aynı zamanda bu nesnenin prototipi içerisinde de aranır. Örnek olarak iki adet nesnemiz olsun.

let obj1 = {
	prop1: "val1",
	prop2: "val2"
}

let obj2 = {
	prop3: "val3"
}

Daha sonra obj2 isimli nesneyi obj1 isimli nesnenin prototipi olarak belirleyelim.

obj1.__proto__ = obj2

Bu işlemi tamamladıktan sonra obj1 isimli nesnenin prop3 isimli alanına erişebiliriz.

console.log(obj1.prop3)

Veya obj1 nesnesini yazdırıp __proto__ alanı içerisine baktığımız zaman prop3 alanı ile karşılaşırız.

console.log(obj1)

Dot-notation Neden Gerekli?

Scope’lar, varsayılan olarak, uygulamanın DOM yapısını taklit eden hiyerarşilere sahiptir ve bu hiyerarşiler prototip zincirleriyle ifade edilir. Örnek olarak iki adet controller oluşturalım ve bu controller’ların içerisinde scope nesnelerine birer alan tanımlayalım. Daha sonra hiyerarşide bulunan controller’ların scope’larını yazdıralım.

module.controller('parentController', function($scope) {
	$scope.prop1 = "val1";
	
	console.log('parent scope');
	console.log($scope);
});

module.controller('childController', function($scope) {
	$scope.prop2 = "val2";
	
	console.log('child scope');
	console.log($scope);
});

Hiyerarşide daha alt seviyede bulunan childController controller’ının scope’unun prototipine bakarsak (__proto__ alanına), prototip içerisinde hiyerarşide daha üst seviyede bulunan parentController controller’ının scope’unu görürüz. Ayrıca tüm scope’ların prototipinde ebeveyn controller’ın scope’u bulunmak zorunda olmadığını belirtmekte fayda var.

Şu ana kadar scope’ların ne olduğundan ve aralarındaki hiyerarşinin prototip zincirleri tarafından sağlandığından bahsettik. Artık prototipsel kalıtımın konumuz için kritik bir özelliğinden bahsedebiliriz.

Bir nesne içerisinde, aynı zamanda prototipi içerisinde bulunan bir özelliğin değeri kurulmaya (set) çalışıldığında prototip içerisindeki özellik değişmez. Bunun yerine nesne içerisinde yeni bir özellik oluşur ve bu özelliğin değeri verdiğimiz yeni değere eşit olur.

module.controller('parentController', function($scope) {
	$scope.prop1 = "old_val";
});

module.controller('childController', function($scope) {
	// scope içerisindeki prop1
	// özellikleğinin değerini
	// güncelleme girişiminde 
	// bulunduk
	$scope.prop1 = "new_val";
	
	// scope içerisindeki prop2
	// özellikleğinin değerini 
	// güncelleme girişiminde 
	// bulunduktan sonra scope
	// nesnesinin durumunu yazdırdık
	console.log($scope);
});

Programın çıktısına bakarsak parent scope nesnesi içerisinde bulunan prop1 özelliğinin değişmediğini, bunun yerine child scope nesnesi içerisinde yeni bir prop1 özelliğinin oluşturulduğunu görürüz.

Javascript prototipsel kalıtımın bu özelliği bazı öngörmesi ve anlaşılması güç durumlara sebep olabilir. Örnek olarak iki adet controller oluşturalım. Bu controller’ların ikisinin de içerisinde birer input oluşturalım ve bu inputları parent controller scope’unda bulunan bir özelliğe bağlayalım (bind).

Child controller içerisinde bulunan input’un değerini değiştirmeden, parent controller içerisinde bulunan input’un değeri değiştirildiğinde child controller içerisinde bulunan input’un da değerinin değiştiği görülür. Ancak child controller içerisinde bulunan input’u değiştirdiğimiz zaman parent controller içerisinde bulunan input’un değerinin değişmediğini ve bunda sonra parent controller içerisinde bulunan input’un değeri değiştirildiğinde bu değişikliğin child controller içerisindeki input’a yansımadığını görürüz.

Bu gibi durumları önlemek için dot-notation kullanılarak, önce bir nesneye erişip (get) daha sonra bu nesnenin içerisindeki özellikler güncellenebilir. Böylece atama işleminden önce bir erişim işlemi gerçekleştirmiş oluruz ve nesne içerisinde bir değer kurulmaya çalışıldığında bu özelliğin prototip içerisinde güncellenmesi mi gerektiği yoksa nesne içerisinde yeni bir özellik mi tanımlanması gerektiğiyle ilgili belirsizliği ortadan kaldırmış oluruz. Bir önceki örneği güncelleyecek olursak.

Artık child controller içerisinde bulunan input güncellendiğinde değişikliklerin parent controller içerisindeki input’a yansıdığı görülür.

Transclusion

Transclusion, bir dökümanın (document) bir parçasının veya tümünün başka bir döküman içerisine dahil edilmesini sağlayan bir mekanizmadır. Yukarıda belirtilen problemin en sık karşılaşıldığı durumlar directive içerisinde transclusion kullanıldığı durumlardır.

Yeni bir directive tanımlandığında eğer aksi belirtilmezse bu yeni directive’in scope’u ve içerisinde bulunduğu controller’ın scope’u ortaktır.

<div ng-controller="parentController">
	<ka-test></ka-test>
</div>


module.controller('parentController', function($scope) {
	$scope.prop1 = "val1";

	console.log('parent scope');
	console.log($scope);
});

module.directive('kaTest', function() {
	return {
		controller: function($scope) {
    	console.log('directive scope');
			console.log($scope);
		}
	}
});

Yukarıdaki çıktıda da görüldüğü gibi parentController scope’u ve directive scope’u aynı id’lere sahiptir. Yani bu iki scope’ un referansı, veya hafıza içerisinde gösterdiği adres, aynıdır. Bu iki scope arasındaki ilişki prototip zincirine bağlı değildir. Bu yüzden dot-notation kullanılması gerekmez. Aynı durum izole(isolate) scope’lara sahip directive’ler için de geçerlidir. Bu tip directive’lerde de scope’lar arasındaki ilişkiler prototip zincirleriyle temsil edilmez.

<div ng-controller="parentController">
	I'm inside parent controller: <input ng-model="prop">
	
	<ka-test ka-model="prop"></ka-test>
</div>

module.controller('parentController', function($scope) {
	$scope.prop = "initial_value";
});

module.directive('kaTest', function() {
	return {
		restrict: 'E',
		template: `
			<div>
				I'm inside directive: <input ng-model="kaModel">
			</div>
		`,
		scope: {
			'kaModel': '='
		},
		controller: ['$scope', function($scope) {
			console.log($scope);
		}]
	}
});

Çıktıda görüldüğü gibi kaTest isimli directive scope’unun prototipi içerisinde parentController’ın scope’una rastlanmaz.

Ancak directive içerisinde transclusion kullanıldığında bu directive’in scope’u izole veya içerisinde bulunduğu controller ile ortak dahi olsa transclusion ile yerleştirilen elementlere AngularJs tarafından otomatik olarak directive’in içerisinde bulunduğu scope’u genişleten (extend) bir scope oluşturulur. Yani directive’in içerisinde bulunduğu controller’ın scope’u transclusion ile oluşturulan scope’un prototipi içerisinde bulunur.

<div ng-controller="parentController">
	<ka-outer>
		<ka-inner></ka-inner>
	</ka-outer>
</div>

module.controller('parentController', function($scope) {
	console.log($scope);
});

module.directive('kaOuter', function() {
	return {
		restrict: 'E',
		template: `
			<ng-transclude></ng-transclude>
		`,
		transclude: true
	}
});

module.directive('kaInner', function() {
	return {
		restrict: 'E',
		controller: function($scope) {
			console.log($scope);
		}
	}
});

Burada kaInner directive’inin scope’unun prototipine bakıldığında bu scope’un parentController scope’undan kalıtım aldığı görülür. Bu durumda eğer dot-notation kullanılmazsa kaInner directive’i içerisinde yapılan değişikliklerin parentController’a yansımadığı görülür.

Nokta kullandığımız zaman değişiklikler doğru şekilde parentControllera yansır.

KAYNAK

https://docs.angularjs.org/guide/scope

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

https://en.wikipedia.org/wiki/Transclusion

Total
0
Shares
Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Previous Article
sanayileşme ve dijitalleşme

Sanayileşme ve Dijitalleşme