I have a controller used by a modal view (with angularui). At some point I would like to use this controller with a custom view (not a modal one). So everything is fine except for the resolve variables (variables sent to the controller).
I call the modal like this:
var modalInstance = $modal.open({
templateUrl: 'myModal.html',
controller: 'modalCtrl',
size: 'lg',
resolve: {
variable: function(){
return myVar;
}
}
});
How can I send variables from javascript (or html)?
Here is how I bootstrap my custom view (custom.html):
angular.element($(document.body)[0].children[0]).attr('ng-controller','modalCtrl');
angular.bootstrap( document.body, [ 'myApp' ]);
One way to do this would be to define a service or a value with the same name of your resolve (variable in this case). Then Angular will find the dependency for you and inject it.
You may not want to always have this service/value defined. So you might define this service/value in a module, and then conditionally load that module when you bootstrap the app:
if (someCondition) {
angular.module('use.me.conditionally', []).value('variables', 123);
angular.element($(document.body)[0].children[0]).attr('ng-controller','modalCtrl');
angular.bootstrap(document.body, ['myApp', 'use.me.conditionally']);
} else {
angular.bootstrap(document.body, ['myApp']);
}
EDIT:
You can use the value() or service() functions to declare injectable objects that have many properties:
var foo = { property1: 'abc', property2: 456 };
angular.module('use.me.conditionally',[])
.value('variablesAsAValue', foo)
.service('variablesAsAService', function() { return foo; });
Note that you don't need both value() and service(), I'm just showing both approaches. value() is typically used for constant type of variables, and services are typically object/classes.
Related
I have a constant which is injected into a controller and I need to write a test which changes this constant and expects different results. I can use $provide to mock the constant but according to articles I've found online, I need to do it in the module declaration, which I believe is like this:
beforeEach(module("someModule"));
beforeEach(function () {
module(function ($provide) {
$provide.constant('someConstant', false);
});
});
I later load the controller like this:
function createController() {
view = $controller(
"someController",
{
$scope: $injector.get("$rootScope").$new()
});
}
Where $controller, $scope and $injector are all injected in my main beforeEach
This does provide the constant and it does change if I change the value in my beforeEach. But only for the entire test suite. I want to change this constant in a describe or an it but I'm not sure how. If I move the $provide down to a describe or it, I get the error:
Error: Injector already created, can not register a module!
I could just create a new file and that is probably what I'm going to do but is there a way I can $provide a dynamic value?
Lets consider such a code
controller
angular.module('someModule', [])
.controller('someController', function($scope, someConstant) {
$scope.someProvidedValue = someConstant;
})
and test for it
controller spec
describe('module', function () {
beforeEach(module("someModule"));
var createController;
beforeEach(inject(function (_$controller_, _$injector_) {
scope = _$injector_.get("$rootScope").$new()
createController = function createController(scope, obj) {
_$controller_("someController", {
$scope: scope,
someConstant: obj.someConstant
});
}
}))
it('someConstant', function () {
expect(scope.someProvidedValue).toBe(undefined)
createController(scope, {
someConstant: false
})
expect(scope.someProvidedValue).toBe(false)
})
it('someConstant', function () {
expect(scope.someProvidedValue).toBe(undefined)
createController(scope, {
someConstant: true
})
expect(scope.someProvidedValue).toBe(true)
})
})
In the mean time I'm looking for looks nicer solution.
Let's say in 90-95% of my routes I need to check if user is in (let's say jail).
Then, I'm currently doing something like:
$routeProvider
.when("/news", {
templateUrl: "newsView.html",
controller: "newsController",
resolve: {
injail: function(jailservice){
return jailservice.injail();
}
}
})
Do I really need to do this resolve on each route? (the routes are in different folders, each route file contains route for a specific module).
Is it something better I can do than call the resolve injail on every route?
A few options.
Option 1 - use parent state with resolve
$stateProvider
.state('parent', {
resolve:{
resA: function () {}
}
})
.state('parent.child', {
// resA from parent state available to all child states
controller: function (resA) {}
});
More info
Option 2 - external resolve functions (less duplicated code)
Declaration:
resolve: {
myResolve: myResolve
}
If using ES2015, you can shorten it to resolve: {myResolve} (see enhanced object literals)
Definition (in a separate file containing all resolves):
myResolve.$inject = ['myService'];
function myResolve(myService) {
return myService.getStuff();
}
More info
EDIT - example using your code:
In your routes declaration, change resolve to: resolve: {injail: injailResolve}
In separate file, the definition:
injailResolve.$inject = ['jailservice'];
function injailResolve(jailservice) {
return jailservice.injail();
}
Question: how do I access a dynamically generated data in scope B, when I go from scope A and generate this data in scope A, using angular's ui-controller. Data is not available when the scope is initialized.
Note: I am fine with showing request data in the URL. I'm looking for the simplest way for new state to read data it needs and pass it to server and properly generate its contents.
When the page loads, it fetches data from server and populates scope "tests" with new data. This new data is shown on the page. I create links to scope "test" with this data. Links look like this:
<a ui-sref="test({id:test._id})">{{test.name}}</a>
On a rendered page it looks like this:
<a ui-sref="test({id:test._id})" class="ng-binding" href="#/test/57adc0e30a2ced3810983640">A test</a>
The href is correct and points to a database reference of an item. My goal is to have this reference as a variable in scope "test". My state provider:
$stateProvider
.state('tests', {
url: '/tests/',
templateUrl: 'test/index.html',
controller: 'Test.IndexController',
controllerAs: 'vm',
data: { activeTab: 'tests' }
})
.state('test', {
url: '/test/{id}',
templateUrl: 'test/item.html',
controller: 'Test.ItemController',
controllerAs: 'vm',
data: {
activeTab: 'tests',
testId: '{id}'
}
});
So far no matter what I tried I couldn't access "testId" in the "test" scope. It was either "undefined", created errors or returned "itemId: {id}".
My Item.Controller:
(function () {
'use strict';
function Controller(TestService) {
var vm = this;
vm.test = null;
function getTest(id) {
TestService.GetTestById(id).then(function(test) {
vm.test = test;
});
}
function initController() {
getTest(...);
}
initController();
}
angular
.module('app')
.controller('Test.ItemController', Controller);
})();
TestService provides http get methods for getting data from server.
(function () {
'use strict';
function Service($http, $q) {
var service = {};
function handleSuccess(res) {
return res.data;
}
function handleError(res) {
return $q.reject(res.data);
}
function GetTestById(_id) {
var config = {
params: {
testId: _id
}
};
return $http.get('/api/tests/:testId', config).then(handleSuccess, handleError);
}
service.GetTests = GetTests;
service.GetTestById = GetTestById;
return service;
}
angular
.module('app')
.factory('TestService', Service);
})();
I tried $scope - scope is not defined. I tried a number of other techniques, shown by other users with similar success - either "undefined" or error of some sort.
This is based on another person's code so there may be obvious mistakes, please let me know if you find any. If you need more code, let me know - I'll upload it to github (its a messy work in progress at the moment so I'm not sure what should be uploaded).
I'm trying to figure out how to pass unit_number into the modal when it pops up. I'm pretty new with Angular and I'm a little confused with what resolve: and group: are doing and how I can include the unit_number in that return statement.
$scope.openTenantModal = function (unit_number) {
var modalInstance = $uibModal.open({
animation: true,
templateUrl: 'views/addtenantmodal.html',
controller: 'AddTenantModalCtrl',
size: 'large',
resolve: {
group: function () {
return $scope.group;
}
}
});
modalInstance.result.then(function () {
}, function () {
});
};
You are using ui-bootstrap
Bootstrap components written in pure AngularJS
To pass a variable to a modal's controller you need to use
resolve: {
A: function() {
return 'myVal'
}
}
And then you can access that variable 'A' from the modal`s controller by injecting it
controller: ['A', function(A) {
// now we can add the value to the scope and use it as we please...
$scope.myVal = A;
}]
Check out: https://angular-ui.github.io/bootstrap/#/modal
Resolve:
Members that will be resolved and passed to the controller as locals; it is equivalent of the resolve property in the router.
And group is just a member (it could be anything you choose)
Just add a property in resolve object unitNumber with a function returning unit_number value from it. So that you can get the unit_number value inside AddTenantModalCtrl by injecting unitNumber dependency in controller factory function.
resolve: {
group: function () {
return $scope.group;
},
unitNumber: function(){
return unit_number
}
}
Note: Don't directly do unitNumber: unit_number, because when you have that, angular DI system will try to search the dependency
with name unit_number(value) and It will try to evaluate it as
function. Resultant you will get $injector error in console.
So I have this directive that has its own scope but I want to access to a function inside its parent controller. I can do this if the parent controller exposes the function with a $scope.getElementsList(), although I'm trying to avoid the use of $scope and I have the function exposed with self.getElementsList() and the directive cannot reach it.
Directive:
angular.module('myApp').directive('accountBalance', function() {
return {
scope: {
elementId: '=elementid'
},
transclude: true,
restrict: 'E',
templateUrl: '../views_directives/account-balance.html',
controller: function($scope) {
$scope.removeElement = function(){
//this where I want to access the parent function
console.log($scope.$parent.getElementsList());
console.log("ALSO I WANT TO ACCESS THIS DIRECTIVE elementId WITHOUT USING $scope", $scope.elementId);
}
}
};
});
ParentController:
angular.module('myApp').controller('AppDesignCtrl', function ($scope) {
var self = this;
self.elementsList = [];
self.getElementsList = function(){
return self.elementsList;
}
});
I also want to know what is the best way to access, inside the directive controller, the data passed to the directive's $scope.
scope: {
elementId: '=elementid'
},
UPDATE
<div>
<i class="fa fa-arrows element-drag"></i>
<i class="fa fa-trash-o element-remove" ng-click="removeElement()"></i>
</div>
And what about calling functions from the directive template inside the controller of the directive? Do I need to expose them with something like $scope.removeElement()? How do I use this.removeElement() and be able to access it from the template?
Sorry about the long question. I'm trying to set the best practices to my new project since I've been away from angular for a year+.
Thanks in advance
(Going from bottom to top...)
To call functions in the controller without using the scope in Angular >= 1.2, use the controllerAs syntax:
<div ng-controller="AppDesignCtrl as appDesignCtrl">
...
<i class="fa fa-trash-o element-remove" ng-click="appDesignCtrl.removeElement()"></i>
</div>
And removeElement() must be a method of the controller:
angular.module('myApp').controller('AppDesignCtrl', function ($scope) {
...
this.removeElement = function() {
...
};
});
To access the scope data from the controller in Angular >= 1.3, use the new bindToController: true configuration (this is especially useful when combined with the new controllerAs syntax):
angular.module('myApp').directive('accountBalance', function() {
return {
...
scope: {
elementId: '=elementid'
},
controller: function() {
// now elementId is a member of the controller:
console.log(this.elementId);
}
};
});
Having said these, the answer to how you can call getElementsList from the directive would be:
angular.module('myApp').directive('accountBalance', function() {
return {
...
scope: {
elementId: '=elementid',
getElementList: '&'
},
controller: function() {
...
// invoking the expression that was passed to us
var theElements = this.getElementList();
}
};
});
The correct expression should be passed as:
<div ng-controller="AppDesignCtrl as appDesignCtrl">
<account-balance element-id="xxx"
get-elements-list="appDesignCtrl.getElementsList()"></account-balance>
</div>
It is generally not recommended, because directives are meant to be self-contained. It isn't critical if you don't plan to reuse the directive. And wise usage of isolate scope can solve this.
angular.module('myApp').directive('accountBalance', function() {
return {
scope: {
outerScope: '#'
elementId: '='
},
transclude: true,
restrict: 'E',
templateUrl: '../views_directives/account-balance.html',
controller: function($scope) {
console.log("we can use anything from other controller", $scope.outerScope.elementsList)
$scope.elementId = "and share data with any other scope";
}
};
});
Controller is defined as ng-controller="AppDesignCtrl as appDesign", and directive usage is
<account-balance element-id="sharedParentScopeVar" outer-scope="appDesign">
So there won't be any problem if the directive should be moved to other controller.
I guess 'best practice' may be to set up a service that embraces the data and is used by both app controller and directive, so directive controller operates on data items and not DOM elements.
And what about calling functions from the directive template inside
the controller of the directive? Do I need to expose them with
something like $scope.removeElement()?
You surely don't. If there's a need to use functions from outside, you're doing something wrong. Send a message to respective element to run the function if it is DOM-related. Or put the function into the service if it is data-related.