ionic, pass scope variable to popover scope - angularjs

I would like to pass a variable from the view scope ($scope.sort) into the popover scope.
Once set in the popover, the variable 'goes out' of it with no issue though, with code below in$scope.closeSortPopover`.
I thought the popover scope was the same as the scope of the controller it comes from.
But it seems it's not the case.
Where is the popover scope ?
If I understand correctly from here the popover scope is a child of the controller scope. So how can I access it... ?
FYI my popover is a list of <ion-radio>s offering different sorting opions for the big list in my main view.
html:
<button ng-click="openSortPopover($event, sort)">
controllers.js
$ionicPopover.fromTemplateUrl('templates/computeSortPopover.html', {
scope: $scope
}).then(function(popover) {
$scope.sortPopover = popover;
});
$scope.openSortPopover = function($event, sort) {
$scope.sortPopover.show($event);
};
$scope.closeSortPopover = function(sort) {
$scope.sortPopover.hide();
$scope.sort = sort;
};

In the controller js, you are assigning the scope of the controller to the scope of the popover. Thus all the methods in the controller scope are accessible via the popover view. In other words, both the controller view and popover view share the same scope.
In fact you might think this a violation of mvc as one controller cannot own two views, but with angular, you actually can't otherwise.
To create isolated scope, your only option is directives.

I discovered that due to javascript inheritence, the scope variable must be contained into an object to be passed to inherited scopes.
So instead of this:
$scope.sort = ....
I declared this in my controller:
$scope.data={};
$scope.data.sort = ....
and used in my html (both in the initial view and in the popover templates):
data.sort
Documented here apparently: https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance

Related

angular 1.5: watch doesn't call when i toggle the ui

I my template i have:
<md-checkbox ng-model="$ctrl.isAdOps" aria-label="isAdOps">
isAdOps {{ $ctrl.isAdOps }}
</md-checkbox>
In my component:
(function (app) {
app.component('homeComponent', {
templateUrl: 'partials/home-partial.html',
bindings: {
isAdOps: '='
},
controller: ['$scope', '$state', function ($scope, $state) {
var self = this;
$scope.$watch(
self.isAdOps,
function (isAdOps) {
$scope.$broadcast('isAdOpsToggled', isAdOps);
}
);
}]
});
})(myApp);
why doesn't the watch called when i toggle the md-checkbox?
Angular $watch first parameter should of type string/function
string: Evaluated as expression
function(scope): called with current scope as a parameter.
replace self.isAdOps with 'isAdOps' in $watch or alternativly use a function syntax with $scope.
You are better off using the ng-change directive:
<md-checkbox ng-model="$ctrl.isAdOps"
ng-change="$ctrl.isAdOpsChanged($ctrl.isAdOps)"
aria-label="isAdOps>
isAdOps {{ $ctrl.isAdOps }}
</md-checkbox>
It avoids using $scope and adding a watcher.
For more information, see Writing Components without Watchers.
See also, AngularJS Developer Guide - Component-based application architecture.
Components should follow a few simple conventions:
Inputs should be using < and # bindings. The < symbol denotes one-way bindings which are available since 1.5. The difference to = is that the bound properties in the component scope are not watched, which means if you assign a new value to the property in the component scope, it will not update the parent scope. Note however, that both parent and component scope reference the same object, so if you are changing object properties or array elements in the component, the parent will still reflect that change. The general rule should therefore be to never change an object or array property in the component scope. # bindings can be used when the input is a string, especially when the value of the binding doesn't change.
Outputs are realized with & bindings, which function as callbacks to component events.
This will make the migration to Angular 2+ easier.

Two-way binding within nested directives and ui-select

Problem I have been working on:
The original problem (which only addressed one-way binding) can be found here:
Unable to pass/update ngModel from controller to directive
I have managed to get the one-way binding from inside the directive out to the view working. But if I want push in a selection when the page loads, for example, I cannot get the two-way binding to function. I can see that the ng-model inside the first directive is getting the data, but I tried various scope settings with the child directive and either it breaks it or it does the same thing as before - nothing.
I have a basic $watch function set up, so that when I push a simple object into the binding that is attached to ng-model in the view, the watcher assigns the $viewValue to the directive's scope object. It does this, and the directive responds only by having any existing selection wiped out, even though I can clearly see the objects inside ng-model binding assigned to ui-select.
Here is the watch function:
scope.$watch(function() {
return ngModel.$viewValue;
}, function(newVal) {
console.log(newVal, scope.options);
scope.options.selected = newVal;
});
I use this function to update the view whenever we interact with the ui-select (which works fine):
scope.changer = function() {
ngModel.$setViewValue(scope.options.selected);
console.log(scope.options.selected);
};
A Plunker for tinkering
So the expected behavior is:
selections from the ui-select should be displayed in the select, and also get passed to the view
by clicking the 'Change' button, data should be displayed in the view, and get passed to the ui-select
The situation was that I had a directive with ui-select inside it. The way I was able to get data from the main controller scope through the directive scope and down to ui-select's scope was by putting a watch function on ngModel.$viewValue in the link function of the directive.
I then assigned that new value, whenever there was a change, to an object on that directive's scope, to hold it for me. Then I set a watch function in the link function of ui-select to watch scope.$parent.myVal so that if anything was ever pushed to that variable from the main controller, ui-select would see it. When that happened, I would assign that new value to $select.selected which is the container for selected items in the ui-select directive.
It should be noted that ui-select uses scope: true in it's directive, and thus becomes a child of whatever scope it is instantiated in, which allows you to access the scope of it's parent with $parent.
Watch function for the directive:
scope.$watch(function() {
return scope.$parent.myVar;
}, function(newVal) {
$select.selected = newVal;
})
Watch function to be added to ui-select:
scope.$watch(function() {
return ngModel.$viewValue;
}, function(newVal) {
scope.myVar = newVal;
})
Plunker Demo

where is the scope within a directive div

Just something I'm trying to figure out at the moment,
If I have a controller, AController, with a variable
scope.test = '123';
and a directive,
.directive('aDirective', function() {
return {
scope: {
test: '#aTest'
}
}
});
with HTML
<div ng-controller="AController">
<div a-directive a-test="abc{{test}}">
<h4>{{test}}</h4>
</div>
</div>
Where is the scope of test within the h4 tags ie nested inside the directive div. I expected it to be the isolate scope and the h4 to contain 'abc123' but instead it seems to be getting the scope from the controller. Is this because the directive test var is specified with the readonly # tag? I've created a similar example here: http://jsfiddle.net/f46df2gn/
Any thoughts appreciated
C
Indeed the content of a-directive does not know anything about the directive's isolate scope.
If you want to access the isolate scope, you should use a template (via template or templateUrl).
The isolate scope is also available in the pre- and post-linking functions and the directive controller (if any).
E.g.:
.directive('aDirective', function () {
return {
scope: {
test: '#aTest'
},
template: '<h4>{{test}}</h4>'
};
});
<div ng-controller="AController">
<div a-directive a-test="abc{{test}}"></div>
</div>
See, also, this modified demo.
If you want to retain the content of an element (as specified in the view - which will be bound to the parent, non-isolate scope) as well as let the directive add its own content (bound to its isolate scope), you should look into ngTransclude and transclusion in general.
Beware, though, that it's a somewhat advanced subject, so make sure you understand the basics of directives first.
(In fact, using transclusion it is possible to bind the content defined in the view to the isolate scope, but it's a fairly advanced transclusion usecase.)

can a custom directive have both an isolated scope AND a "controller: ctrlName" field?

If a directive has an isolated scope, will the controller field be taken into account ?
If yes, how does exactly the directive access this controller ?
Yes, the isolated scope has nothing to do with the controller. Your problem is more of how to work with the controller (it use doesn't change in regard of the scope type).
A controller is useful when you want a directive to be required. If you have a directive called menu and another directive called menu-item and you want for example register all your menu-item in the menu directive, you create a controller.
When your menu-item does a require: 'menu' what it requires is the menu controller, not the directive itself.
Then you can have a directive like:
angular.module('app').directive('menu', function() {
return {
scope: {},
controller: function($scope) {
$scope.foo = "foo";
this.register = function(scope) {
// register child here
};
}
});
$scope.foo can be accessed by menu template, but this.register can't.
When you require menu in your menu-item you can't access $scope.foo but you can access this.register.
TL;DR; Scope type and having a controller are not related.
Example: http://plnkr.co/edit/fzbSnhxN9rp4Ct5kvB9i?p=preview

Modify $rootscope property from different controllers

In my rootscope I have a visible property which controls the visibility of a div
app.run(function ($rootScope) {
$rootScope.visible = false;
});
Example HTML:
<section ng-controller='oneCtrl'>
<button ng-click='toggle()'>toggle</button>
<div ng-show='visible'>
<button ng-click='toggle()'>×</button>
</div>
</section>
Controller:
var oneCtrl = function($scope){
$scope.toggle = function () {
$scope.visible = !$scope.visible;
};
}
The above section works fine, the element is shown or hide without problems. Now in the same page in a different section I try to change the visible variable to show the div but it doesn't work.
<section ng-controller='otherCtrl'>
<button ng-click='showDiv()'>show</button>
</section>
Controller:
var otherCtrl = function($scope){
$scope.showDiv = function () {
$scope.visible = true;
};
}
In AngularJS, $scopes prototypically inherit from their parent scope, all the way up to $rootScope. In JavaScript, primitive types are overwritten when a child changes them. So when you set $scope.visible in one of your controllers, the property on $rootScope was never touched, but rather a new visible property was added to the current scope.
In AngularJS, model values on the scope should always "have a dot", meaning be objects instead of primitives.
However, you can also solve your case by injecting $rootScope:
var otherCtrl = function($scope, $rootScope){
$scope.showDiv = function () {
$rootScope.visible = true;
};
}
How familiar are you with the concept of $scope? It looks to me based on your code that you're maintaining two separate $scope variables called "visible" in two different scopes. Do each of your controllers have their own scopes? This is often the case, in which case you're actually editing different variables both named "visible" when you do a $scope.visible = true in different controllers.
If the visible is truly in the rootscope you can do $rootScope.visible instead of $scope.visible, but this is kind of messy.
One option is to have that "otherCtrl" code section in a directive (you should probably be doing this anyway), and then two-way-bind the directive scope to the parent scope, which you can read up on here. That way both the directive and the page controller are using the same scope object.
In order to better debug your $scope, try the Chrome plugin for Angular, called Batarang. This let's you actually traverse ALL of your scopes and see the Model laid out for you, rather than just hoping you're looking in the right place.

Resources