Using ‘this’ as scope when creating $ionicModal - angularjs

I seem to be having an issue creating an $ionicModal when trying to specify my scope as this instead of $scope.
Since I'm binding everything in my controller via an instance name, I'm not using $scope inside of the controller.
So, I initiate the modal as instructed here in Ionic Framework doc
and switched $scope with this
$ionicModal.fromTemplateUrl('my-modal.html', {
scope: this,
animation: 'slide-in-up'
}).then(function(modal) {
this.modal = modal;
});
When the app runs, I get the following error:
undefined is not a function
and it references the following code in ionic.bundle.js:
var createModal = function(templateString, options) {
// Create a new scope for the modal
var scope = options.scope && options.scope.$new() || $rootScope.$new(true);
I even tried assigning another variable to represent this and running it that way, but the same error prevails!
If I'm not using $scope in my controller, what would be the best way to load a modal while maintaining the usage of this? Is it just not possible or am I missing something?
EDIT-
As requested, adding more info to the original,
Template:
<div id="wrapper" ng-controller="MainCtrl as ctrl">
<button ng-click="ctrl.demo()">Demo Button</button>
</div>
Controller:
angular.module('MyDemo', ['ionic'])
.controller('MainCtrl', function ($ionicModal) {
var _this = this;
this.demo = function () {
//do demo related stuff here
}
$ionicModal.fromTemplateUrl('my-modal.html', {
scope: _this,
animation: 'slide-in-up'
}).then(function(modal) {
_this.modal = modal;
});
});
So, basically, I'm using the 1st declaration style found here:
https://docs.angularjs.org/api/ng/directive/ngController
EDIT: Changed this to _this inside of $ionicModal
As requested, here's a plunker with the code above:
http://plnkr.co/edit/4GbulCDgoj4iZtmAg6v3?p=info

Because of how AngularJs currently sets up the controller when using the "controller as" syntax, you only have whatever functions and properties that you yourself define in the controller function. In order to access the $new() function that AngularJs provides to create child scopes, you need to provide an AngularJs $scope object -- which you can still get by getting it injected into your constructor function, even when using the "controller as" syntax.
angular.module('MyDemo', ['ionic'])
.controller('MainCtrl', function ($scope, $ionicModal) {
var _this = this;
this.demo = function () {
//do demo related stuff here
}
$ionicModal.fromTemplateUrl('my-modal.html', {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
_this.modal = modal;
});
});

Related

When using $compile on component, why is the scope passed through $parent?

I'm trying to dynamically compile an Angular component using $compile, but the scope isn't passed to the components scope, but to the $parent scope instead.
Here is a simple component that binds to a myTitle-attribute and presents it:
app.component('myComponent', {
bindings: {
myTitle: '<'
},
template: `
<div>
<div>Doesn't work: {{ $ctrl.myTitle}}</div>
<div>Works: {{ $parent.$ctrl.myTitle}}</div>
</div>`
});
Then in the controller (or directive, etc.) I compile it using $compile:
app.controller('MainCtrl', function($scope, $element, $compile) {
var template = '<my-component></my-component>';
var bindings = {
myTitle: 'My Title'
}
var scope = angular.extend($scope.$new(true), {
$ctrl: bindings
});
var newElement = $compile(template)(scope);
$element.append(newElement);
});
When running this, it yield the result:
Doesn't work:
Works: My Title
Here's a plunker showing it in action
The question
How come the scope I create for the dynamically created component, is passed as a parent scope of the component?
Any pointer on why angular behaves like this and perhaps how to avoid it is much welcome.
As I see, you need to pass binding here var template = '<my-component></my-component>';
var template = '<my-component my-title="$ctrl.myTitle"></my-component>';
Full component may be like this:
app.controller('MainCtrl', function($scope, $element, $compile) {
var template = '<my-component my-title="$ctrl.myTitle"></my-component>';
$scope.$ctrl = {myTitle: 'My Title'};
$element.append($compile(template)($scope));
});

Angular - changes to directive controller's scope aren't reflected in view

Changes to my scope variable foo are getting updated in the html. When that value is change inside the scope of a directive's controller, it isn't updating in the html.
What do I need to do to make it update?
I have a simple example:
app.js
var app = angular.module('app', []);
app.controller('ctrl', function($scope) {
$scope.foo = 99;
$scope.changeValue = function() {
$scope.foo = $scope.foo + 1;
}
});
app.directive('d1', function(){
return {
restrict: 'E',
scope: {
theFoo: '='
},
templateUrl: 'd1.html',
controller: 'd1Ctrl',
}
});
app.controller('d1Ctrl', function($scope) {
$scope.test = $scope.theFoo;
});
d1.html
<div>
<p>The value of foo is '{{theFoo}}'.</p>
<p>The value of test is '{{test}}'.</p>
</div>
inside index.html
<d1 the-foo='foo'>
</d1>
<button ng-click='changeValue()'>change value</button>
So in summary, {{theFoo}} is updating, but {{test}} isn't. Why?
The reason is that $scope.foo value is a primitive.
In the directive controller you only assign $scope.test once when controller initializes. Primitives have no inheritance the way objects do so there is nothing that would change $scope.test after that initial assignment
If you used an object instead to pass in ... inheritance would be in effect and you would see changes...otherwise you would need to watch $scope.theFoo and do updates to $scope.test yourself
The code you have in your controller only initializes to that value if it is indeed set at the time the controller is linked. Any subsequent changes are not going to work.
If you want to bind any subsequent changes, then you need to set a $watch statement either in your controller or a link function.
$scope.$watch( 'theFoo', function(val){ $scope.test = val; })
updated plunker - http://plnkr.co/edit/eWoPutIJrwxZj9XJu6QG?p=preview
here you have isolated the scope of the directive, so test is not visible to the d1.html, if you need to change test along with the theFoo you must first make it visible to the directive by
app.directive('d1', function(){
return {
restrict: 'E',
scope: {
theFoo: '=',
test : '=' //getting test as well
},
templateUrl: 'd1.html',
controller: 'd1Ctrl',
}
});
and in index.html you should pass the value to the test by
<d1 the-foo='foo' test='foo'></d1>
in the above code your controller is not much of a use , code will work fine even without this part controller: 'd1Ctrl'.
with this example you dont have to use $watch.

Unable to call Angular directive method

I've got an Angular view thusly:
<div ng-include="'components/navbar/navbar.html'" class="ui centered grid" id="navbar" onload="setDropdown()"></div>
<div class="sixteen wide centered column full-height ui grid" style="margin-top:160px">
<!-- other stuff -->
<import-elements></import-elements>
</div>
This is controlled by UI-Router, which is assigning the controller, just FYI.
The controller for this view looks like this:
angular.module('pcfApp')
.controller('ImportElementsCtrl', function($scope, $http, $location, $stateParams, $timeout, Framework, OfficialFramework) {
$scope.loadOfficialFrameworks();
// other stuff here
});
The <import-elements> directive, looks like this:
angular.module('pcfApp').directive('importElements', function($state, $stateParams, $timeout, $window, Framework, OfficialFramework) {
var link = function(scope, el, attrs) {
scope.loadOfficialFrameworks = function() {
OfficialFramework.query(function(data) {
scope.officialFrameworks = data;
$(".ui.dropdown").dropdown({
onChange: function(value, text, $item) {
loadSections($item.attr("data-id"));
}
});
window.setTimeout(function() {
$(".ui.dropdown").dropdown('set selected', data[0]._id);
}, 0);
});
}
return {
link: link,
replace: true,
templateUrl: "app/importElements/components/import_elements_component.html"
}
});
I was under the impression that I'd be able to call the directive's loadOfficialFrameworks() method from my controller in this way (since I'm not specifying isolate scope), but I'm getting a method undefined error on the controller. What am I missing here?
The problem is that your controller function runs before your link function runs, so loadOfficialFrameworks is not available yet when you try to call it.
Try this:
angular.module('pcfApp')
.controller('ImportElementsCtrl', function($scope, $http, $location, $stateParams, $timeout, Framework, OfficialFramework) {
//this will fail because loadOfficialFrameworks doesn't exist yet.
//$scope.loadOfficialFrameworks();
//wait until the directive's link function adds loadOfficialFrameworks to $scope
var disconnectWatch = $scope.$watch('loadOfficialFrameworks', function (loadOfficialFrameworks) {
if (loadOfficialFrameworks !== undefined) {
disconnectWatch();
//execute the function now that we know it has finally been added to scope
$scope.loadOfficialFrameworks();
}
});
});
Here's a fiddle with this example in action: http://jsfiddle.net/81bcofgy/
The directive scope and controller scope are two differents object
you should use in CTRL
$scope.$broadcast('loadOfficialFrameworks_event');
//And in the directive
scope.$on('loadOfficialFrameworks_event', function(){
scope.loadOfficialFrameworks();
})

AngularJS can't pass in different function when directive coupled with controller

I have a directive which I want to tightly couple with a controller as a component. I assumed I was following best practice by explicitly passing ion my functions even though I was declaring the controller to use. Here is an example:
app.js
var app = angular.module('plunker', [])
app
.controller('myCtrl', function($scope) {
$scope.output = '';
$scope.foo = function () {
$scope.output = 'foo';
}
$scope.bar = function () {
$scope.output = 'bar';
}
})
.directive('myDirective', function() {
return {
scope: {
output: '=',
foo: '&',
},
templateUrl: 'template.html',
replace: true,
controller: 'myCtrl',
};
})
template.html
<div>
<button ng-click="foo()">Click Foo</button>
<p>You clicked: <span style="color:red">{{output}}</span></p>
</div>
index.html
<body>
<my-directive
output="output"
foo="bar()"> <!-- pass in the *bar* function instead of the *foo* function -->
</my-directive>
</body>
Plunkr: http://plnkr.co/edit/Y4lhxuXbK9YbjAklR7v1?p=preview
Here, even though I'm passing in the bar() function, the output is 'foo' when the button is clicked. If I uncouple the controller by commenting out controller: 'myCtrl' in the directive, the output becomes 'bar'.
I thought I could declare the controller but still be free to pass in which functions I desire to the directive. It also seems that explicitly passing these functions in is a little redundant if the directive just looks up to the controller to find it (I can pass nothing into the directive and it still works).
This is especially problematic when testing as I would like to pass in my own stub functions to the directive, which at the moment I cannot do.
Is there some way to achieve what I want or am I doing something fundamentally wrong?
EDIT I meant to not have the controller declared in the HTML.
Remove the controller property on the directive:
.directive('myDirective', function() {
return {
scope: {
output: '=',
foo: '&',
},
templateUrl: 'template.html',
replace: true,
// controller: 'myCtrl',
};
})
You're wiring up the same controller to the directive as the parent, which is overwriting all the properties you're trying to pass in via isolate scope. The controller is wired up twice, once on the parent scope and then again on the directive. Removing this will allow you to pass in the function bar() and it will not be overwritten.
Here's the Plunker Demonstration
When running inside a directive, the $scope is initialized with output and foo variables before the controller constructor is called. Your controller is essentially overwriting these properties.
A simple check in your controller
if(!$scope.foo)
{
$scope.foo = function () {
$scope.output = 'foo';
}
}
Would work.
PS. I'm assuming your example is a simplification of your problem. If it's not, then the other answer's advice to simply remove the controller from the directive is the best approach.

Angular UI $modal and scope confusion when passing data to and from the modal dialog

I am just getting into Angular and am finding the $modal dialog confusing. I cannot get the modal's close() method to work, though I'm following the documentation:
$scope.ok = function () {
$modalInstance.close(["cat","dog"]);
};
In the code snippet below, from the online docs, inside the ModalInstanceCtrl function, does $scope refer to the scope of ModalDemoCtrl or the scope of ModalInstanceCtrl? I am also confused by the appearance of the items parameter in the ModalInstanceControl signature, since items also appears in the resolve section of the $modal config.
<snip>
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: ModalInstanceCtrl,
size: size,
resolve: {
items: function () {
return $scope.items;
}
}
});
var ModalInstanceCtrl = function ($scope, $modalInstance, items) {
$scope.items = items;
$scope.selected = {
item: $scope.items[0]
};
$scope.ok = function () {
$modalInstance.close($scope.selected.item);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
We can achieve through Isolated Scope: pass some values from the parent scope to the directives
There’re 3 types of prefixes AngularJS provides
"#" ( Text binding / one-way binding )
"=" ( Direct model binding / two-way binding )
"&" ( Behaviour binding / Method binding )
All these prefixes receives data from the attributes of the directive element.
class="directive"
name="{{name}}"
color="color"
When the directive encounters a prefix in the scope property, it will look for an attribute ( with same property name ) on directive’s html element
scope : {
name: "#"
}
I've followed this linkhttp://jsfiddle.net/shidhincr/pJLT8/10/light/

Resources