AngularJS: отвязывание обработчиков и рассуждение об опасностях использования вложенных контроллеров

В нашем приложении (чего уж тут скрывать — http://m.mamba.ru, тач-версия) всё построено на встроенных в angular маршрутах.
При этом, как известно, шаблон страницы отрисовывается внутри тега ng-view.
Появилось несколько страниц, на которых внутри контента (это важно) нужно показывать баннер.

Сделал я это так:
внутри списка пользователей в html:

<div ng-switch="$index">
<div class="b-content__iframe" ng-controller="BannerMiddleCtrl" ng-switch-when="4">
<inline-ad frame-src-base="http://mamba.ru/be.phtml" position="91" show-ad="showAd"></inline-ad>
</div>
</div>

в контроллерах:

controller('BannerMiddleCtrl', ['$scope', '$rootScope', function BannerMiddleCtrl($scope, $rootScope) {
	$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
		setCanShowAd();
	});

	$rootScope.$watch('showPopupTip + controllerDataLoaded + controllerDataLoadError + routeError + hidePageForPopup', function () {
		setCanShowAd();
	});

	function setCanShowAd()
	{
		$scope.showAd = canShowAd();
		$rootScope.middleAdShown = $scope.showAd;
	}

	function canShowAd()
	{
		if (<сложное бизнес-условие>) {
			return true;
		}
		return false;
	}
}]).

И заметил я, что после показа страницы, на которой используется этот контроллер, функция setCanShowAd() продолжает вызываться и на других страницах. И ещё забавнее, что вызываться она будет несколько раз, если несколько раз зайти на эту страницу.
Что, в общем-то, логично, хотя и печально. Получается, что AngularJS сам не снимает обработчики событий для таких контроллеров (возможно, и для обычных контроллеров тоже), что, наверное можно объяснить и идеологическими соображениями и сложностью реализации.

Поэтому родилась следующая идея (не без помощи http://stackoverflow.com/questions/14957614/angular-js-clear-watch).

angular.module('CommonControllers', []).
value('BannerMiddleCtrlWatchers', {}).
controller('BannerMiddleCtrl', ['$scope', '$rootScope', function BannerMiddleCtrl($scope, $rootScope) {
	BannerMiddleCtrlWatchers.rsOnUnbind = $rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
		setCanShowAd();
	});

	BannerMiddleCtrlWatchers.rsWatchUnbind = $rootScope.$watch('showPopupTip + controllerDataLoaded + controllerDataLoadError + routeError + hidePageForPopup', function () {
		setCanShowAd();
	});

	function setCanShowAd()
	{
		$scope.showAd = canShowAd();
		$rootScope.middleAdShown = $scope.showAd;
	}

	function canShowAd()
	{
		if (<сложное бизнес-условие>) {
			return true;
		}
		return false;
	}
}]).

app.js

app.run(['$rootScope', 'BannerMiddleCtrlWatchers', function($rootScope, BannerMiddleCtrlWatchers) {
	$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
		BannerMiddleCtrlWatchers.rsOnUnbind && BannerMiddleCtrlWatchers.rsOnUnbind();
		BannerMiddleCtrlWatchers.rsWatchUnbind && BannerMiddleCtrlWatchers.rsWatchUnbind();
	});

Идея в том, чтобы отвязывать обработчики событий на rootScope.
О том, как сделать правильно — в следующем выпуске :)

LEAVE A COMMENT