I'm trying to call a function from within one angular component's controller to another component's controller. I found this answer, but the controllers are defined differently than what is shown in the tutorials I followed, so I'm confused as to how I'm supposed to reference the components and templates.
Here is how I currently define one of my controllers based on the official angular tutorial
angular.
module('moduleName').
component('firstComponent', {
templateUrl: 'url/to/template',
controller: function FirstController($scope) {
//I want to call a function in SecondController here
}
});
//in another component file
angular.
module('moduleName').
component('secondComponent', {
templateUrl: 'url/to/template',
controller: function SecondController($scope) {
//function here which will be called
}
});
Say I re-structure them like in the example I linked above...
var app= angular.module("myApp",[]);
app.controller('One', ['$scope', '$rootScope'
function($scope) {
$rootScope.$on("CallParentMethod", function(){
$scope.parentmethod();
});
$scope.parentmethod = function() {
// task
}
}
]);
//in another file
var app= angular.module("myApp",[]);
app.controller('two', ['$scope', '$rootScope'
function($scope) {
$scope.childmethod = function() {
$rootScope.$emit("CallParentMethod", {});
}
}
]);
How am I supposed to reference each controller's component which they belong to, and their respective templates? I tried to write them like in the example I linked above, but nothing happened. I didn't get any errors, but literally nothing happened. Nothing was displayed on the page. It tried to write to the console, but nothing appeared.
Your second block of code has the right concept, but both controllers need to be instantiated for it to work.
Here's a working JSFiddle. https://jsfiddle.net/reid_horton/rctx6o1g/
When you click the button, the text Hello from callerComponent! appears below it.
HTML
<div ng-app="myApp">
<caller-component></caller-component>
<callee-component><</callee-component>
</div>
JS
var app = angular.module("myApp", []);
app.component("callerComponent", {
template: "<button ng-click='externalCall()'>Click Me!</button>",
controller: function ($scope, $rootScope) {
$scope.externalCall = function () {
$rootScope.$emit("setText");
}
}
});
app.component("calleeComponent", {
template: "<p>{{ myText }}</p>",
controller: function ($scope, $rootScope) {
$rootScope.$on("setText", function () {
$scope.myText = "Hello from callerComponent!"
});
}
});
Related
So I am trying to list out some data from an API call, which is JSON. I am new to Angular and JavaScript and I think the whole Async thing is messing with my logic. Sometimes when I refresh the page, it works perfectly, all the data I expect to be there is, but then the next time I refresh its gone. This seems to happen when I change something in the controller.
var app = angular.module("hasRead", ['hasRead.controllers', 'hasRead.directives', 'hasRead.services', 'hasRead.filters','ngRoute']);
app.config(["$routeProvider", function ($routeProvider) {
$routeProvider
.when("/books/:shelf", {controller: "BooksCtrl", templateUrl: "partials/books_list.html"})
.when("/recomend", {controller: "RecomendCtrl", templateUrl: "partials/recomend.html"})
.otherwise({redirectTo: "/books/read"})
}]);
controller.js
var ctrls = angular.module('hasRead.controllers', []);
ctrls.controller('AppCtrl', function ($scope) {
});
ctrls.controller('BooksCtrl', function ($scope, $book, $routeParams) {
$scope.books;
var shelf = $routeParams.list;
$book.getBooks(shelf).then( function(data) {
$scope.books = data.query.results.body.goodreadsresponse.reviews;
console.log($scope.books);
$scope.$apply;
});
});
ctrls.controller('RecomendCtrl', function ($scope) {
$scope.test = "testing";
});
HTML
<div>
<book ng-repeat="review in books.review" review-data="review"></book>
</div>
Its a fair bit of code so here is a plunkr:
https://plnkr.co/edit/l3A6QF?p=info
I didn't have the parentheses in $scope.$apply(); in the controller.
I want to create a modal (dialog). I have followed examples on official bootstrap documentation but I stuck. When I am trying to create modal I receive an error
angular.min.js:122 Possibly unhandled rejection: {}
mainController:
angular
.module('app')
.controller('tlmController', function($scope, $http, $timeout, $uibModal, DTOptionsBuilder, DataLoader, TestLines) {
$scope.openTestLineDetails = function(id) {
var modalInstance = $uibModal.open({
size: 'lg',
controller: 'testlineDetailsController',
templateUrl: 'app/client/layout/testlinedetails.tpl.html',
resolve: {
testLineId: function() {
return id;
}
}
});
};
})
and TestlineDetailsController:
angular
.module('app')
.controller('testlineDetailsController', function($scope, $modalInstance, testLineId) {
});
What is wrong with this code? I am using $uibModal ($modal service does not exist) in main controller. When I replace $modalInstance by $uibModalInstance I receive an error too (service $uibModalInstance does not exist), so I have to use $uibModal with $modalInstance. Strage but true.
you can write below code in app.config
app.config(['$qProvider', function ($qProvider) {
$qProvider.errorOnUnhandledRejections(false);
}]);
First of all, check your modal controller script is appended to the main HTML file and if its appended(appears) in the browser (In Chrome, open web developer tools with F12 keyboard button then open the "Elements" tab button) (This is in case you are using some scaffolding tool like generator-angular from Yeoman's team, remember to clear cache in order to get the latest update from your code), because I had the same problem :( and I was reviewing constantly what was wrong with my code then I found out that the browser was not appending the latest script I made (Modal controller), so my code was like yours, but taking your code example:
<!-- In your index.html file, check for this script being appended in your browser -->
<script src="testlineDetailsController.js"></script>
//In your parent controller
angular
.module('app')
.controller('tlmController', function($scope, $http, $timeout, $uibModal, DTOptionsBuilder, DataLoader, TestLines) {
$scope.openTestLineDetails = function(id) {
var modalInstance = $uibModal.open({
size: 'lg',
controller: 'testlineDetailsController',
templateUrl: 'app/client/layout/testlinedetails.tpl.html',
resolve: {
testLineId: function() {
return id;
}
}
});
};
})
Secondly, make sure you are implementing at least one method from the modal instance service in the modal controller: EDIT: (This is optional, you can hide the modal using the backdrop property from the modal option object)
//In your modal controller
angular.module('app').
controller('testlineDetailsController', function ($scope, $uibModalInstance, testLineId) {
//In case you have the ok and cancel buttons in your modal template
$scope.id = testLineId;
$scope.ok = function () {
$uibModalInstance.close();
};
$scope.cancel = function () {
$uibModalInstance.dismiss('cancel');
};
});
After this, your app should be working.
Now, there is another alternative to get this issue solved, you can directly write the controller function in the property of the modal options object:
//In your parent controller
angular
.module('app')
.controller('tlmController', function($scope, $http, $timeout, $uibModal, DTOptionsBuilder, DataLoader, TestLines) {
$scope.openTestLineDetails = function(id) {
var modalInstance = $uibModal.open({
size: 'lg',
//write an anonymous function instead of naming the controller name.
controller: function ($scope, $uibModalInstance, testLineId) {
$scope.id = testLineId;
$scope.ok = function () {
$uibModalInstance.close();
};
$scope.cancel = function () {
$uibModalInstance.dismiss('cancel');
};
},
templateUrl: 'app/client/layout/testlinedetails.tpl.html',
resolve: {
testLineId: function() {
return id;
}
}
});
};
})
This alternative should work also in your app. So I hope this explanation helps you to solve the issue you have.
I have an app based on Angular which I initialize like this:
myapp.init = (function () {
'use strict';
var angularApp = angular.module('myapp', [])
.directive('homeIterationDirective', function () {
return function (scope, element, attrs) {
var isTopCard = scope.$last ? true : false;
cards.initSingleSwipe(element.get(0), function (event) {
// I want to call indexPageController.onSwiped(event) here!
}, isTopCard);
};
})
.directive('homeDirective', function () {
return function (scope, element, attrs) {
cards.initPanel(element, function (event) {
// I want to call indexPageController.onButtonPressed(event) here!
});
};
});
angularApp.factory('AjaxService', myapp.services.AjaxService);
angularApp.controller('IndexPageController', ['$scope', '$http', '$sce', 'AjaxService', myapp.pages.IndexPageController]);
}());
My controller looks like this:
myapp.pages.IndexPageController = function ($scope, $http, $sce, MyService) {
'use strict';
var somevalue = {};
this.onSwiped = function (event) {
doSomethingWith(event, somevalue);
};
this.onButtonPressed = function (event) {
doSomethingWith(event, somevalue);
};
};
In the 2 directives homeIterationDirective and homeDirective I have 2 callbacks cards.initSingleSwipe and cards.initPanel. Within these callbacks I want to call public methods of my controller but I don't have the instance available that Angular created from IndexPageController. How can I achieve this?
Use (inject) a service (and not a Controller) if you want "to call a public method" from another place, possibly from another Controller.
angularApp.controller('MyController', function ($scope, IndexPageService) {
IndexPageService.blah();
}));
Controller is intended to receive and modify a $scope (adding methods, variables..etc). The $scope can than be used inside the template (html) that use the controller itself.
If you want to call some controller code within the Angular context but in another place, then you should probably move that calling code to a service and then call the service method directly.
But if you want to call that method outside Angular context then, you can achieve that like this:
<div id="foo" ng-controller="IndexPageController">
<!-- code -->
</div>
Now, you can write something like this:
angular.element(document.getElementById("foo")).scope().blah();
I also think you should use a service for this. But if you still need to call one controller from another you can use $controller service
(function() {
var app = angular.module('myApp', ['ngRoute']);
app.config(function ($routeProvider) {
$routeProvider.when("/first", {
templateUrl: 'app/view.html',
controller: 'firstCtrl'
}).when("/second", {
templateUrl: 'app/view.html',
controller: 'secondCtrl'
})
.otherwise({
redirectTo: "/first"
});
});
app.controller('firstCtrl', function ($scope) {
$scope.name = "first Controller";
$scope.nameToUpper = function () {
return $scope.name.toUpperCase();
}
});
app.controller('secondCtrl', function ($scope, $controller) {
var newScope = $scope.$new();
$controller('firstCtrl', { $scope: newScope });
$scope.name = newScope.nameToUpper() + 'from second ctrl';
});
}())
and view is
<div>
{{name}}
</div>
I've ran into problem with ng-controller and 'resolve' functionality:
I have a controller that requires some dependency to be resolved before running, it works fine when I define it via ng-route:
Controller code looks like this:
angular.module('myApp')
.controller('MyController', ['$scope', 'data', function ($scope, data) {
$scope.data = data;
}
]
);
Routing:
...
.when('/someUrl', {
templateUrl : 'some.html',
controller : 'MyController',
resolve : {
data: ['Service', function (Service) {
return Service.getData();
}]
}
})
...
when I go to /someUrl, everything works.
But I need to use this controller in other way(I need both ways in different places):
<div ng-controller="MyController">*some html here*</div>
And, of course, it fails, because 'data' dependency wasn't resolved. Is there any way to inject dependency into controller when I use 'ng-controller' or I should give up and load data inside controller?
In the below, for the route resolve, we're resolving the promise and wrapping the return data in an object with a property. We then duplicate this structure in the wrapper service ('dataService') that we use for the ng-controller form.
The wrapper service also resolves the promise but does so internally, and updates a property on the object we've already returned to be consumed by the controller.
In the controller, you could probably put a watcher on this property if you wanted to delay some additional behaviours until after everything was resolved and the data was available.
Alternatively, I've demonstrated using a controller that 'wraps' another controller; once the promise from Service is resolved, it then passes its own $scope on to the wrapped controller as well as the now-resolved data from Service.
Note that I've used $timeout to provide a 1000ms delay on the promise return, to try and make it a little more clear what's happening and when.
angular.module('myApp', ['ngRoute'])
.config(function($routeProvider) {
$routeProvider
.when('/', {
template: '<h1>{{title}}</h1><p>{{blurb}}</p><div ng-controller="ResolveController">Using ng-controller: <strong>{{data.data}}</strong></div>',
controller: 'HomeController'
})
.when('/byResolve', {
template: '<h1>{{title}}</h1><p>{{blurb}}</p><p>Resolved: <strong>{{data.data}}</strong></p>',
controller: "ResolveController",
resolve: {
dataService: ['Service',
function(Service) {
// Here getData() returns a promise, so we can use .then.
// I'm wrapping the result in an object with property 'data', so we're returning an object
// which can be referenced, rather than a string which would only be by value.
// This mirrors what we return from dataService (which wraps Service), making it interchangeable.
return Service.getData().then(function(result) {
return {
data: result
};
});
}
]
}
})
.when('/byWrapperController', {
template: '<h1>Wrapped: {{title}}</h1><p>{{blurb}}</p><div ng-controller="WrapperController">Resolving and passing to a wrapper controller: <strong>{{data.data ? data.data : "Loading..."}}</strong></div>',
controller: 'WrapperController'
});
})
.controller('HomeController', function($scope) {
$scope.title = "ng-controller";
$scope.blurb = "Click 'By Resolve' above to trigger the next route and resolve.";
})
.controller('ResolveController', ['$scope', 'dataService',
function($scope, dataService) {
$scope.title = "Router and resolve";
$scope.blurb = "Click 'By ng-controller' above to trigger the original route and test ng-controller and the wrapper service, 'dataService'.";
$scope.data = dataService;
}
])
.controller('WrapperController', ['$scope', '$controller', 'Service',
function($scope, $controller, Service) {
$scope.title = "Resolving..."; //this controller could of course not show anything until after the resolve, but demo purposes...
Service.getData().then(function(result) {
$controller('ResolveController', {
$scope: $scope, //passing the same scope on through
dataService: {
data: result
}
});
});
}
])
.service('Service', ['$timeout',
function($timeout) {
return {
getData: function() {
//return a test promise
return $timeout(function() {
return "Data from Service!";
}, 1000);
}
};
}
])
// our wrapper service, that will resolve the promise internally and update a property on an object we can return (by reference)
.service('dataService', function(Service) {
// creating a return object with a data property, matching the structure we return from the router resolve
var _result = {
data: null
};
Service.getData().then(function(result) {
_result.data = result;
return result;
});
return _result;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular-route.min.js"></script>
<div ng-app="myApp">
By ng-controller |
By Resolve |
By Wrapper Controller
<div ng-view />
</div>
Create a new module inside which you have the service to inject like seen below.
var module = angular.module('myservice', []);
module.service('userService', function(Service){
return Service.getData();
});
Inject newly created service module inside your app module
angular.module('myApp')
.controller('MyController', ['$scope', 'myservice', function ($scope, myservice) {
$scope.data = data;
// now you can use new dependent service anywhere here.
}
]
);
You can use the mechanism of the prototype.
.when('/someUrl', {
template : '<div ng-controller="MyController" ng-template="some.html"></div>',
controller: function (data) {
var pr = this;
pr.data = data;
},
controllerAs: 'pr',
resolve : {
data: ['Service', function (Service) {
return Service.getData();
}]
}
})
angular.module('myApp')
.controller('MyController', ['$scope', function ($scope) {
$scope.data = $scope.pr.data; //magic
}
]
);
Now wherever you want to use
'<div ng-controller="MyController"></div>'
you need to ensure that there pr.data in the Scope of the calling controller. As an example uib-modal
var modalInstance = $modal.open({
animation: true,
templateUrl: 'modal.html',
resolve: {
data: ['Service', function (Service) {
return Service.getData();
}]
},
controller: function ($scope, $modalInstance, data) {
var pr = this;
pr.data = data;
pr.ok = function () {
$modalInstance.close();
};
},
controllerAs:'pr',
size:'sm'
});
modal.html
<script type="text/ng-template" id="modal.html">
<div class="modal-body">
<div ng-include="some.html" ng-controller="MyController"></div>
</div>
<div class="modal-footer">
<button class="btn btn-primary pull-right" type="button" ng-click="pr.ok()">{{ 'ok' | capitalize:'first'}}</button>
</div>
</script>
And now you can use $scope.data = $scope.pr.data; in MyController
pr.data is my style. You can rewrite the code without PR.
the basic principle of working with ng-controller described in this video https://egghead.io/lessons/angularjs-the-dot
Presuming that Service.getData() returns a promise, MyController can inject that Service as well. The issue is that you want to delay running the controller until the promise resolves. While the router does this for you, using the controller directly means that you have to build that logic.
angular.module('myApp')
.controller('MyController', ['$scope', 'Service', function ($scope, Service) {
$scope.data = {}; // default values for data
Service.getData().then(function(data){
// data is now resolved... do stuff with it
$scope.data = data;
});
}]
);
Now this works great when using the controller directly, but in your routing example, where you want to delay rendering a page until data is resolved, you are going to end up making two calls to Service.getData(). There are a few ways to work around this issue, like having Service.getData() return the same promise for all caller, or something like this might work to avoid the second call entirely:
angular.module('myApp')
.controller('MyController', ['$scope', '$q', 'Service', function ($scope, $q, Service) {
var dataPromise,
// data might be provided from router as an optional, forth param
maybeData = arguments[3]; // have not tried this before
$scope.data = {}; //default values
// if maybeData is available, convert it to a promise, if not,
// get a promise for fetching the data
dataPromise = !!maybeData?$q.when(maybeData):Service.getData();
dataPromise.then(function(data){
// data is now resolved... do stuff with it
$scope.data = data;
});
}]
);
I was trying to solve the problem using ng-init but came across the following warnings on angularjs.org
The only appropriate use of ngInit is for aliasing special properties
of ngRepeat, as seen in the demo below. Besides this case, you should
use controllers rather than ngInit to initialize values on a scope.
So I started searching for something like ng-resolve and came across the following thread:
https://github.com/angular/angular.js/issues/2092
The above link consists of a demo fiddle that have ng-resolve like functionality. I think ng-resolve can become a feature in the future versions of angular 1.x. For now we can work around with the directive mentioned in the above link.
'data' from route resolve will not be available for injection to a controller activated other than route provider. it will be available only to the view configured in the route provider.
if you want the data to the controller activated directly other than routeprovider activation, you need to put a hack for it.
see if this link helps for it:
http://www.johnpapa.net/route-resolve-and-controller-activate-in-angularjs/
Getting data in "resolve" attribute is the functionality of route (routeProvider) , not the functionality of controller.
Key( is your case : 'data') in resolve attribute is injected as service.
That's why we are able fetch data from that service.
But to use same controller in different place , you have fetch data in controller.
Try this
Service:
(function() {
var myService = function($http) {
var getData = function() {
//return your result
};
return {
getData:getData
};
};
var myApp = angular.module("myApp");
myApp.factory("myService", myService);
}());
Controller:
(function () {
var myApp = angular.module("myApp");
myApp.controller('MyController', [
'$scope', 'myService', function($scope, myService) {
$scope.data = myService.getData();
}
]);
//Routing
.when('/someUrl', {
templateUrl : 'some.html',
controller : 'MyController',
resolve : {
data: $scope.data,
}
})
}());
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