Change Binding in main window after being called with $routeProvider - angularjs

I call a my controller with $routeProvider like this:
$routeProvider
.when('/home', {
templateUrl: "partials/main.html",
controller: "AppCtrl"
});
So the focus of $scope in the controller will change only the content of partials/main.html.
I would need to change {{status}} in index.html, but it won't change if I do $scope.status = 'ready'; in the controller, it will change only if it was in main.html.
How can I make this work ?
Thank you very much.

The scope associated with AppCtrl should prototypically inherit from the scope associated with the controller you have defined in index.html. Define a method on that controller, and then you can call it from your child controller/scope (because of the way JavaScript prototypal inheritance works):
function MainCtrl($scope) {
$scope.updateStatus = function(newStatus) {
$scope.status = newStatus;
}
}
function AppCtrl($scope) {
...
$scope.updateStatus(....);
}
Fiddle. In the fiddle, I don't use ng-view, but it still shows the concept.

Related

Unable to bind $scope value to view on Url change

I trying to create a search application using angularJS.I am facing the issue in binding $scope values to view when router Url changes.
I have a search field in /main.When I write the query and click on search button, the function does the data fetch and assign to a scope variable.The router URL will change to '/Result' and the respective view is displayed.But the view doesn't have the scope values bound. /main and /Result uses the same controller.
router code in main module :
$routeProvider.
when('/main', {
templateUrl: '/views/search.html',
controller: 'searchCtrl'
}).when('/Result',{
templateUrl:'/views/Result.html',
controller: 'searchCtrl' ,
reloadOnSearch: false
}).otherwise({
redirectTo: '/main'
});
Controller :
On button click from /main
$scope.fetchResult=function(searchWord){
shService.fetchResultDocumentsList(searchWord).data.then(function(response){
//service call here-data fetch is successfull.
$scope.docResultList=response[0];
$scope.docResultList=response[0];
$scope.documents = $scope.docResultList.data.documentEntities;
$location.path('/Result');
}
When the respective view is changing, the binding is not done.But when i replace the $scope.documents with $rootScope.documents binding is successful.
I have read the use of $scope is encouraged over $rootScope.
The controller and $scope gets re initialized when you move from one page to another page. if you want to use $scope , you should consider using service to share the data across controllers.
Create a service, that will hold your variable.
angular.service("dataService", function() {
this.value1 = ""
});
reference that service in your controllers,
angular.controller("myCntrl1", function($scope, dataService) {
$scope.val= dataService.value1 ;
});
angular.controller("myCntrl2", function($scope, dataService) {
$scope.val= dataService.value1 ;
});
As Sajeetharan said, $scope get reinitialized when you update location.
Using angularJs, you don't need to change location. You simply update $scope in the same view you used for searching.
But if you really need to use another view, and assuming your search returns only strings, you could try to pass data through url, and grab it from your controller.
Something like this (not tested):
Solution 1 (Angular way)
Controller
$scope.documents = ""; // init your results to empty/null, as you need
$scope.fetchResult=function(searchWord){
shService.fetchResultDocumentsList(searchWord).data.then(function(response){
//service call here-data fetch is successfull.
$scope.docResultList=response[0];
$scope.docResultList=response[0];
$scope.documents = $scope.docResultList.data.documentEntities;
$location.path('/Result');
}
View
<!-- search div is over here -->
<!-- results goes here -->
<div ng-if="$scope.documents">
{{$scope.documents}}
</div>
Solution 2 (your way)
Router
$routeProvider.
when('/main', {
templateUrl: '/views/search.html',
controller: 'searchCtrl'})
.when('/Result/{data:[a-z]{1,}}',{ //here we specify that data expected format is only lowercase letters
templateUrl:'/views/Result.html',
controller: 'searchCtrl' ,
reloadOnSearch: false
})
.otherwise({
redirectTo: '/main'
});
Controller
// dont forget to inject $stateParams service in your controller
if( !$stateParams.data){
$scope.data = $stateParams.data;
}

Angular: injecting state into controllers (or binding "models" to controllers)

Suppose I have a general purpose controller, TableController, that can be used in multiple places in the app to display a table of Key x Value pairs via a custom directive, ui-table, that generates an HTML table.
angular.module('ui.table', [])
.controller('TableController', ['$scope', 'data',
function($scope, data) { $scope.data = data; }])
.directive('uiTable', function() {
return { templateUrl: 'table.html' }
});
I could use the controller in the following template:
<div ng:controller="TableController">
<div ui-table></div>
</div>
And create a factory to pass data to this controller.
.factory('data', function() {
return [{'App':'Demo', 'Version':'0.0.1'}];
})
But, I have multiple controllers (sometimes in the same views), so I need to "bind" a particular factory to a particular controller (e.g., UserProfile, AppData, etc.)
I have started to look at angular-ui-router's $stateProvider, but it seems too complicated for what must be a typical use case? What I'd really like to be able to do is use the template to annotate which factory (what I think of as a model) should be used for that particular controller. E.g., something like:
<div ng:controller="TableController" ng:model="AppData"></div>
What is the right approach?
EDIT:
I've figured out $stateProvider and "resolve" allows me to map provider services onto injected values for the state's main controller -- but the controller I want to influence is a child of this controller.
$stateProvider
.state('home', {
url: '/home',
templateUrl: '/home/view.html',
controller: 'MainViewController',
resolve: {
'data': 'AppData'
}
});
So, I still can't figure out how to influence the controllers inside the state's view.
I think what you are looking for is simply passing your data into the directive through attributes. Then use an isolated scope in directive so you can have multiple instances active at the same time
<div ng-controller="ViewController">
<div ui-table dataSource="tableData"></div>
</div>
Then your directive would be written in a generic way to be re-usable regardless of the data passed in.
.factory('SomeService', function(){
var data ={
headings: ['ID','Age','Name'],
rows:[
{id:1, 33,'Bill'}......
]
};
return {
get:function(){ return data;}
};
})
.controller('ViewController', function($scope, SomeService){
$scope.tableData = SomeService.get();
})
.directive.directive('uiTable', function () {
return {
scope: {
dataSource: '=' // isolated scope, 2 way binding
}
templateUrl: 'table.html',
controller: 'TableController', // this controller can be injected in children directives using `require`
}
});
In essence this is just reversing your layout of controller/directive. Instead of TableController wrapping the directive, it is used internally within directive. The only reason it is a controller in the directive is to allow it to be require'd by child directives such as perhaps row directive or headings directive and even cell directive. Otherwise if not needing to expose it for injection you can use link and put all sorts of table specific operations in there
As mentioned in my comments there are various approaches to creating a table directive. One is with heavy configuration objects, the other is with a lots of declarative view html that use many child directives. I would suggest analyzing the source of several different grid/table modules to see what best suits your coding style
Thanks in part to #charlietfl (above) I have an answer:
<ui-graph model="SomeGraphModel"></ui-graph>
Then:
angular.module('ui.graph', [])
.directive('uiGraph', [function() {
return {
controller: 'GraphController',
scope: {
model: '#model' // Bind the element's attribute to the scope.
}
}
}]);
.controller('GraphController', ['$injector', '$scope', '$element',
function($injector, $scope, $element) {
// Use the directive's attribute to inject the model.
var model = $scope.model && $injector.get($scope.model);
$scope.graph = new GraphControl($element).setModel(model);
}])
Then somewhere else in the app (i.e., not necessarily in the directive/controller's module):
angular.module('main', [])
.factory('SomeGraphModel', function() {
return new GraphModel();
})

AngularJS: Pass functions and variables to directive's

I have one situation where I have to pass the directive, functions and variables declared in the controller. I can easily do this with $scope. But I read one article which states that we should not populate the scope instead use this. The article had the following example -
//Don't do this
app.controller('MyCtrl', function($scope){
$scope.name = 'Techno Fattie';
});
//Do this
var MyCtrl = function(){
this.name = 'Techno Fattie';
};
app.controller('MyCtrl', MyCtrl);
I liked the idea and I tried implementing the same in my situation which is as follows -
I have a CountryController - which I modified to use this instead of $scope.
I have a countryList.tpl.html - which has only a directive in it and no other code.
Now I have a parent controller which has a stateProvider where I have configuration for country. something like this -
.state('app.country', {
url: "/country",
templateUrl: "countryList.tpl.html",
controller: CountryController
})
I converted the controller's $scope to this but then I observed that I the directive is not receiving the function and variables and the page is not loading properly.
Is it that if the tpl file has a directive then this approach is not useful? If that is not true, then how can we do this? Can anyone help?

AngularJS $location not updated properly when using $routeProvider

I have an Angular JS application with a defaultController which controls the header of the app. Then I have some other controllers one for each view. The views are loaded in the <main>. I load the views using the $routeProvider with this code:
myapp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/login', {
templateUrl: 'templates/login.html',
controller: 'loginController'
}).
when('/dashboard', {
templateUrl: 'templates/dashboard.html',
controller: 'dashboardController'
}).
...
I am trying to display a LOGOUT button inside the header when the loaded view is the dashboard and hide it if the loaded view is the login view. In order to do that I have on the defaultController the $location object and I properly add and remove classes from the LOGOUT button with ng-class.
There is only one problem: $location gives me the correct path the first time I load the page, but after I change the view (changed by the $routeProvider) that variable is not updated anymore, so when I am actually on /#/dashboard , the $location.url is still on /login. Here the controller code:
controllers.controller('defaultController', ['$scope', 'ipCookie', '$location', function($scope, ipCookie, $location) {
$scope.url = $location.url();
...
I also tried with $window.location.hash with the same result.
Any idea?
EDIT: after the accepted answer this is what I ve added on the defaultController in order to make it work
$scope.$on("$locationChangeSuccess", function() {
$scope.url = $location.url();
});
The location is probably updated in the service after your default controller is loaded.
You can either inject the $location service into the scope and make decisions in your template based on it (then it will automatically be watched and re-evaluated) or you could listen for the $locationChangeSuccess event.
When injecting, you can simply $scope.location = $location and then use something like <a ng-hide="location.path() != '/something'">.
$location broadcasts the $locationChangeSuccess on the root scope, so you should be able to listen for it on whichever scope you have available: $scope.$on( "$locationChangeSuccess", function() { /* do something */ } );

Invoke function between two controllers while using routeprovider

I am using route provider as follows,
var appModule = angular.module('ngLogin', ['ngRoute','restangular','btford.socket-io','ngSanitize','xeditable']);
appModule.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/home', {
templateUrl: 'sample/homepage.html',
controller: 'ngHomeControl'
}).
when('/contacts', {
templateUrl: 'sample/homepage.html',
controller: 'ngContactControl'
});
}]);
Here I need to call function from ngHomeControl to ngContactControl.
I tried as follows, but the function didn't invoked.
appModule.controller('ngHomeControl', function($scope,$routeParams,socket,Restangular,$http) {
$rootScope.$broadcast('getFriendList',{"userName":userName});
});
appModule.controller('ngContactControl', function($scope,$routeParams,$rootScope,socket,sharedProperties,Restangular,$http,$timeout) {
$scope.$on("getFriendList",function(event,data)
{
console.log('getFriendList');
});
});
Can anyone help me to resolve?
This will not work as only one controller is instantiated at a time (in your case).
A proper way would be to use a service. There is a nice article that wil help you with this.
See also this answer on how to create a service.
Based on those two resources you should came up with something similar to this:
var appModule = angular.module('appModule', ['ngRoute']);
appModule.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/home', {
templateUrl: 'home.html',
controller: 'ngHomeControl'
}).
when('/contacts', {
templateUrl: 'contacts.html',
controller: 'ngContactControl'
});
}]);
appModule.service('friendsService', function(){
this.getFriendList = function () {
return ['John', 'James', 'Jake'];
}
});
appModule.controller('ngHomeControl', function($scope, friendsService) {
$scope.homeFriends = friendsService.getFriendList();
});
appModule.controller('ngContactControl', function($scope, friendsService) {
$scope.contactFriends = friendsService.getFriendList();
});
There is a complete working JSFiddle so you can test it out.
Please also checkout the console output to see when used components are instantiated.
You will see that controllers are instantiated each time the route changes - they are instantiated implicitly via the ngController directive used inside templates. The service is instantiated only once and this is at the time when it is needed/injected for the first time.
The reason your event listener isn't fired is because there isn't a ngContactControl instance alive when your at /home. You can create a parent controller, which handles the scope events but a better way is to use a service that is shared among the controllers that need this functionality.
See this plunker for an example how to share data and/or functions via a service.

Resources