I am trying to get reference to a kendo grid placed within a template used by bootstrap modal. The same grid works when placed directly in the scope of the app controller, but throws undefined error from the modal instance controller.
Can somebody please tell me what I am doing wrong.
var app = angular.module('plunker', ['ui.bootstrap', 'kendo.directives']);
app.controller('MainCtrl', function($scope, $modal) {
$scope.name = 'World';
$scope.mySource = new kendo.data.DataSource({
data: [
{ColumnOne:'One', ColumnTwo:'Two'},
{ColumnOne:'Three', ColumnTwo:'Four'},
{ColumnOne:'Five', ColumnTwo:'Six'}
]
});
$scope.myGridChange = function(){
var selectedRows = $scope.myGrid.select();
console.log($scope.myGrid.dataItem(selectedRows[0]));
};
$scope.items = ['item1', 'item2', 'item3'];
$scope.open = function () {
$modal.open({
templateUrl: 'myGridTemplate.html', //'myTemplate.html',
controller: ModalInstanceCtrl,
resolve: {
items: function () {
return $scope.items;
}
}
});
};
});
var ModalInstanceCtrl = function ($scope, $modalInstance, items) {
$scope.items = items;
$scope.selected = {
item: $scope.items[0]
};
$scope.myTempSource = new kendo.data.DataSource({
data: [
{ColumnOne:'One', ColumnTwo:'Two'},
{ColumnOne:'Three', ColumnTwo:'Four'},
{ColumnOne:'Five', ColumnTwo:'Six'}
]
});
$scope.myTempGridChange = function(){
var selectedRows = $scope.myTempGrid.select();
console.log($scope.myTempGrid.dataItem(selectedRows[0]));
};
$scope.ok = function () {
$modalInstance.close($scope.selected.item);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
I get Cannot call method 'select' of undefined at
var selectedRows = $scope.myTempGrid.select();
Here is a link to my plnkr
http://plnkr.co/edit/GUSX6JR9HRvtdSWclvPl?p=preview
Outside of your modal the controller and grid share the same scope. However when they are inside your modal the grid's scope is nested inside the controllers scope. Not entirely sure why that is but here is why it is a problem.
When you have nested scopes the child scope will prototypically inherit from the parent scope. With prototypical inheritance when you set something directly on a child scope it will not override the parent scope. So when Kendo sets $scope.myTempGrid it only gets set on the child scope and when you try to access it from your controller it is not there.
This can get pretty confusing but fortunately if you always avoid binding things directly to the scope you shouldn't run into this sort of problem. For example if you put the grid in some container object from the parent scope then you won't run into this problem.
for example: http://plnkr.co/edit/0VFJfp?p=preview
Controller:
...
$scope.container = {};
...
HTML
...
<div kendo-grid="container.myTempGrid">
...
A better solution is to always use the 'controller as' syntax: http://plnkr.co/edit/Pmjend?p=preview
Related
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));
});
I am trying to pass data to a ubi modal that is an Angular 1.5 component using resolve. I know this is possible because it shows that resolve is supported for components in the uib modal documentation.
component (Type: string, Example: myComponent) - A string reference to
the component to be rendered that is registered with Angular's
compiler. If using a directive, the directive must have restrict: 'E'
and a template or templateUrl set.
It supports these bindings:
(...)
resolve - An object of the modal resolve values. See UI Router
resolves for details.
All the examples I am finding declare templateurl/controller in the open method. Then the item declared in resolve is injected into to the controller. I am passing an Angular 1.5 component to the modal (not templateurl/controller), and when I try to inject the item from resolve, I get a dreaded "unknow provider" error.
Here is my code. I am trying to pass a url.
Controller of component calling the model
ParentController.$inject = ['$uibModal'];
function ParentController $uibModal) {
var $ctrl = this;
$ctrl.openComponentModal = function(url) {
var modalInstance = $uibModal.open({
animation: false,
component: "ImageModalComponent",
resolve: {
url: function() {
return url;
}
}
});
};
}
Controller in component that is the modal
ImageModalController.$inject = ['url'];
function ImageModalController(url) {
var $ctrl = this;
$ctrl.$onInit = function() {
console.log(url);
};
}
For components, resolve needs to be added to the bindings, then it is available on the isolated scope. In other words, add resolve: '<' when declaring the component.
Modal Component
var template = require('./image_modal.component.html');
var ImageModalController = require('./image_modal.controller');
module.exports = {
template: template,
bindings: {
close: '&',
resolve: ‘<‘
},
controller: ImageModalController
};
Controller of component calling the model
ParentController.$inject = ['$uibModal'];
function ParentController $uibModal){
var $ctrl = this;
$ctrl.openComponentModal = function (urlFromClickEvent) {
var modalInstance = $uibModal.open({
animation: false,
component: "ImageModalComponent",
resolve: {
url: function() {
return url;
}
}
});
};
}
Controller of component that is the modal
ImageModalController.$inject = [];
function ImageModalController() {
var $ctrl = this;
$ctrl.$onInit = function () {
console.log($ctrl.resolve.url);
};
}
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;
});
});
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/
I want to create an AlertFactory with Angular.factory.
I defined an html template like follow
var template = "<h1>{{title}}</h1>";
Title is provided by calling controller and applied as follow
var compiled = $compile(template)(scope);
body.append(compiled);
So, how I can pass isolated scope from controller to factory?
I'm using in controller follow code
AlertFactory.open($scope);
But $scope is global controller scope variable. I just want pass a small scope for factory with just title property.
Thank you.
You can create a new scope manually.
You can create a new scope from $rootScope if you inject it, or just from your controller scope - this shouldn't matter as you'll be making it isolated.
var alertScope = $scope.$new(true);
alertScope.title = 'Hello';
AlertFactory.open(alertScope);
The key here is passing true to $new, which accepts one parameter for isolate, which avoids inheriting scope from the parent.
More information can be found at:
http://docs.angularjs.org/api/ng.$rootScope.Scope#$new
If you only need to interpolate things, use the $interpolate service instead of $compile, and then you won't need a scope:
myApp.factory('myService', function($interpolate) {
var template = "<h1>{{title}}</h1>";
var interpolateFn = $interpolate(template);
return {
open: function(title) {
var html = interpolateFn({ title: title });
console.log(html);
// append the html somewhere
}
}
});
Test controller:
function MyCtrl($scope, myService) {
myService.open('The Title');
}
Fiddle
Followings are the steps:
Add your HTML to the DOM by using var comiledHTML =
angular.element(yourHTML);
Create a new Scope if you want var newScope = $rootScope.$new();
Call $comile(); function which returns link function var linkFun =
$compile(comiledHTML);
Bind the new scope by calling linkFun var finalTemplate =
linkFun(newScope);
Append finalTemplate to your DOM
YourHTMLElemet.append(finalTemplate);
check out my plunkr. I'm programmatically generating a widget directive with a render directive.
https://plnkr.co/edit/5T642U9AiPr6fJthbVpD?p=preview
angular
.module('app', [])
.controller('mainCtrl', $scope => $scope.x = 'test')
.directive('widget', widget)
.directive('render', render)
function widget() {
return {
template: '<div><input ng-model="stuff"/>I say {{stuff}}</div>'
}
}
function render($compile) {
return {
template: '<button ng-click="add()">{{name}}</button><hr/>',
link: linkFn
}
function linkFn(scope, elem, attr) {
scope.name = 'Add Widget';
scope.add = () => {
const newScope = scope.$new(true);
newScope.export = (data) => alert(data);
const templ = '<div>' +
'<widget></widget>' +
'<button ng-click="export(this.stuff)">Export</button>' +
'</div>';
const compiledTempl = $compile(templ)(newScope);
elem.append(compiledTempl);
}
}
}
I assume when you are talking about an isolate scope you are talking about a directive.
Here is an example of how to do it.
http://jsfiddle.net/rgaskill/PYhGb/
var app = angular.module('test',[]);
app.controller('TestCtrl', function ($scope) {
$scope.val = 'World';
});
app.factory('AlertFactory', function () {
return {
doWork: function(scope) {
scope.title = 'Fun';
//scope.title = scope.val; //notice val doesn't exist in this scope
}
};
});
app.controller('DirCtrl', function ($scope, AlertFactory) {
AlertFactory.doWork($scope);
});
app.directive('titleVal',function () {
return {
template: '<h1>Hello {{title}}</h1>',
restrict: 'E',
controller: 'DirCtrl',
scope: {
title: '='
},
link: function() {
}
};
});
Basically, attach a controller to a directive that has defined an isolate scope. The scope injected into the directive controller will be an isolate scope. In the directive controller you can inject your AlertFactory with wich you can pass the isolate scope to.