Pass data to angular's nested controller - angularjs

The problem
I'm using UI Bootstrap's dialog service in my application, this service creates modal dialog and I do that using the following method on my outer $scope:
$scope.showRouteEditDialog = function (template, route) {
// argument `route` is not currently used
var dialog = $dialog.dialog({
backdrop: true,
keyboard: false,
backdropClick: true,
templateUrl: template,
controller: 'RouteController'
});
dialog.open().then(function(result) {
alert(result); // This line is called when dialog is closed
});
}
This method is later called from partial view with the following markup
<i class="halflings-icon edit"></i>
My dialog handles editing of a route (route is a sub-model within main model) and therefore I would like to pass that route to the dialog so that it treats it like it's own model without knowing anything about the outer model.
My initial guess on this problem was to assign the route argument to dialog variable, something like dialog.route = route and have it later used within controller with the following code:
Application.controller('RouteController', ['$scope', 'dialog', function ($scope, dialog) {
// `dialog` is our dialog and it is injected with injector
doSomethingWith(dialog.route)
}]);
Though this approach creates a dependency and doesn't look like an angular's way of doing things
I have also found this post saying that I should be using service for that purpose, but it seems like this solution is an overkill for such minor problem.
Question
What is the angular way of passing a value from outer controller to an inner one with scenario described above.
Thank you

You can use "resolve" -
var dialog = $dialog.dialog({
resolve: {route: function(){return "route"}}
});
and later you can inject the "route" value inside of your controller
.controller("SomeCtrl",function(route){
})

Related

How to associate an AngularJS controller with a route via a function's return value?

I'm trying to create an AngularJS slideshow app where users can add content within a projects/ directory.
The router already uses variable routes.
.when('/:project', {
templateUrl: function(params) {
return 'projects/' + params.project + '/project.html';
}
})
However, I can't figure out how to use variable Controller names. For example, the following does not work:
.when('/:project', {
templateUrl: function(params) {
return 'projects/' + params.project + '/project.html';
},
controller: function() { return 'SomeController'; }
})
But this does:
.when('/:project', {
templateUrl: function(params) {
return 'projects/' + params.project + '/project.html';
},
controller: 'SomeController'
})
Is it possible to associate a controller with a route using a function?
If this sounds like a silly idea, let me explain what I'm trying to do. Instead of having user's edit the MainController, I'd like to be able to write JavaScript specific to a project. If a project gets deleted, that project's specific JavaScript is also deleted. If a new project gets added that needs custom JavaScript, users can use an API (AngularJS Events) to interact with the UI.
What's the best way to do something like this?
UI Router has controllerProvider feature. ngRoute doesn't have it.
The function specified in route controller is actual controller constructor, it can't just return controller name. It is ngView directive that defines how route controllers are instantiated. Without patching the directive the behaviour can be simulated with:
controller: function ($scope, $controller) {
return $controller('SomeController', { $scope: $scope });
}

Angular Ui-router Resolve concept

I was reading the docs of ui-router but I couldn't grasp the concept of resolves for controllers in each state. I am not able to figure out where should we use resolve and why the controller attached to a state is not enough (as we can inject any dependencies in it we want) ?
I've tried going through docs and other tutorials several times but its quite confusing , Can someone please explain it with its real life application?
Imagine you want to create a modal and pass some data to it. I'm using the angular-ui-bootstrap modals for this example.
var openExampleModal = function () {
var modalInstance = $modal.open({
templateUrl: "Modal.html",
controller: "ModalController",
size: "lg"
});
return modalInstance.result;
};
Now if you want to pass some data to this modal on initialization, you can either save it in your $rootScope or some data service, or you can use resolve to inject it into your controller directly without having to use anything else.
var openExampleModal = function (myData) {
var modalInstance = $modal.open({
templateUrl: "Modal.html",
controller: "ModalController",
size: "lg",
resolve: {
sampleData: function () {
return myData;
}
}
});
return modalInstance.result;
};
and in your controller you would have:
MyController.$inject = ["sampleData"];
function Mycontroller(sampleData) {
//You can access the data you passed on via sampleData variable now.
};
Resolve is used to inject your own custom objects into the controller, not for injecting dependencies.
A resolve is simply a value that is passed to the controller upon instantiation (which are used like an injected value). The neat thing about them is that if the value returned is a promise, the view/controller won't load until the promise has resolved.
The way you use them is by adding a resolve key to your route state, and returning the object you want injected into your controller (also naming it). For example:
.state('example', {
url: '/page',
templateUrl: 'sometemplate.html',
controller: 'SomeCtrl',
resolve: {
injectionName: function(){
// return a value or promise here to be injected as injectionName into your controller
}
}
});
Then inside your controller you simply add the resolve name to the controller injected values:
.controller('SomeCtrl', function($scope, injectionName){
// do stuff with injectionName
});
Just note that if you do return a promise, the value that is injected is the result of the promise (not the promise itself). Also note that if the promise errors the view/controller will not load, and an error will be thrown. As #koox00 commented, this error will fail silently unless $stateChangeErrorError is handled (usually in your apps primary run() function).
So why would you use this? Well if not inferred from above, you do this usually when you want your view/controller to wait until some async process has completed before loading a particular state. This saves you from creating loaders or loading processes for every single view/controller, moving it to a simple definition of what needs to be loaded.
As said by Jean-Philippe you can use resolve if you want to load some data before switching to a certain state. Resolve waits and blocks until the data is arrived and only then the state transition is done.
It is an highly discussed topic whether using a resolve or loading the data on the fly within the controller. I would say: It depends on your use case :)
Further info from supercool todd motto: https://toddmotto.com/resolve-promises-in-angular-routes/

How to get the current route's controller (or scope)?

I have a controller that needs a thing provided by a route resolve function:
$routeProvider.when('/some/url', {
controller: MyController,
controllerAs: 'myCtrl',
resolve: {
theAnswer: ['deepThought', function(deepThought) {
return deepThought.computeTheAnswerAndReturnAPromise();
}]
}
});
var MyController = ['$route', function($route) {
this.theAnswer = $route.current.theAnswer;
}];
Now I want to do an end-to-end test, checking that the route matches and that parameters are propagated properly:
// ...set up the routes...
$location.path('/some/url');
$rootScope.$digest();
var ctrl = ???;
expect(ctrl.aThing).toBe(42);
In the non-test setup, I can put in a log statement and see that the controller is being created successfully and gets the correct data injected. The only problem is: how to get hold of the controller in the test?
There is $route.current.controller, but it contains the controller's constructor function and not the controller instance.
The documentation promises a $route.current.locals.$scope, from which I could get myCtrl, but the $scope property doesn't actually exist unless we also use ngView (it gets set here).
The controller isn't registered with any module, so I can't use $provide to intercept its creation and stash the controller somewhere.
Found it, thanks to #PSL's comment. The thing that actually constructs the controller is the ngView link function. We can fake that easily enough:
var ctrl = $controller(MyController, $route.current.locals);

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 */
});

How to use the same controller for modal and non-modal form in Angular UI Bootstrap?

I've got a modal with a registration form. The same form should be displayed at the bottom of the landing page not in a modal.
Currently my controller that handles registration modal takes $modalInstance as one of its parameters along $scope etc. If I add ng-controller="SignUpCtrl" to an element in the landing page, it doesn't work, because the controller wasn't created via $modal.open method and so Angular complains about Unknown provider: $modalInstanceProvider <- $modalInstance.
I've got a service for registering users (authService.signUp(data).then/catch...), but the controller itself does a bit more - handles input, emits events (e.g. with translated error messages), sets cookies etc.
What's the best way to handle such case without duplicating almost whole controller code? Should I move the code from controller into yet another, higher-level service?
After struggling for a long while I found a easier trick to reuse our Controller for both modal and normal case.
I found that we can pass caller's scope to modal controller, so I pushed modalInstance into $scope and passed it to the modal controller.
Now you don't have unknown provider problem because $scope is a well known one.
Below is an example:
CallerController = function($rootScope, ...) {
var modalScope = $rootScope.$new();
modalScope.modalInstance = $modal.open({
templateUrl: tempUrl,
controller: ReusableModalController,
scope: modalScope // <- This is it!
});
modalScope.modalInstance.result.then(function (result) {
// Closed
}, function () {
// Dismissed
});
};
ReusableModalController = function($scope, ...){
var dataToSendBack = 'Hello World';
$scope.modalInstance.close(dataToSendBack);
};
Cheers!
If you are using ui-router you can easily use the resolve from ui-router to provide a $uibModalInstance (was $modalInstance before):
$stateProvider
.state('conductReview', {
url: '/review',
templateUrl: '/js/templates/review.html',
controller: 'yourController',
resolve: {
$uibModalInstance: function () { return null; } // <- workaround if you want to use $uibModalInstance in your controller.
}
})
That way you can use your modal controller like a normal controller. If you inject $uibModalInstance in your controller it will be null.
If you want to use same controller for both modal form as well as landing page form, make use of
modalInstance.result.then(function (selectedItem) {
$scope.selected = selectedItem;
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
You can get all the data from modal form in selectedItem and use this in landing page form.
Also, how do you open the modal. If it is through a button, bind that ng-model to open modal using $modal.open.Don't create separate controller for your modal.Use the same one as your landing page. This way you can use 1 controller to open modal as well as other function after the modal is closed.
PS: Code snippet given here is from angular ui's page. Check that page's documentation for selectedItem

Resources