how to pass object in child component to the parents in angularJS - javascript

i'm looking to make a mulit step from in angularJS 1.5x and here is my file format
-events
--events.component.js
--events.controller.js
-form.component.js
-form.controller.js
in my form.component.js i am using UI router to route to the different parts of the form..
var createForm = {
templateUrl: 'app/components/event/form.html'
};
angular
.module('components.event')
.component('createEvent', createEvent)
.config(function($stateProvider) {
$stateProvider
.state('form', {
url: '/form',
component: 'createForm'
})
// nested states
.state('form.events', {
url: '/events',
component: 'createEvents'
})
});
in the parent controller(form.controller.js) i have an object $scope.formData:
function EventController($scope) {
$scope.formData = {};
}
angular
.module('components.event')
.controller('EventController', EventController);
I want to access $scope.formData from my events controller. I've tried using $scope.$parent.formData in events.controller.js which haven't worked for me yet
function EventController($scope) {
$scope.parentformData = $scope.$parent.formData;
}
angular
.module('components.event')
.controller('EventController', EventController);
any ideas on how i can do this?

Hope below code is helpful !
//No need to pass the .js file name.
//Just pass the name / element of your controller.
//eg:
// assuming this is your controller definition/declaration inside form.controller.js
function formController($scope) {
// code insde controller
}
// assuming this code is written inside events.controller.js
function EventController($scope) {
var formControllerScope = angular.element(formController).scope();
console.log(formControllerScope.formData);
}

Related

access resolve in component controller in angularjs 1.5

I am trying to bind to an angularjs 1.5 component a resolve value without success, In the state definition, I have replaced the common value of template properties with a value of the name of my new component. like this:
.state('eventslogs.create', {
url: '/create',
template: '<addevent data="$resolve.addevent"></addevent>.',
resolve: {
addevent: newEventslog
},
data: {
roles: ['admin'],
pageTitle: 'Eventslogs Create'
}
})
NewEventslog is a function that injects one of my services
newEventslog.$inject = ['EventslogsService'];
function newEventslog(EventslogsService) {
return new EventslogsService();
}
In my controller I have tried several ways but nothing works
angular.module('eventslogs')
.component('addevent', {
templateUrl: 'addevent.client.component.view.html',
bindings: {
data: '<'
},
controller: function($scope, $element) {
var vm = this;
vm.eventslog = vm.data;
}
But vm.eventslog always results in an undefined value, what's wrong with my aproach?, If I use "#" instead "<" in bindings, vm.addevent results in a string with value "$resource.addevent" and not like an instance of newEventslog function.
I am using ui-route version 0.2.18
not sure but try this inside the component
this.$onInit = () => {
vm.eventslog = vm.data;
}

Dynamic partial arguments in AngularJS routing

I'm working with an angularjs site and have a background with working with routes in Rails and also Laravel in php. With routes in Laravel we could dynamically create a set of routes similar to:
foreach($cities as $city):
Route::get($city.'/hotels');
Route::get($city.'/{slug}');
endforeach;
Here we defined series of seperate routes in Laravel which technically do look the same except for the value of city and slug.
I'm finding angularJS a bit limited in defining routes in this case. Frankly am a bit lost here.
UPDATE
I've made some modifications here - basically I set up a service which retrieves assets from my database such as in this case a list of cities and categories. I'm trying to do this:
If {slug} is in the array of categories retrieved from my API, then use my ListController and list view but if its not then instead use my SingleVenueController and single view. Here's my code at the moment but its not working :(
appRouteProvider.when('/:city/:slug', {
templateUrl : function(sharedParams, $routeParams){
t = sharedParams.getCurrentPageType($routeParams);
if(t=='list'){
return '../../app/templates/list.html';
}
if(t=='single'){
return '../../app/templates/single.html';
}
},
controller : function(sharedParams, $routeParams){
t = sharedParams.getCurrentPageType($routeParams);
if(t=='list'){
return 'ListsController';
}
if(t=='single'){
return 'SingleController';
}
},
resolve:{
sharedParamsData:function(sharedParams){
return sharedParams.promise;
},
}
})
In the above sharedParams is a service and the getCurrentPageType just checks the url slug to decide what controller to send back - but its not really working at all :(
How about defining a single route with a paramater ?
In angularjs v1.x you can defined as many routes you want with as many params xor query
.config(function($routeProvider, $locationProvider) {
$routeProvider
.when('/city/:slug', {
templateUrl: 'book.html',
controller: 'BookController',
resolve: {
// you can also retrieve some data as a resolved promise inside your route for better performance.
}
})
ref: https://docs.angularjs.org/api/ngRoute/service/$route
appRouteProvider.when('/:city/:slug', {
templateUrl : 'dafault.html',
controller : 'DefaultController',
resolve:{
factory: function($routeParams, $http, $location, sharedParams){
var city = $routeParams.city;
var slug = $routeParams.slug;
var deferred = $q.defer();
sharedParams.getCurrentPageType($routeParams).then(function(t) {
if(t=='list'){
$location.path('/' + city + '/' + slug + '/list');
deferred.resolve();
}
else if(t=='single'){
$location.path('/' + city + '/' + slug + '/single');
deferred.resolve();
} else {
deferred.reject();
}
});
return deferred.promise;
},
}
});
appRouteProvider.when('/:city/:slug/list', {
templateUrl: '../../app/templates/list.html',
controller: 'ListsController',
});
appRouteProvider.when('/:city/:slug/single', {
templateUrl: '../../app/templates/single.html',
controller: 'SingleController',
});
You can do it with separate routes. The idea is when user hits the main route it resolves first with the data from the backend. If the condition is met, resolve function will redirect to specific route if not it wont pass
Services in Angular cannot be injected in the configuration phase since they become available only in the run phase of an Angular application.
There is however a trick to load $http service in the config phase which you can use to load your cities/categories and set up your routes. Meanwhile, since controllers aren't registered up until the run phase, you may use the $controllerProvider to register your controllers beforehand in the configuration phase:
app.config(function ($routeProvider, $controllerProvider) {
$controllerProvider.register('ListController', ListController);
$controllerProvider.register('SingleController', SingleController);
// wire the $http service
var initInjector = angular.injector(['ng']);
var $http = initInjector.get('$http');
...
});
You can now call your API to get the cities (or whatever else) and iterate while registering each route:
...
// fetch the cities from the server
$http.get('/cities')
.then(function (response) {
var cities = response.data;
for(var i = 0; i < cities.length; i++){
$routeProvider
// assuming each city object has a `name` property
.when('/' + cities[i]['name'] + '/:slug', {
templateUrl: getTemplate(cities[i]['name']),
controller: getController(cities[i]['name'])
})
}
});
...
Note that I'm using the getTemplate and the getController methods which return the templateUrl and the relevant controller name strings respectively using an ordinary switch expression. You can choose your own approach.
Plunkr Demo
Note:
While a function with the templateUrl route options property does work with setting up a custom template, but when you use a function alongside the controller property, Angular will consider it as the constructor for the controller. Therefore, returning the name of the controller in that function won't work.
As Ahmad has already pointed out in his answer, if you pass a function to controller it is considered as a constructor for the controller.
Also you can't get a service injected dynamically in config block of your app.
So what you can do is, move your sharedData service in separate app (in my code below I've used appShared as a separate app where this service is defined) and then access it using angular.injector. This way you don't have to define it as a parameter to templateUrl / controller functions.
Btw, you can't pass custom parameters to templateUrl function (ref: https://docs.angularjs.org/api/ngRoute/provider/$routeProvider)
If templateUrl is a function, it will be called with the following
parameters:
{Array.<Object>} - route parameters extracted from the current $location.path() by applying the current route
Now for the controller, use $controller to dynamically load either ListsController or SingleController based on your condition.
Once that is loaded, extend your current controller (defined by your controller function) using angular.extend so that it inherits all the properties and methods of the dynamically loaded controller.
Check the complete code here: http://plnkr.co/edit/ORB4iXwmxgGGJW6wQDy9
app.config(function ($routeProvider) {
var initInjector = angular.injector(['appShared']);
var sharedParams = initInjector.get('sharedParams');
$routeProvider
.when('/:city/:slug', {
templateUrl: function ($routeParams) {
console.log("template url - ");
console.log($routeParams);
var t = sharedParams.getCurrentPageType($routeParams);
console.log(t);
if (t == 'list') {
return 'list.html';
}
if (t == 'single') {
return 'single.html';
}
},
controller: function ($routeParams, $controller, $scope) {
//getController(cities[i]['name'])
console.log("controller - ");
console.log($routeParams);
var t = sharedParams.getCurrentPageType($routeParams);
console.log(t);
if (t == 'list') {
angular.extend(this, $controller('ListsController', { $scope: $scope }));
}
if (t == 'single') {
angular.extend(this, $controller('SingleController', { $scope: $scope }));
}
}
});
});

Angular UI Router - How to Pass Resolve Data to a Nested View from Parent State

I'm currently working on an Angular web application which will display various types of financial events for the user. These events are tied together under one ID, organized into different groups. Each of these groups needs to be displayed in their own separate HTML <table>.
That financial data is retrieved using an Angular Service, and resolved within the Angular UI Router configuration. All of the data is also stored within that same Service.
Below is the current Router configuration:
import EventsController from './events.controller';
import EventsService from './events.service';
import EventsTableController from './events-tables/events-table.controller';
import eventsView from './events.html';
import eventsDetailsTableView from './events-tables/details-table.html';
export default function routes($stateProvider) {
$stateProvider
.state('events', {
url: '/events/{payDate:string}',
template: eventsView,
controller: EventsController,
controllerAs: 'events',
resolve: {
eventsData: ['$http', '$stateParams', ($http, $stateParams) => {
return new EventsService($http, $stateParams);
}]
},
views: {
'details#': {
template: eventsDetailsTableView,
controller: EventsTableController,
controllerAs: 'details',
resolve: {
eventData: 'eventsData.details'
}
}
}
}
);
}
routes.$inject = ['$stateProvider'];
In the future, the EventsController will be used to filter which <table>s will be displayed based on the user's preference.
The EventsTableController is a generic Controller which stores both the type of data being displayed (in this example, "details"), and the data itself, stored as a two-dimensional array.
export default class EventsTableController {
constructor(eventData) {
this.name = eventData.name;
this.data = eventData.data;
console.log(this)
}
}
Also for reference, here's an example of the data being returned from the Service:
{
'details': [[
"Event-ID",
"Company-CUSIP",
"Company-Name"]
]
}
Each <table> will correspond to a different field within that object.
I'm having trouble, however, passing the data from the 'events' state's resolve into the nested details# view. The above configuration returns an error, stating that Angular UI Router is unable to find the specific dependency: 'eventsData.details'.
My question is this: how do I pass individual object fields into the nested views, such that they can all be independently displayed? If there is any more source information that I can provide, please let me know and I'll amend this post for clarity.
By default resolves from parent are available in child states, and named views.
If you have multiple named views inside a state, and they need to use one of the state's resolves, you can inject them normally in the controller, for example:
EventsTableController.$inject = ['eventsData
function EventsTableController(eventsData) {
console.log(eventsData);
}
Child states also inherit resolved dependencies from parent state(s). For example (resA):
$stateProvider.state('parent', {
resolve:{
resA: function(){
return {'value': 'A'};
}
},
controller: function($scope, resA){
$scope.resA = resA.value;
}
})
.state('parent.child', {
resolve:{
resB: function(resA){
return {'value': resA.value + 'B'};
}
},
controller: function($scope, resA, resB){
$scope.resA2 = resA.value;
$scope.resB = resB.value;
}

How to access parameter from nested state in parent state?

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.

Resolving data in Angular Controller

I'm pretty new to Angular and I've been going round in circles on this one for a while now.
A bit of background first, I'm using the MEAN stack from mean.io which uses Angular UI Router.
I have a Post model in my DB which can have a category id assigned to it.
When I create a new post I want to load the existing categories from the DB and display them in a select box.
From what I can see I need to use resolve, first of all it doesn't feel right having logic in the resolve property which is in a file called config.js - so far I've placed the call to a service in there and im getting the categories back using the following code:
.state('create post', {
url: '/posts/create',
templateUrl: 'views/posts/create.html',
controller: 'PostsController',
resolve: {
loadCategories: function (Categories) {
Categories.query(function(categories) {
return categories;
});
}
}
})
The first problem is that I can't access the returned data in my controller or view.
Secondly I only want to load Categories that belong to a certain Organisation. I will be assigning an organisation id to each user so how can I access the currently signed in user when I'm in config.js - again this doesn't feel like the right place to be doing this sort of logic though.
Any help would be really appreciated.
Thanks
config.js:
register post state :
.state('post', {
url: '/posts/create',
templateUrl: 'views/posts/create.html',
controller: 'PostsController',
resolve: PostsController.resolve
})
register posts controller:
.controller({
PostsController: ['$scope', 'loadCategories', PostsController],
...
})
controller function:
function PostsController($scope, loadCategories){
$scope.categories = loadCategories;
};
PostsController.resolve = {
loadCategories: ['dependencies', function(dependencies){
return dependencies.query(...)
}]
};
Angular manage your dependency injection
Assuming Categories is an angular resource, you should be able to just
loadCategories: function (Categories) {
return Categories.query();
}
And then in your controller:
.controller('PostsController', function ($scope, loadCategories) {
$scope.categories = loadCategories;
});
Ok, reading your comments, it sounds like you'll have some issue because you want to inject this into the controller, but only in certain states. You could try:
.state('create post', {
url: '/posts/create',
templateUrl: 'views/posts/create.html',
controller: 'PostsController',
data: {
categories: Categories.query()
}
})
and then
.controller('PostsController', function ($scope, $state){
console.log($state.current.data.categories);
});
Which should work...

Categories

Resources