I have a page with a controller that has a directive that displays a form with inputs.
The directive has an object that contains all the inputs (and their ng-model) that are displayed on the form. This binds the form inputs data to the object variable inside the directive.
I need to display results (and other actions) submiting the data of the form.
For that I created a service that handles the business logic and thae ajax calls.
The questions here is... how should I access the form data from the service to perform the required actions? I thought about accessing the directive variable from the service, but I'm not sure how to do it and if this is the right way in the first place.
The service should hold a model which is basically the javascript object of your form.
The directive should inject the service and add that object on his scope(a reference).
The directive's template should speak with the directive's scope and display the form.
Changing a value on the view will reflect the service since we they have the same reference and the view will update the directives scope since there is two way binding.
admittedly I'm still working things out, but I think if you add in a controller between your directive and service things will be bit clearer. This is the most compact example of the basic structure I've been playing with.. (forgive the coffeescript if that's not your thing).
angular.module 'app'
.controller 'KeyFormCtrl', (SessionService, $scope, $location, $log) ->
#error = null
#message = null
#keySent = false
#creds =
username: null
#sendKey = ->
SessionService.sendKey({ username: #creds.username })
.then(
(resp) =>
#keySent = true
(err) =>
#error = err.error
)
.directive 'eaKeyForm', ->
{
restrict: 'AE'
scope: {}
templateUrl: 'session/key-form.html'
replace: true
controller: 'KeyFormCtrl'
controllerAs: 'kfc'
bindToController: true
}
session/key-form.html:
<form>
<div ng-show="kfc.error">{{kfc.error}}</div>
<div ng-show="kfc.message">{{kfc.message}}</div>
<div ng-show="kfc.keySent">
An account key has been sent to {{kfc.creds.username}}.<br />
</div>
<div ng-hide="kfc.keySent">
<input type="email" ng-model="kfc.creds.username">
<button ng-click="kfc.sendKey()">Send Key</button>
</div>
</form>
angular.module('myApp', [])
.directive('myAweseomDirective', ['MyAwesomeService', function(MyAwesomeService) {
return {
link: function(scope) {
scope.saveFormDetails = function() {
MyAweseomeService.saveInformation(scope.data);
//where data is the ng-model for the form
}
}
};
}])
.service('MyAweseomService', function() {
MyAwesomeService.saveInformation = function(data) {
//do whatever you want to with the data
}
});
Related
I want to call controller function from directive. Here is the fiddle. I have a sayHello() function in controller. And I want to call that function from angular directive. If i wall like scope.sayHello();
scope.sayHello is not a function
I am getting like the above in console.
To get your alert in your fiddle to fire, all I had to do what add the person into your template. You had the updateparent="updatePerson()", and you just needed to pass the person in that call, like this: updateparent="updatePerson(person)". Then your alert fired.
The reason for this is that you need to state in the template all of the parameters that you are passing in to the function. Since you call it like updateparent({person: mandatePerson}), you have to put the key person into your template that it will be called with that param. They have to match.
The Angular directive's link function has arguments for both scope and controller -- if the method you want to call is directly on $scope in your controller you can just call it off of the scope arg-- if you are using controllerAs syntax (which I would recommend as it is a recommended Angular pattern) you can call it off the controller argument.
So, for your specific case (methods directly on $scope) in your directive return object you add a property link:
link: function (scope, iElement, iAttrs, controller, transcludeFn)
scope.sayHello();
}
link runs once at directive creation-- if you want the scope or method to be available outside of that for some reason, assign it to a variable defined at the top level of the module.
I changed your directive a bit, but this is how you get that sort of functionality.
FIDDLE
If you're interested in AngularJS I would highly recommend the John papa styleguide.
https://github.com/johnpapa/angular-styleguide
It will get you using syntax like controllerAs and will help make your code cleaner.
HTML
<body ng-app="myApp" ng-controller="MainCtrl">
<div>
Original name: {{mandat.name}}
</div>
<my-directive mandat="mandat"></my-directive>
</body>
JS
var app = angular.module('myApp', []);
app.controller('MainCtrl', MainController);
function MainController($scope) {
$scope.mandat = {
name: "John",
surname: "Doe",
person: { id: 1408, firstname: "sam" }
};
}
app.directive('myDirective', MyDirective);
function MyDirective() {
return {
restrict: 'E',
template: "<div><input type='text' ng-model='mandat.person.firstname' /><button ng-click='updateparent()'>click</button></div>",
replace: true,
scope: {
mandat: "="
},
controller: function($scope) {
$scope.updateparent = function() {
$scope.mandat.name = "monkey";
}
}
}
}
I've got the next problem and I'd like to find a solution or if I should even be doing this like I am.
I have the following ui-router configuration:
$stateProvider.state('ListCounterparties', {
url:"/counterparties",
data: { NavMenuItem : 'Counterparties / Desks' },
views: {
'' : {
templateUrl:"./app/module_Counterparties/templates/ListCounterparties.html",
controller:"CounterpartiesControllerCtrl"
},
'deskLists#ListCounterparties' : {
templateUrl : './app/module_Counterparties/templates/DesksDualListTemplate.html',
controller:'DesksControllerCtrl'
}
}
The first, unnamed view, has a table from which I can select a row and then a method will be called to populate a dual list from the second view.
Up until now, I've had both in the same controller, but the controller is getting too big and I decided I had to separate them.
The method to populate the dual lists in 'deskLists#ListCounterparties' is defined in DesksControllerCtrl but it should be called in CounterpartiesControllerCtrl, as the event of row selection is in that controller.
The problem is that the scopes are not shared and the method is inaccesible to the unnamed view.
Accessing the scope in DesksControllerCtrl I could see that accessing the $parent property twice I can get to the CounterpartiesControllerCtrl, but I don't thin that's an ideal thing to do.
Thanks in advance.
Sharing data, methods, etc. between multiple controllers the Angular way would be to create service(s). That means, you create e.g. a service which holds all your data and another one which provides functionality for several controllers. Then, just inject them in your controllers:
var myApp = angular.module('myApp', []);
myApp.factory('myGlobalData', function() {
return {
data: 1
};
});
myApp.factory('myService', function(myGlobalData) {
return {
increment: function() {
myGlobalData.data++;
}
};
});
myApp.controller('MyCtrlA', function($scope, myGlobalData, myService) {
$scope.myGlobalData = myGlobalData;
$scope.myService = myService;
});
myApp.controller('MyCtrlB', function($scope, myGlobalData, myService) {
$scope.myGlobalData = myGlobalData;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<p ng-controller="MyCtrlA">
{{myGlobalData.data}}
</p>
<p ng-controller="MyCtrlB">
{{myGlobalData.data}}
</p>
<div ng-controller="MyCtrlA">
<button ng-click="myService.increment()">Increment data in myGlobalData service</button>
</div>
</div>
So I have the code for a search bar in its own template, which I have in a custom directive. Then I put that directive in the my Navbar template.
<form class="navbar-form navbar-left" role="search">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search" ng-model="searchText">
</div>
<button type="submit" class="btn btn-default" ng-click="go('/searchResults')">Submit</button>
</form>
.
.state('searchResults', {
url: '/searchResults',
templateUrl: 'templates/searchResults.html',
controller: 'searchController'
})
}]).
directive('searchBar', function() {
return {
restrict: 'E',
templateUrl: 'templates/search.html',
controller: "searchController"
}
});
My SearchController handles the scope for this search bar, but I'm having some trouble binding data to another template, which is my searchResults template. My SearchController calls on a service I wrote which retrieves some data and works fine. When I try to bind the data to my SearchResults page however, angular does not bind any of the data received from the service, (it will console log out the correct data though).
Name: {{player.playerName}}<br>
Link: {{player.link}}<br>
Things: {{things}}<br>
controller:
angular
.module('app')
.controller('searchController', ['$scope',"$http","$location", "PlayerDAO", function($scope, $http, $location, PlayerDAO){
$scope.things="stuff";
$scope.go = function ( path ) {
if(checkSearchBar()){
$location.path( path );
PlayerDAO.getPlayers($scope.searchText).then($scope.postPlayerToResultsPage, onError);
}
};
$scope.postPlayerToResultsPage=function(result){
$scope.player=result;
$scope.things="Hooray, it worked";
$scope.player.playerName;
console.log(result);
};
}]);
Oddly enough to me however, it will bind any data that is not in the function I use to get the data (i.e. it will bind {{things}}), but any of the data in my function "postPlayerToResultsPage", isn't seen by Angular. If I take the code for the search bar and put it in my search results page directly, the nav-bar that appears on that page works flawlessly, however I need the search-bar to be on my main navigation template, view-able on all pages.
I suspect your data isn't binding due to the prototypical nature of your directive's $scope.
Try using controllerAs syntax for data binding to avoid $scope ambiguity. e.g.
directive:
controller: 'searchController',
controllerAs: 'searchCtl'
controller:
var controller = this;
$scope.postPlayerToResultsPage=function(result){
controller.player=result;
controller.things="Hooray, it worked";
controller.player.playerName;
console.log(result);
};
html:
Name: {{searchCtl.player.playerName}}<br>
Link: {{searchCtl.player.link}}<br>
Things: {{searchCtl.things}}<br>
I have an example here. How i can access 'itemId' parameter in the parent state controller? Parent view must not be reloaded.
angular.module('app',['ui.router'])
.config(function($stateProvider){
$stateProvider.state('step', {
url: '/orders/:step',
templateUrl: 'parent.html',
controller: function ($scope, $stateParams) {
$scope.itemId = $stateParams.itemId;
$scope.step = $stateParams.step;
}
})
.state('step.item', {
url: '/:itemId',
templateUrl: 'child.html',
controller: function ($scope, $stateParams) {
$scope.itemId = $stateParams.itemId;
$scope.step = $stateParams.step;
}
});
}).controller('SimpleController', function($scope, $state){
$scope.items = [1,2,3,4,5,6];
$scope.steps = ["first", "second"];
})
I see only two ways:
add an service and inject it to both controllers
access a parent scope from a child controller and pass a parameter
But both cause me to add watchers at a parent scope.
Maybe i can accomplish it easier?
First, you need to recognize that a parent state's controller runs only once when you enter the subtree of its child states, so switching between its children would not re-run the controller.
This is to say that if you want the itemId parameter to always be up to date, you'd need a $watch (which you tried to avoid).
For the first time, you could collect the itemId like so in the parent's state:
$scope.itemId = $state.params.itemId;
If you need it to be kept up-to-date, then you'd need to watch for changes, for example:
$scope.$watch(function(){
return $state.params;
}, function(p){
$scope.itemId = p.itemId;
});
plunker
Similarly, if you only need to place it in the view, then set $state on the scope:
$scope.$state = $state;
and in the view:
<div>parent /orders/{{step}}/{{$state.params.itemId}}</div>
EDIT:
I guess another way would be to call a scope-exposed function on the parent to update the value. I'm not a fan of such an approach, since it relies on scope inheritance (which is not always the same as state inheritance) and scope inheritance in a large application is difficult to track. But it removes the need for a $watch:
In the parent controller:
$scope.updateItemId = function(itemId){
$scope.itemId = itemId;
};
and in the child controller:
if ($scope.updateItemId) $scope.updateItemId($stateParams.itemId)
I can see, that you've already found your answer, but I would like to show you different approach. And I would even name it as "the UI-Router built in approach".
It has been shown in the UI-Router example application, where if we go to child state with some ID = 42 like here we can also change the other, than the main view (the hint view in this case).
There is a working example with your scenario, showing that all in action.
What we do use, is the Multiple Named Views
The parent state, now defines root view, which is injected into unnamed ui-view="" inside of the root/index.html.
A new parent.html
<div ui-view="title"></div>
<div ui-view=""></div>
As we can see, it also contains another view target (than unnamed) - e.g. ui-view="title" which we fill with another template immediately... in parent state:
$stateProvider.state('step', {
url: '/orders/:step',
views : {
'': {
templateUrl: 'parent.html',
},
'title#step': {
templateUrl:'parent_title_view.html',
}
}
})
And child? It can continue to handle main area... but also can change the title area. Both views are independent, and belong to child.
.state('step.item', {
url: '/:itemId',
views : {
'': {
templateUrl: 'child.html',
},
'title': {
templateUrl:'child_title_view.html',
}
}
So, there are no watchers, nothing... which is not shipped with the UI-Router by default. We just adjust many places (views) with the child implementation. We can still provide their content with some defaults in parent..
Check that example here. Hope it will help to see it a bit differently...
I'm not sure if this will really solve your problem but you can do
$stateProvider.state('step', {
url: '/orders/:step/:itemId',
templateUrl: 'parent.html',
http://plnkr.co/edit/sYCino1V0NOw3qlEALNj?p=preview
This means the parent state will collect the itemID on the way through.
include $state in resolve function for parent state
.state('parent', {
url: '/parent',
controller: 'parentController',
resolve: {
params: ['$state', function ($state) {
return $state.params;
}]
}
})
then in parent controller inject the resloved vars
app.controller('parentController', ['$scope', 'params',
function ($scope, params) {
$scope.params = params;
}]);
in this way params available in both child and parent.
From this stackoverflow question, my understanding is that I should be using services to pass data between controllers.
However, as seen in my example JSFiddle, I am having trouble listening to changes to my service when it is modified across controllers.
angular.module('myApp', [])
.controller('Ctrl1', function ($scope, App) {
$scope.status = App.data.status;
$scope.$watch('App.data.status', function() {
$scope.status = App.data.status;
});
})
.controller('Ctrl2', function ($scope, App) {
$scope.status = App.data.status;
$scope.$watch('status', function() {
App.data.status = $scope.status;
});
})
.service('App', function () {
this.data = {};
this.data.status = 'Good';
});
In my example, I am trying to subscribe to App.data.status in Ctrl1, and I am trying to publish data from Ctrl1 to App. However, if you try to change the input box in the div associated with Ctrl2, the text does not change across the controller boundary across to Ctrl1.
http://jsfiddle.net/VP4d5/2/
Here's an updated fiddle. Basically if you're going to share the same data object between two controllers from a service you just need to use an object of some sort aside from a string or javascript primitive. In this case I'm just using a regular Object {} to share the data between the two controllers.
The JS
angular.module('myApp', [])
.controller('Ctrl1', function ($scope, App) {
$scope.localData1 = App.data;
})
.controller('Ctrl2', function ($scope, App) {
$scope.localData2 = App.data;
})
.service('App', function () {
this.data = {status:'Good'};
});
The HTML
<div ng-controller="Ctrl1">
<div> Ctrl1 Status is: {{status}}
</div>
<div>
<input type="text" ng-model="localData1.status" />
</div>
<div ng-controller="Ctrl2">Ctrl2 Status is: {{status}}
<div>
<input type="text" ng-model="localData2.status" />
</div>
</div>
Nothing wrong with using a service here but if the only purpose is to have a shared object across the app then I think using .value makes a bit more sense. If this service will have functions for interacting with endpoints and the data be sure to use angular.copy to update the object properties instead of using = which will replace the service's local reference but won't be reflected in the controllers.
http://jsfiddle.net/VP4d5/3/
The modified JS using .value
angular.module('myApp', [])
.controller('Ctrl1', function ($scope, sharedObject) {
$scope.localData1 = sharedObject;
})
.controller('Ctrl2', function ($scope, sharedObject) {
$scope.localData2 = sharedObject;
})
.value("sharedObject", {status:'Awesome'});
I agree with #shaunhusain, but I think that you would be better off using a factory instead of a service:
angular.module('myApp', [])
.controller('Ctrl1', function ($scope, App) {
$scope.localData1 = App.data;
})
.controller('Ctrl2', function ($scope, App) {
$scope.localData2 = App.data;
})
.factory('App', function () {
var sharedObj = {
data : {
status: 'Good'
}
};
return sharedObj;
});
Here are some information that might help you understand the differences between a factory and a service: When creating service method what's the difference between module.service and module.factory