When is new scope created while using angularUI Bootstrap Modal? - angularjs

I faced the same issue as asked here, And the solution worked too. But could not wrap my head around when are the scopes created. If new scope is created on $modal directive in below code, then why scope in modal.html(view) and ModalInstanceCtrl different ?
In the below piece of code from same question :
$scope.open = function () {
var modalInstance = $modal.open({
templateUrl: 'modal.html',
controller: 'ModalInstanceCtrl'
});
(The main problem was in modal.html the ng-model="text" was not in the same scope object as $scope in its controller : ModalInstanceCtrl.)
What I understand about scopes is that the rootscope is first created by ng-app. Then new scopes are created by directives that create new scopes.
The main discrepancy was that in routing, the structure is similar to above code :
$routeProvider
.when('/', {
redirectTo: '/pages'
})
.when('/food', {
templateUrl: 'food.html',
controller: 'foodController'
})
.when('/play', {
templateUrl: 'play.html',
controller: 'playController'
});
In spite of similarity, here the scope object in templateUrl(view) and controller is same, so why in fisrt code the scope in templateUrl(view) and controller are different ? In referenced question, the comment in answer was that its due to nested controllers, I see that in second piece of code no nesting of controllers is there while in first one there is. But this does not clear that why in first piece of code, the scope in view and controller are different, and when is new scope created.

Reoccuring WTF when writing angular apps. When this happens to me I get that gut feeling that there is a "hidden" scope being used somewhere and I probably forgot the "prefer dot notation in ng-model" rule.
In this case, look at the html source. Your modal.html content is inside a <div class="modal-content" ng-transclude>. This binds to a new transcluded scope that inherits from ModalInstanceCtrl's scope.
When you type into input, a new text property is added to the transcluded scope because assignment of primitives directly on a scope does not consult the prototype chain. ModalInstanceCtrl's scope is not consulted.
Assignment of primitives to objects on the scope does consult the prototype chain, hence the use dot notation rule. That is why your referenced SO article works with input.abc
Here is a great SO article on prototypical inheritance and angular scopes

Related

Get ControllerAs alias inside base controller in AngularJS

We have legacy code written in AngularJS that does not use controllerAs.
Sample Plunker
Our BaseController (Plunker: script.js line 19) has this code to make all class methods available in $scope without writing this.$scope.method = ...
this.$scope[key] = this[key].bind(this)
Now since we are starting to migrate to Controller as vm syntax we do not need this code any more. In Plunker NewCtrl does not even need $scope injected, but because of this line we need to.
Question: How to find out if current controller (NewCtrl) is used with controllerAs syntax or not?
If your app uses ui-router, you could use $state service to check if a controller is declared with as syntax.
$state.get() returns a list of all registered states. Inside each item you can check for controllerAs existance or 'as' inside controller :
controller: 'testCtrl as vm' or
controllerAs: 'vm'
From inside a controller, you could use $state.current as above.

Access directive scope from parent controller

I am using angularjs ui tour https://github.com/benmarch/angular-ui-tour
I have successfully installed the directive,
I now want to initialize the directive in my controller when the page is loaded.
On my routes, I have the following code
when('/start', {
templateUrl: 'start.html',
controller: 'startController'
})
On the template start.html, I have the following code
<div ui-tour class="myClass">
So I want to access the tour var of ui-tour scope from startController, how can I do that?
That depends on how directive is defined. Does it create a new scope or not? Is it's scope proto inherited or isolated one. The simplest solution is to make your directive not create a new scope but use parent's one. If it is the same scope, simply call a function in the directive from parents controller like you would do for a function in that controller.
For more info about why is not a straightforward answer and what are the options when defining directives concerning scope, I find this well explained on the following link:
directives and scopes
Taking a look at the documentation, it looks like it's possible. Via the uiTourService documentation
yourModule.controller('startController',
['$scope', 'uiTourService', function($scope, uiTourService) {
var myTour = uiTourService.getTour();
}]);

Access controller constructor by controller name in angular

I have a controller name as string and I want to get constructor of it.
My current method is using $controller as below:
$scope.myControllerConstructor= $controller( "myControllerName" , {$scope: $scope.$new()}).constructor;
Later on, I use this constructor in the html like this:
<div ng-controller="myControllerConstructor">
The issue is my controller runs two time, one time when I get the constructor (which is wrong) and one time when my html gets compiled (which is correct)
The question is how to get the controller constructor without running it?
Update about use-case: In our application we have many pages with 60% similarities and 40% different activities. I created a directive for those pages and other developers in the team are using my directive to create their page.
The directive accepts a template and a controller (I get them as string) and later on I include the provided template and controller as below:
<div ng-include="myTemplate" ng-controller="myControllerConstructor"></div>
Please take a look at this jsfiddle for a simple example of issue.
The structure of your code looks ok but the issue is $controller( "myControllerName" , {$scope: $scope.$new()}) already instantiate the controller with the given scope for you.
It is true that you can access the controller constructor with .constructor but it is too late as you already created an instance of the controller.
ng-controller does the exact same thing as $controller( "myControllerName" , {$scope: $scope.$new()})
When a Controller is attached to the DOM via the ng-controller
directive, AngularJS will instantiate a new Controller object, using
the specified Controller's constructor function. A new child scope
will be created and made available as an injectable parameter to the
Controller's constructor function as $scope.
To solve this issue you should pass the controller constructor function to pageDirectiveTemplate instead of the controller name.
The working fiddle
There is a different way we can achieve this. In directive, while using isolated scope (like you are doing here in fiddle), you could have a property controller that has value "#" and have another name property having the value of "myController" or whatever your controller name you are passing as.
So, your directive could look something like this:
app.directive('pageDirective', function() {
return {
restrict: "A",
templateUrl: "pageDirectiveTemplate",
scope: {
myTemplate: "#"
},
controller: "#",
name: "myController"
}
});
Notice that, only change in HTML would be to have the directive as an attribute instead of an element. So,
<div page-directive my-template="templateA" my-controller="controllerA">
</div>
<div page-directive my-template="templateA" my-controller="controllerB">
</div>
This would give you exactly what you are looking for. Now you can have same template pointing different controllers and vice-versa.
working fiddle | Note that it works for two different controllers having same template. Also, check the console to see how it logs only once from each controller.

Behaviour bindToController in child scope versus isolated scope

I was playing around with the bindToController option for directives. I stumbled upon a seemingly strange difference between the behaviour using a child scope compared to an isolated scope. When I use an isolated scope, an new scope is created for the directive, but changes to the bound controller attributes are forwarded to the parent scope. Yet when I use a child scope instead, my example breaks. (Using bindToController using child scopes should be allowed according to http://blog.thoughtram.io/angularjs/2015/01/02/exploring-angular-1.3-bindToController.html#improvements-in-14 )
The code:
{
restrict: 'E',
scope: {},
controller: 'FooDirCtrl',
controllerAs: 'vm',
bindToController: {
name: '='
},
template: '<div><input ng-model="vm.name"></div>'
};
Working demo https://jsfiddle.net/tthtznn2/
The version using a child scope:
{
restrict: 'E',
scope: true,
controller: 'FooDirCtrl',
controllerAs: 'vm',
bindToController: {
name: '='
},
template: '<div><input ng-model="vm.name"></div>'
};
Demo: http://jsfiddle.net/ydLd1e00/
The changes to name are forwarded to the child scope, but not to the parent scope. This in contrast to binding to an isolated scope. Why is this?
This is because you are using the same alias for both controllers (and has nothing to do with object vs string value as mentioned in the comments).
As you might know, the controller as alias syntax is merely creating an alias property on scope and sets it to the controller instance. Since you are using vm as the alias in both cases (MainCtrl and FooDirCtrl) you are "shadowing" the MainCtrl alias in the case of the normal child scope.
(In this context, "normal" means "prototypally inheriting from parent scope".)
Thus, when you are trying to evaluate vm.name (vm for MainCtrl) on the new scope to get the "parent value", it is actually evaluating FooDirCtrl.name (which is undefined) and the same happens when you are trying to assign back to the parent scope.
The isolate scope version is not affected, since the scope is not inheriting from it's parent scope.
Updated fiddle
UPDATE:
Taking a closer look at the source code, this might be a bug (because support for bindToController on non-isolate scopes was added "retroactively").
We seem to have been getting away with this bug, because of the prototypal inheritance, but when the names collide we are out of luck.
I have to take a closer look to make sure if it's indeed a bug and if it's "fixable", but for now you can work around it by using different aliases for your controllers (see fiddle above).
That's (part of) why I don't like using vm as my conroller alias (despite it being suggested by popular style-guides) :)
Let's track this in angular.js#13021.

Is there a way to ensure directive scope is resolved before using it?

I'm writing an element-level directive that has a number of attributes on it. These attributes are then put into an isolate scope using the '#' modifier.
The directive is used on a page that populates the attributes with expressions i.e
<my-directive attr1="{{foo.bar}}"></my-directive>
I'm finding that when the directive controller executes, the $scope hasn't resolved the expressions yet. Is there a way to force the scope to resolve before entering the controller?
No, you can't force the scope to be resolved before the controller runs. Use $observe in the controller to asynchronously get the value (and to be notified whenever the value changes -- just like $watch):
controller: function($scope, $attrs) {
$attrs.$observe('attr1', function(newValue) {
....
});
}

Resources