combine two scopes in angular - javascript

I have two scopes and need to combine them. Does anyone know the best way to do this? below are the two scopes. the second one comes from a service
The first scope, retrieves info from database using PHP SLIM RESTful API
Data.get('posts').then(function(data){
$scope.posts = data.data;
});
The second scope retrieves info from database via a service
dataShare.getconfigs().then(function(data){
$scope.configs = data;
});
UPDATE:
When I open the modal to edit I only get the $scope.posts. I am not currently passing through the $scope.configs
$scope.open = function (p,size) {
var modalInstance = $uibModal.open({
templateUrl: 'views/postsEdit.html',
controller: 'postsEditCtrl',
size: size,
resolve: {
item: function () {
return p;
}
}
}); ...

The problem is that $modal.open will create new isolated scope which will be used inside of modal controller and template. This new scope is going to be a direct child of the $rootScope, and thus it will not inherit from your $scope. However, what you want is to inherit from the $scope object from which you open a modal. For this configure modal like this:
$scope.open = function(p, size) {
var modalInstance = $uibModal.open({
templateUrl: 'views/postsEdit.html',
controller: 'postsEditCtrl',
size: size,
scope: $scope, // <-- use $scope as a parent to for modal scope
resolve: {
item: function() {
return p;
}
}
});
};

Related

Binding a Directive Controller's method to its parent $scope

I will explain what exactly I'm trying to do before explaining the issue. I have a Directive which holds a form, and I need to access that form from the parent element (where the Directive is used) when clicking on a submit button to check fi the form is valid.
To do this, I am trying to use $scope.$parent[$attrs.directiveName] = this; and then binding some methods to the the Directive such as this.isValid which will be exposed and executable in the parent.
This works fine when running locally, but when minifying and building my code (Yeoman angular-fullstack) I will get an error for aProvider being unknown which I traced back to a $scopeProvider error in the Controller.
I've had similar issues in the past, and my first thought was that I need to specifically say $inject for $scope so that the name isn't lost. But alas.....no luck.
Is something glaringly obvious that I am doing wrong?
Any help appreciated.
(function() {
'use strict';
angular
.module('myApp')
.directive('formDirective', formDirective);
function formDirective() {
var directive = {
templateUrl: 'path/to/template.html',
restrict: 'EA',
scope: {
user: '='
},
controller: controller
};
return directive;
controller.$inject = ['$scope', '$attrs', 'myService'];
function controller($scope, $attrs, myService) {
$scope.myService = myService;
// Exposes the Directive Controller on the parent Scope with name Directive's name
$scope.$parent[$attrs.directiveName] = this;
this.isValid = function() {
return $scope.myForm.$valid;
};
this.setDirty = function() {
Object.keys($scope.myForm).forEach(function(key) {
if (!key.match(/\$/)) {
$scope.myForm[key].$setDirty();
$scope.myForm[key].$setTouched();
}
});
$scope.myForm.$setDirty();
};
}
}
})();
Change the directive to a component and implement a clear interface.
Parent Container (parent.html):
<form-component some-input="importantInfo" on-update="someFunction(data)">
</form-component>
Parent controller (parent.js):
//...
$scope.importantInfo = {data: 'data...'};
$scope.someFunction = function (data) {
//do stuff with the data
}
//..
form-component.js:
angular.module('app')
.component('formComponent', {
template:'<template-etc>',
controller: Controller,
controllerAs: 'ctrl',
bindings: {
onUpdate: '&',
someInput: '<'
}
});
function Controller() {
var ctrl = this;
ctrl.someFormThing = function (value) {
ctrl.onUpdate({data: value})
}
}
So if an event in your form triggers the function ctrl.someFormThing(data). This can be passed up to the parent by calling ctrl.onUpdate().

Refresh data originally passed into modalInstrance in AngularJS

Using Angular - UI Bootstrap
I am passing data into a modalInstance like the below. In the modal, users have the ability to create new items. On the save button press, a new item is sent to the server. I need the ability to refresh the modal instance with the updated data from $scope.items. Note, the modal doesn't close on the save, so I can't just re-open it. Any help would be greatly appreciated.
$scope.items = ['item1', 'item2', 'item3'];
$scope.open = function (size) {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
resolve: {
items: function () {
return $scope.items;
}
}
});
modalInstance.result.then(function () {
// post updated $scope.items to server
// get fresh $scope.items from server
// notify modal of the updated items
}, function () {
});
};
});
You can try just to create scope for your $modalInstance as a child of current $scope like this:
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
scope: $scope
});
So now after add some item to your items everything will work fine.
For more info you can take a look at description of $modal directive on the angular bootstrap page:
scope - a scope instance to be used for the modal's content (actually the $modal service is going to create a child scope of a provided scope). Defaults to $rootScope
Demo: http://plnkr.co/edit/khzNQ0?p=preview
Hope, it will resolve your problem ;)

Named view in angular ui-router not updating, despite being watched

I have a scoped variable $scope.foo that I am keeping a watch on. It could be updated through a text field in a form.
I have two named views A and B on a page that I am rendering using angular ui-router.
The named view A has the text form field that is being watched for changes in a controller through ng-model="foo". When the value of foo is changed by a user it changes the value of another scoped variable $scope.bar, which is an array, in the controller that is being used in the ng-repeat directive on the named view B. The changes in the $scope.bar is made using $scope.$watch method in the controller.
The issue that I am facing is that the when the foo is changed I could see the changes in bar on the named view A but not on the named view B.
Could somebody help me resolve this issue?
Edit:
Here is the plunker for this issue.
There is a plunker, which should show that your scenario is working.
The most important part of that solution is driven by:
Scope Inheritance by View Hierarchy Only (cite:)
Keep in mind that scope properties only inherit down the state chain if the views of your states are nested. Inheritance of scope properties has nothing to do with the nesting of your states and everything to do with the nesting of your views (templates).
It is entirely possible that you have nested states whose templates populate ui-views at various non-nested locations within your site. In this scenario you cannot expect to access the scope variables of parent state views within the views of children states.
Let me express it again: A scope inheritance goes only via the view nesting.
With that we can create this states definitions:
$stateProvider
.state('root', {
url: '/root',
templateUrl: 'tpl.root.html',
controller: 'RootCtrl', // this root scope will be parent
})
.state('root.entity', {
url: '/entity',
views:{
'A': {
templateUrl: 'tpl.a.html',
controller: 'ACtrl', // scope is inherited from Root
},
'B': {
templateUrl: 'tpl.b.html',
controller: 'ACtrl', // scope is inherited from Root
}
}
})
So the state defintion support nested views - let's profit from that and place the $scope.bar collection into the parent. All views involved will then have access to the same collection:
.controller('RootCtrl', function ($scope, $state) {
$scope.bar = ['first', 'second', 'last'];
})
.controller('ACtrl', function ($scope, $state) {
// *) note below
$scope.foo = $scope.bar[0];
$scope.$watch("foo", function(val){$scope.bar[0] = val; });
})
.controller('BCtrl', function ($scope, $state) {
})
*) note: here we do 1) set from bar 2) $watch and 3) set back to bar to follow the question description... but if the array would contain objects, we can work with them directly... without that overhead, but that's another story...
Check here how that works, and that any changes in view A are also visible in B ... because of inherited reference to the array bar declared in parent $scope.
I created the second answer, to follow also the issue in this plunker, which #skip (OP) passed me as the example fo the issue.
Firstly There is an updated working version
of that plunker, which does what we need. There are the main changes:
The original state def:
.state('home', {
url: '/',
views: {
'': { templateUrl: 'home.html' },
'A#home': {
templateUrl: 'a.html',
controller: 'MainCtrl'
},
'B#home': {
templateUrl: 'b.html',
controller: 'MainCtrl'
}
}
Was replaced with the RootCtrl defintion:
.state('home', {
url: '/',
views: {
'': {
templateUrl: 'home.html',
controller: 'RootCtrl' // here we do use parent scoping
},
'A#home': {
templateUrl: 'a.html',
controller: 'MainCtrl'
},
'B#home': {
templateUrl: 'b.html',
controller: 'MainCtrl'
}
}
And this was one controller:
app.controller('MainCtrl', function($scope) {
var fruits = [{"name": "Apple"}, {"name": "Banana"}, {"name": "Carrot"}];
$scope.bar = $scope.bar || [];
$scope.foo = 2;
$scope.$watch('foo',function(value, oldValue){
$scope.bar = [];
getBar(fruits, value);
});
function getBar(fruits, howManyFruits) {
for(var i=0; i < $scope.foo; i++) {
$scope.bar.push(fruits[i]);
}
}
});
But now we do have two (Parent and child):
app.controller('RootCtrl', function($scope) {
$scope.bar = [];
})
app.controller('MainCtrl', function($scope) {
var fruits = [{"name": "Apple"}, {"name": "Banana"}, {"name": "Carrot"}];
//$scope.bar = $scope.bar || [];
$scope.foo = 2;
$scope.$watch('foo',function(value, oldValue){
$scope.bar.length = 0;
getBar(fruits, value);
});
function getBar(fruits, howManyFruits) {
for(var i=0; i < $scope.foo; i++) {
$scope.bar.push(fruits[i]);
}
}
});
Some important parts to mention
I. The least common denominator
We have to move the shared collection (array bar) into the parent. Why?
we have to move the shared reference to the least common denominator - to the parent scope
see
How do I prevent reload on named view, when state changes? AngularJS UI-Router
II. The Reference to array must be unchanged
we have to keep the reference to the Parent $scope.bar unchanged!. This is essential. How to achieve that? see:
Short way to replace content of an array
where instead of creating new reference, we clear the array, keeping the reference to it
// wrong
$scope.bar = [];
// good
$scope.bar.length = 0;
III. Controller can have multiple instances
Also, the fact that both views A and B had the same controller (same controller name in fact), definitely did not mean, that they were the same instance.
No, they were two different instances... not sharing anything. That is I guess, the most critical confusion. see
angularjs guide - Dependency Injection
Controllers are special in that, unlike services, there can be many instances of them in the application. For example, there would be one instance for every ng-controller directive in the template.
Please, observe that all in the updated example

Testing AngularUI Bootstrap modal instance controller

This is a somewhat of a follow-on question to this one: Mocking $modal in AngularJS unit tests
The referenced SO is an excellent question with very useful answer. The question I am left with after this however is this: how do I unit test the modal instance controller? In the referenced SO, the invoking controller is tested, but the modal instance controller is mocked. Arguably the latter should also be tested, but this has proven to be very tricky. Here's why:
I'll copy the same example from the referenced SO here:
.controller('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');
};
});
So my first thought was that I would just instantiate the controller directly in a test, just like any other controller under test:
beforeEach(inject(function($rootScope) {
scope = $rootScope.$new();
ctrl = $controller('ModalInstanceCtrl', {$scope: scope});
});
This does not work because in this context, angular does not have a provider to inject $modalInstance, since that is supplied by the UI modal.
Next, I turn to plan B: use $modal.open to instantiate the controller. This will run as expected:
beforeEach(inject(function($rootScope, $modal) {
scope = $rootScope.$new();
modalInstance = $modal.open({
template: '<html></html>',
controller: 'ModalInstanceCtrl',
scope: scope
});
});
(Note, template can't be an empty string or it will fail cryptically.)
The problem now is that I have no visibility into the scope, which is the customary way to unit test resource gathering, etc. In my real code, the controller calls a resource service to populate a list of choices; my attempt to test this sets an expectGet to satisfy the service my controller is using, and I want to validate that the controller is putting the result in its scope. But the modal is creating a new scope for the modal instance controller (using the scope I pass in as a prototype), and I can't figure out how I can get a hole of that scope. The modalInstance object does not have a window into the controller.
Any suggestions on the "right" way to test this?
(N.B.: the behavior of creating a derivative scope for the modal instance controller is not unexpected – it is documented behavior. My question of how to test it is still valid regardless.)
I test the controllers used in modal dialogs by instantiating the controller directly (the same way you initially thought to do it above).
Since there there's no mocked version of $modalInstance, I simply create a mock object and pass that into the controller.
var modalInstance = { close: function() {}, dismiss: function() {} };
var items = []; // whatever...
beforeEach(inject(function($rootScope) {
scope = $rootScope.$new();
ctrl = $controller('ModalInstanceCtrl', {
$scope: scope,
$modalInstance: modalInstance,
items: items
});
}));
Now the dependencies for the controller are satisfied and you can test this controller like any other controller.
For example, I can do spyOn(modalInstance, 'close') and then assert that my controller is closing the dialog at the appropriate time.
Alternatively, if you're using jasmine, you can mock the $uibModalInstance using the createSpy method:
beforeEach(inject(function ($controller, $rootScope) {
$scope = $rootScope.$new();
$uibModalInstance = jasmine.createSpyObj('$uibModalInstance', ['close', 'dismiss']);
ModalCtrl = $controller('ModalCtrl', {
$scope: $scope,
$uibModalInstance: $uibModalInstance,
});
}));
And test it without having to call spyOn on each method, let's say you have 2 scope methods, cancel() and confirm():
it('should let the user dismiss the modal', function () {
expect($scope.cancel).toBeDefined();
$scope.cancel();
expect($uibModalInstance.dismiss).toHaveBeenCalled();
});
it('should let the user confirm the modal', function () {
expect($scope.confirm).toBeDefined();
$scope.confirm();
expect($uibModalInstance.close).toHaveBeenCalled();
});
The same problem is with $uidModalInstance and you can solve it in similar way:
var uidModalInstance = { close: function() {}, dismiss: function() {} };
$ctrl = $controller('ModalInstanceCtrl', {
$scope: $scope,
$uibModalInstance: uidModalInstance
});
or as said #yvesmancera you can use jasmine.createSpy method instead, like:
var uidModalInstance = jasmine.createSpyObj('$uibModalInstance', ['close', 'dismiss']);
$ctrl = $controller('ModalInstanceCtrl', {
$scope: $scope,
$uibModalInstance: uidModalInstance
});
Follow below given steps:
Define stub for ModalInstance like give below
uibModalInstanceStub = {
close: sinon.stub(),
dismiss: sinon.stub()
};
Pass the modal instance stub while creating controller
function createController() {
return $controller(
ppcConfirmGapModalComponentFullName,
{
$scope: scopeStub,
$uibModalInstance: uibModalInstanceStub
});
}
});
Stub methods close(), dismiss() will get called as part of the tests
it('confirm modal - verify confirm action, on ok() call calls modalInstance close() function', function() {
action = 'Ok';
scopeStub.item = testItem;
createController();
scopeStub.ok();
});

ui-router: open modal and pass parent scope parameters to it

I am using this FAQ entry to open a modal dialog in a child state of a certain state: https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-open-a-dialogmodal-at-a-certain-state
My code is below. When I open a modal dialog, I need to get access to the properties of the parent state's scope. Is this possible?
plnkr: http://plnkr.co/edit/knY87n
.state('edit', {
url: '/{id:[0-9a-f]+}',
views: {
'#': {
templateUrl: 'views/edit.html',
controller: 'editContr'
}
}
})
.state('edit.item', {
url: "/item/new",
onEnter: function($stateParams, $state, $modal) {
$modal.open({
controller: 'itemEditContr',
templateUrl: 'views/edit-item.html',
}).result.then(function (item) {
//
// here I need to insert item into the items
// seen by my parent state. how?
//
$state.go('^');
}, function () {
$state.go('^');
});
}
});
function editContr($scope) {
$scope.items = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
}
function itemEditContr($scope, $modalInstance) {
$scope.submit = function () {
$modalInstance.close($scope.item);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
$scope.item = {name: 'test'};
}
Are you planning to update params set by the parent? You should be able to get the parent state using
$state.$current.parent
For e.g. the name of the parent state will be $state.$current.parent.name
EDIT: Updating since the OP wanted to access the Scope and not the parent state.
You can emit an event from the child and then capture it in the parent.
Untested code:
In Parent:
$scope.$on('ADD_ITEM', function(evt, msg) {
$scope.items.add(msg);
}
In the child state:
$scope.$emit('ADD_ITEM', item);
long story short, yes it is. Reading the angularjs developer guide for scopes, is actually one of their more helpful and very well documented pieces: http://docs.angularjs.org/guide/scope
Aside from that, scope in angular is no different than scope with any javascript object.
You've got one or two things that you're not doing correctly. For one, passing item in your onEnter function won't help unless you're grabbing something from the url as an identifier, or resloving some data that you can inject into the states controller. You're trying to do the latter, but you aren't resolving anything, so you are getting undefined on item.
One trick you can use is to set a truthy value in your your parent, and access it.
//in parent ctrl
$scope.newItem = function(itemname){
return {name:itemname}
}
$scope.save = function(item){
$scope.items.push(item);
}
Then when you open your modal call $scope.getItem() within the controller instead of injecting item into the controller directly
function itemEditContr($scope, $modalInstance) {
$scope.submit = function () {
$modalInstance.close();
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
$scope.item = $scope.newItem('test') //this will look for newItem funciton in the local scope, fail to find it, and walk up the heirarchy until it has found newItem() in the parent
//now a save function, also defined on the parent scope
$scope.save($scope.item);
}
Accessing the parent scope is nothing special, just make sure to get a value, and not overwrite it. so you can access $scope.items from the child controller by assigning it to a variable, or you can push it new values, but never set it, or you will create a new local items object on the child scope instead.
I struggled with this too and found an alternative solution. Instead of using the onEnter function you can use a controller with a very simple view that calls a function to open the modal. I used a
config(['$stateProvider', function($stateProvider) {
// Now set up the states
$stateProvider
.state('exmple.modal', {
url: "/example/modal/",
controller: "ExampleController",
template: '<div ng-init="openModal()"></div>'
});
}]);
In the example controller you an put the openModal() function to open the modal where you have a scope.

Categories

Resources