angular accessing modal parent controller - angularjs

I'm using ui-router with $modal and my set up is like this in my routing page:
.state('resourcesControl.resource.dataStuff', {
url: "/:resourceId/dataStuff",
onEnter: ['$stateParams', '$state', '$modal', '$timeout', 'resourceService', function($stateParams, $state, $modal, $timeout, resourceService){
var modalInst = $modal.open({
templateUrl: "templates/dataStuff.html",
windowClass: 'data-modal',
controller: 'dataStuffCtrl'
});
modalInst.result.then(function(){
}, function(){
$state.go('resourcesControl.resource');
});
}]
})
My understanding with UI router is that all the child states have access to their parents controller and scope variables. In theory my dataStuffCtrl should have access to resource and resourcesControl controllers and their scopes.
However, when I wrap curly brackets around a parent scope item in the dataStuff view, nothing renders. Is there a way around this? I remember seeing other people posting about parent controllers with $modal but I can't find them on SO atm.

The issue is that the $modal service adds the element to the end of the document. So it doesn't sit in you scope hierarchy and instead is only a child of the $rootScope. You have a couple of options.
Pass the data to the modals controller via the $rootScope or broadcast/emit events (messy)
Manually pass the modal the scope you want to use as part of the modal.open options object via the scope property. (You can call $scope.$new() to manually create a child scope).
Use the resolve property of the modal.open options object to pass the data to be injected into the modals controller just like angular route resolves

Related

ui-router controller stateparams

I have the following usecase:
I have two views which are identical, as well as two controllers which are very similar. I intend to extend one controller with the other and just override the diff. The problem I am having is in ui-router I want to choose a controller by name (Since the templates is shared, and the controllers differ this cannot be declared in the template itself)
Before this refactoring I had the following code:
.state('edit-page-menu', {
url: '/sites/:siteId/page_menus/:menuId',
templateUrl: 'partials/edit-menu.html',
controller: ['$scope', '$stateParams', (scope, stateParams) => {
scope.siteId = stateParams.siteId
scope.menuId = stateParams.menuId
}]
})
Which adds the stateParams to the scope. In this usecase I had the controller defined in the template and could modify the scope that way. However now when I switch to controller by name approach
.state('edit-page-menu', {
url: '/sites/:siteId/menus/:menuId',
templateUrl: 'partials/edit-menu.html',
controller: 'editMenuCtrl'
})
I don't know how to inject / add the $stateParams to the controller. And I would really like to avoid injecting "$state" in the controller and fetch the parameters that way. Is there a way to modify the scope of a controller if you choose it by name?
Best regards.

How to call parent method from model controller

I have one controller named "contractController"
contractController contains a method save()
contractController opens one model window with controller named "PopUpcontroller"
from PopUpcontroller i want to call save method on contractController
tried to call save method like $parent but nothing works.
Please advice.
If you are using Angular UI Bootstrap (http://angular-ui.github.io/bootstrap/), then, make use of the "resolve" attribute when you call $modal.open().
Whatever you add to resolve, will be available if you hook it up as a dependency injection.
In the example on their page, the "item" is available as below because it is hooked up in "resolve".
Resolve:
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
size: size,
resolve: {
items: function () {
return $scope.items;
}
}
});
Use:
angular.module('ui.bootstrap.demo').controller('ModalInstanceCtrl', function ($scope, $modalInstance, items) {
If the two controllers are nested so that:
<div ng-controller="contractController">
<div ng-controller="PopUpcontroller">
</div>
</Div>
you can just call $scope.save() on the popUpController and it automatically goes up to find a parent with that method (till the $rootScope).
if they are not nested you should use the services in order to perform the communication among controllers
You need to make sure the popup is created from contractController's scope if you want to retain its prototype. How are you creating the popup?
Something like this in Angular UI Boostrap (I don't use it but the API should accept it):
$modal.open({
scope: $scope,
/* ... other opts */
});

AngularJS : writing ui-router $state data into ancestor $state for sibling access

I have a customer management interface that I'm trying to write using ui-router. I have some states set up as
"csp"
"csp.search"
"csp.customer"
"csp.customer.details"
"csp.customer.status"
How can I use ui-router's $state data to take the csp.search result and provide it to the rest of csp and/or csp.customer? As I understand it, the data would need to be on the closest common ancestor, csp, but there's no easy/clean way to do that that I can find.
I know I can make everything a child state of csp.search, so that they would inherit $state.current.data. I could also parse $state.current.name for the first name before the ., but how universal is that? Further still, I think I could write something that climbs up the ancestry ($state.$current.parent) until finding some "top-most" signal, but I don't know what that should be.
Is there a more elegant, Angular solution?
Edit: The same question might be asked, given a known state, e.g. csp, how can I add data to it from any controller?
Your csp.search results would be on a $scope. If $scopes in additional controllers need to share the model/state/data referenced by that $scope, use a singleton object instance by registering a angular service. That one factory can be injected into as many controllers as you like, and then everything can work off that one source of truth.
Heres a simple demo of a factory sharing an Object between controllers with ui-router http://plnkr.co/edit/P2UudS?p=preview (left tab only)
Factory & Controllers:
app.factory('uiFieldState', function () {
return {uiObject: {data: null}}
});
app.controller('NavbarCtrl', ['$scope', 'uiFieldState', '$stateParams', '$state',
function($scope, uiFieldState, $stateParams, $state) {
$scope.selected = uiFieldState.uiObject;
}
]);
app.controller('LeftTabACtrl', ['$scope', 'uiFieldState', '$stateParams', '$state',
function($scope, uiFieldState, $stateParams, $state) {
$scope.selected2 = uiFieldState.uiObject;
}
]);
The factory object {uiObject: {data: null}} is injected into the controller with uiFieldState & then its simply $scope.selected = uiFieldState.uiObject; for connecting the factory to the scope ng-model="selected.data" .
This is a pretty good tutorial on angularJS services: http://ng-newsletter.com/posts/beginner2expert-services.html

Can you emit event from Controller in one module to Directive Controller in another module?

I have a controller "MyController" and a directive "MyDirective" in separate modules "app" and "directiveModule" respectively. DirectiveModule has been injected into the angular.module of "app".
The issue I am having is as part of "app", I have a controller that emits an event "TEST" that the controller for the directive does not pick up. How can I successfully get the directive of its own module to catch the emit? Is this possible? (Note: I tried $scope originally, then used $rootScope, but both do not make a difference.)
My controller:
app.controller('MyController', function($scope, $rootScope) {
$rootScope.$emit('TEST');
});
My directive:
directiveModule.directive('MyDirective', ['$rootScope', 'MyService',
function($rootScope, MyService) {
return {
restrict: 'E',
controller: ['$scope', '$rootScope', function($scope, $rootScope) {
$rootScope.$on('TEST', function() {alert("Event Caught")})
}]};
}];
UPDATE: It looks like my directive has not initiated by the time the event is broadcast. Is there a way to make it such that I could "wait for directive to instantiate" as opposed to waiting an arbitary "1000" ms or another alternative?
I think the only case your directive needs to catch that event is to get some initial data depending on the MyController because if your directive is independent of the MyController, you don't need to catch that event, just initiate the directive independently.
My solution is using $watch to get notified when the initial data from the MyController is ready.
app.controller('MyController', function($scope) {
$scope.data = {}; //initialize the data, this data could be fetched from an ajax
});
directiveModule.directive('MyDirective', ['MyService',
function(MyService) {
return {
restrict: 'E',
controller: ['$scope', function($scope) {
$scope.$watch("data",function(newValue,OldValue,scope){
//do your work here when data is changed.
});
}]};
}];
In case you use isolate scope in your directive:
directiveModule.directive('MyDirective', ['MyService',
function(MyService) {
return {
restrict: 'E',
scope : {
directiveData:"=",
},
controller: ['$scope', function($scope) {
$scope.$watch("directiveData",function(newValue,OldValue,scope){//watch on the directive scope property
//do your work here when data is changed.
});
}]};
}];
Your html may look like this:
<my-directive directive-data="data" /> //bind isolate scope directive property with controller's scope property.
I think below questions are some similar cases like this:
AngularJS : Directive not able to access isolate scope objects
AngularJS : directives and scope
all modules injected into main module share same rootScope. $emit however travels up the scope tree, you need $broadcast.
If you $broadcast from $rootScope it will be received by all active scopes so in directive could actually use $scope.$on and wouldn't need to inject $rootScope just for the purpose of using $on
Similarly if directive is descendant of the controller, could also broadcast from controller scope
You can. Demo: http://plnkr.co/edit/vrZgnu?p=preview
A side note: if you try to communicate to children element that is lower in DOM tree, you should use $broadcast. $emit is used to communicate with parents elements ("up" direction).
But your true problem might be that when you tried to emit the event, the directive might not be initiated yet. I used $timeout service to wait 1000ms to broadcast the event.
Update
If you don't want to wait a arbitrary 1000ms, you can let your directive $emit "ready" event to inform controller that it's ready. Then broadcast from the controller.
Also removed the 1000ms in this demo http://plnkr.co/edit/GwrdIw?p=preview, everything still works. But I don't think this is a reliable solution.

$routeParams is empty in main controller

I have this piece of layout html:
<body ng-controller="MainController">
<div id="terminal"></div>
<div ng-view></div>
<!-- including scripts -->
</body>
Now apparently, when I try to use $routeParams in MainController, it's always empty. It's important to note that MainController is supposed to be in effect in every possible route; therefore I'm not defining it in my app.js. I mean, I'm not defining it here:
$routeProvider.when("/view1", {
templateUrl: "partials/partial1.html"
controller: "MyCtrl1"
})
$routeProvider.when("/view2", {
templateUrl: "partials/partial2.html"
controller: "MyCtrl2"
})
// I'm not defining MainController here!!
In fact, I think my problem is perfectly the same as this one: https://groups.google.com/forum/#!topic/angular/ib2wHQozeNE
However, I still don't get how to get route parameters in my main controller...
EDIT:
What I meant was that I'm not associating my MainController with any specific route. It's defined; and it's the parent controller of all other controllers. What I'm trying to know is that when you go to a URL like /whatever, which is matched by a route like /:whatever, why is it that only the sub-controller is able to access the route parameter, whereas the main controller is not? How do I get the :whatever route parameter in my main controller?
The $routeParams service is populated asynchronously. This means it will typically appear empty when first used in a controller.
To be notified when $routeParams has been populated, subscribe to the $routeChangeSuccess event on the $scope. (If you're in a component that doesn't have access to a child $scope, e.g., a service or a factory, you can inject and use $rootScope instead.)
module.controller('FooCtrl', function($scope, $routeParams) {
$scope.$on('$routeChangeSuccess', function() {
// $routeParams should be populated here
});
);
Controllers used by a route, or within a template included by a route, will have immediate access to the fully-populated $routeParams because ng-view waits for the $routeChangeSuccess event before continuing. (It has to wait, since it needs the route information in order to decide which template/controller to even load.)
If you know your controller will be used inside of ng-view, you won't need to wait for the routing event. If you know your controller will not, you will. If you're not sure, you'll have to explicitly allow for both possibilities. Subscribing to $routeChangeSuccess will not be enough; you will only see the event if $routeParams wasn't already populated:
module.controller('FooCtrl', function($scope, $routeParams) {
// $routeParams will already be populated
// here if this controller is used within ng-view
$scope.$on('$routeChangeSuccess', function() {
// $routeParams will be populated here if
// this controller is used outside ng-view
});
);
As an alternate to the $timeout that plong0 mentioned...
You can also inject the $route service which will show your params immediately.
angular.module('MyModule')
.controller('MainCtrl', function ($scope, $route) {
console.log('routeParams:'+JSON.stringify($route.current.params));
});
I have the same problem.
What I discovered is that, $routeParams take some time to load in the Main Controller, it probably initiate the Main Controller first and then set $routeParams at the Child Controller. I did a workaround for it creating a method in the Main Controller $scope and pass $routeParams through it in the Child Controllers:
angular.module('MyModule')
.controller('MainController', ["$scope", function ($scope) {
$scope.parentMethod = function($routeParams) {
//do stuff
}
}]);
angular.module('MyModule')
.controller('MyCtrl1', ["$scope", function ($scope) {
$scope.parentMethod($routeParams);
}]);
angular.module('MyModule')
.controller('MyCtrl2', ["$scope", function ($scope) {
$scope.parentMethod($routeParams);
}]);
had the same problem, and building off what Andre mentioned in his answer about $routeParams taking a moment to load in the main controller, I just put it in a timeout inside my MainCtrl.
angular.module('MyModule')
.controller('MainCtrl', function ($scope, $routeParams, $timeout) {
$timeout(function(){
// do stuff with $routeParams
console.log('routeParams:'+JSON.stringify($routeParams));
}, 20);
});
20ms delay to use $routeParams is not even noticeable, and less than that seems to have inconsistent results.
More specifically about my problem, I was confused because I had the exact same setup working with a different project structure (yo cg-angular) and when I rebuilt my project (yo angular-fullstack) I started experiencing the problem.
You have at least two problems here:
with $routeParams you get the route parameters, which you didn't define
the file where you define a main controller doesn't really matter. the important thing is in which module/function
The parameters have to be defined with the $routeProvider with the syntax :paramName:
$routeProvider.when("/view2/name1/:a/name2/:b"
and then you can retrieve them with $routeParams.paramName.
You can also use the query parameters, like index.html?k1=v1&k2=v2.
app.js is the file where you'd normally define dependencies and configuration (that's why you'd have there the app module .config block) and it contains the application module:
var myapp = angular.module(...);
This module can have other modules as dependencies, like directives or services, or a module per feature.
A simple approach is to have a module to encapsulate controllers. An approach closer to your original code is putting at least one controller in the main module:
myapp.controller('MainCtrl', function ($scope) {...}
Maybe you defined the controller as a global function? function MainCtrl() {...}? This pollutes the global namespace. avoid it.
Defining your controller in the main module will not make it "to take effect in all routes". This has to be defined with $routeProvider or make the controller of each route "inherit" from the main controller. This way, the controller of each route is instantiated after the route has changed, whereas the main controller is instantiated only once, when the line ng-controller="MainCtrl" is reached (which happens only once, during application startup)
You can simply pass values of $routeParams defined into your controller into the $rootScope
.controller('MainCtrl', function ($scope, $routeParams, MainFactory, $rootScope) {
$scope.contents = MainFactory.getThing($routeParams.id);
$rootScope.total = MainFactory.getMax(); // Send total to the rootScope
}
and inject $rootScope in your IndexCtrl (related to the index.html)
.controller('IndexCtrl', function($scope, $rootScope){
// Some code
});

Resources