view controller $scope.$on "watch" function not working...

1,697 views
Skip to first unread message

Billy Figueroa

unread,
Apr 17, 2014, 9:53:56 PM4/17/14
to ang...@googlegroups.com
Hi All,

so I have a quick issue with my controller ordering I guess.

I have the following shell page layout...

<html>
   <head></head>
   <body ng-controller="MainController">
      <div ng-view></div>
   </body>
</html>

I have an ng-view template that the routeProvider is loading (account.php) when we hit the url and load ClientController...

    .when('/client/:user_id/account', {
        templateUrl: '../MINT/views/account.php',
        controller: 'ClientController',
        restrict: true
    })


I also have an AuthFacotory factory where, once a user logs in and I get data back from my backend (PHP) I broadcast a signal to say we are authenticated

                    if (phpData.account_type === 'client')
                        $rootScope.$broadcast('clientAuthenticated', phpData);
                    else
                        $rootScope.$broadcast('providerAuthenticated', phpData);


I have the following I guess "watches" both inside the MainController and ClientController


<-- MainController -->
    $scope.$on('clientAuthenticated', function(phpData) {
        console.log("We caught the broadcast for clientAuthenticated[MainController]");
    });


<-- ClientController -->

    $scope.$on('clientAuthenticated', function(phpData) {
        console.log("We caught the broadcast for clientAuthenticated[ClientController]");
    });



I am only "catching" the one in the MainController.

Based on the structure being that the ClientController is loaded as part of the view, I m guessing its some sort of a child of the MainController but why is it not "catching" that broadcast?

the weird part is that I have other code in the ClientController and that gets executed but the console.log inside the scope statement is not

I wanted to create a jsfiddle or plunker but I rarely get them working when I try to do it in a non global modular way like real apps are written
i.e. var myApp = angular.module('myApp', [])

Luke Kende

unread,
Apr 18, 2014, 1:45:29 AM4/18/14
to ang...@googlegroups.com
This could be a timing thing.  Are you sure your listener ($scope.$on) in client controller is loaded at the time of the broadcast? Another console.log just before the registration may help you see if that's the case.  I don't believe that main as parent would override the child controllers handler, but I have had it happen in one of my directives....   you can verify by commenting out the one in main to double check.

Billy Figueroa

unread,
Apr 18, 2014, 1:42:31 PM4/18/14
to ang...@googlegroups.com
Hey what do you mean "is loaded at the time of the broadcast'

I know the controller is active becasue I have some json files I am loading for drop down menus and those are all loaded, but we never go into the $scope.$on code to the console.log statement.

I am trying to recreate this and this is why I dislike plunker and jsfiddle, they always give me problems..

why is this not working...

http://plnkr.co/edit/h3a6ZwkUF2vCy36gEPUe?p=preview

I can't get the AuthFactory to work. I tried to add a button to console.log when I click but it doesnt do anything

Luke Kende

unread,
Apr 18, 2014, 4:39:41 PM4/18/14
to ang...@googlegroups.com
Well, without seeing the code, it was an option that the broadcast was happening before the child controller had registered the listener.  Now that I see the code, that is not the case.

So I added the factory login method on the child scope.  Now clicking the button fires the broadcast and both listeners fire as well:




--
You received this message because you are subscribed to a topic in the Google Groups "AngularJS" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/angular/NaBscLyPqhY/unsubscribe.
To unsubscribe from this group and all its topics, send an email to angular+u...@googlegroups.com.
To post to this group, send email to ang...@googlegroups.com.
Visit this group at http://groups.google.com/group/angular.
For more options, visit https://groups.google.com/d/optout.

Billy Figueroa

unread,
Apr 18, 2014, 5:12:55 PM4/18/14
to ang...@googlegroups.com
Luke I think you just helped me realize what is going on. This entire issue I believe comes from the fact that I have a login drop down menu which can be used from ANY page using the index.html "shell" page

By default the "MainController" is on every page and it seems the login functions which I currently have in MainController SHOULD BE in ClientController. The problem is that the way I have the HTML setup, ClientController is LOADED upton hitting a certain route. I have been contemplating moving the login form to its own page. I think doing this will solve my issue. I was already having issues with this set up before but I like the easyness of having a dropdown from anypage as opposed to a seperate page

To simplify things, since this is my first angularjs app, I will remove the dropdown

Thanks, I think this will solve the issue...I HOPE!

Luke Kende

unread,
Apr 18, 2014, 5:30:18 PM4/18/14
to ang...@googlegroups.com
Billy,

Yes starting into angular really changes the way we think.  Timing is one of the main challenges I've run into (if you haven't hit that issue yet, you will), in addition to organization of code.  Angular provides an awesome framework for structuring your app, but it does not provide a standard way to go about achieving the result you are after.  It's great that it's flexible, but also challenging to understand how the components fit together. 

By creating your login method in a factory or service, you can inject it into any controller, and since it is using rootScope to broadcast, really any controller can add a listener to react to it.  Once again, it's up to you how to structure views and controllers.  Having the login come into the client controller is fine since any controller (main or child or other) can add a listener and respond.  

Good luck.  

Luke

Billy Figueroa

unread,
Apr 18, 2014, 5:57:21 PM4/18/14
to ang...@googlegroups.com
Yes, I think I can get this to work but it has caused me a few issues already. I ma try to stick to the basic 1 controller 1 view for now.  Once I have learned angular better I ll learn how to "bend" it when I need to

There is off course an "alternative" to creating a new page and that is pull all the data I need on the MainController which I did not want to sort of do because then I will have Client and Provider data on that controller. It's not like "polluting" the rootScope, but it would be having data from more than one controller in one place which is not best practice.

Billy Figueroa

unread,
Apr 20, 2014, 9:24:08 PM4/20/14
to ang...@googlegroups.com
Ok so this issue has not been resolved. I am trying to avoid putting the logic in my main controller. I did notice this has to be one of the "time" issues you mentioned luke.

I m guessing the MainController receives the broadcast because it is some how already loaded and ready to go, but my client controller somehow is not?

I tried one thing and noticed it is definately a timing issue. I put a $timeout call for 1 second and I noticed that if I do that it hits the broadcast. How can I deal with these timing issues? I tried to change my logic from using jquery success callbacks to using the promise then instead

ANYONE who can help can answer this, not just Luke. I addressed him because hes been helping. Thanks Luke!

Raul Vieira

unread,
Apr 20, 2014, 9:29:49 PM4/20/14
to ang...@googlegroups.com
When does the auth happen relative to the client controller being constructed?

--
You received this message because you are subscribed to the Google Groups "AngularJS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to angular+u...@googlegroups.com.

Billy Figueroa

unread,
Apr 20, 2014, 9:52:51 PM4/20/14
to ang...@googlegroups.com
So the order is as follows

> someone goes to /login at which point loginController is loaded...

'use strict';

mintApp.controller('LoginController', ["$scope", "$rootScope", "$http", "$location", "AuthFactory", function ($scope, $rootScope, $http, $location, AuthFactory) {

    $scope.errors = [];
    $scope.user   = {};

    // Initialize form data to defaults
    $scope.loginData = {
        username : "Username",
        password : "Password"
    };

    $scope.displayErrors = 0;

    $scope.login = function () {
        AuthFactory.login($scope.loginData).then(function (phpData) {

            console.log(phpData);
            if (phpData.status === 'Success') {
                $scope.user.loggedIn = AuthFactory.user.isLoggedIn;
                $location.path('/client/' + phpData.user_id +'/account');
            }
            else {
                $scope.errors.splice(0, $scope.errors.length);
                $scope.displayErrors = 1;
                for (var i = 0; i < phpData.length; i++)
                    $scope.errors.push(phpData[i]);
            }

            return false;
        });

        return false;
    };

}]);

Then someone can submit the form loaded in the login view. When they submit the form, the login function in the loginController above makes a call to AuthFactory which is below...

'use strict';

mintApp.factory("AuthFactory", ["$http", "$rootScope", "$timeout", function($http, $rootScope, $timeout) {
    var factory = {};

    factory.user = {
        isLoggedIn : false
    };

    factory.login = function (loginData) {
        return $http.post('../MINT/scripts/php/validate_user', loginData).then(function (results) {
                // results is an object with the following attributes etc
                //      config object   - Config object with headers 
                //      data object     - This is what I have passed back from my script (array/ object)
                //      status          - an HTTP status code (i.e. 200, 400 etc)
                var phpData = results.data;

                // Set factory.user.isLoggedIn, only if what returned an object and
                // not an array
                if (!angular.isArray(phpData)) {
                    if (phpData.account_type === 'client') {
                        // $timeout(function() {
                            broadcast('clientAuthenticated', phpData);
                        // }, 1000);
                        return phpData;
                    }
                    else {
                        console.log('Provider Section');
                        $timeout(function() {
                            broadcast('providerAuthenticated', phpData);
                        }, 1000);
                        return phpData;
                    }
                }
        });
    };

    var broadcast = function(event, data) {
        factory.user.isLoggedIn = true;
        $rootScope.$broadcast(event, data);
    };

    return factory;
}]);


which the hits the backend and if we get back an array we have errors.  If it's not an array we send a broadcast saying the client is authenticated then we go back to loginController where we check if we got back "Sucess" before I redirect the user to his account page /client/<userid>/account where I will display all the users info.

here is my routing for these pages...

'use strict';

var mintApp = angular.module('mintApp', ['ngRoute', 'bfLink', 'bfInput']);

mintApp.config(function ($routeProvider) {
    $routeProvider
    .when('/', {
        templateUrl: '../MINT/views/main.php'
    })
    .when('/login', {
        templateUrl: '../MINT/views/login.php',
        controller:  'LoginController'
    })
    .when('/client/signup', {
        templateUrl: '../MINT/views/client-membership.php',
        controller:  'ClientController'
    })
    .when('/provider/signup', {
        templateUrl: '../MINT/views/service-provider-membership.php',
        controller:  'ProviderController'
    })
    .when('/registered', {
        templateUrl: '../MINT/views/registered.php',
        controller:  'MainController'
    })
    .when('/:email/activate/:activationId', {
        templateUrl: '../MINT/views/activate.php',
        controller: 'ActivateController'
    })

    .when('/client/:user_id/account', {
        templateUrl: '../MINT/views/account.php',
        controller: 'ClientController',
        restrict: true
    })
    .when('/user/:userId/edit', {
        templateUrl: '../MINT/views/main.php',
        controller: 'MainController',
        restrict: true
    })
    .otherwise({redirectTo: '/'});
});

mintApp.run(['$rootScope', '$log', '$location', 'AuthFactory', function($rootScope, $log, $location, AuthFactory) {
    $rootScope.$log = $log;

    // Protecting member pages
    $rootScope.$on("$routeChangeStart", function (event, next, current) {
        if (next && next.$$route && next.$$route.restrict) {
            console.log("We hit a protected route");
            console.log("AuthFactory.user.isLoggedIn = " + AuthFactory.user.isLoggedIn);
            if (!AuthFactory.user.isLoggedIn)
                $location.path('/');
        }
    });
}]);


Let me know if I am missing anything please so I can provide it for you

Billy Figueroa

unread,
Apr 20, 2014, 9:54:43 PM4/20/14
to ang...@googlegroups.com
FIY ignore the time functions. I was only using those to see if the broadcast worked. This is not part of my original code


On Thursday, April 17, 2014 9:53:56 PM UTC-4, Billy Figueroa wrote:

Luke Kende

unread,
Apr 20, 2014, 11:02:00 PM4/20/14
to ang...@googlegroups.com
So, here's what I am understanding about your order of events, simplest scenario...

1. User comes to home page and is not logged in therefore sees Login form.
2. User enters credentials and clicks login, which let's say is successful, so factory method sets it's local isLoggedIn value to true and broadcasts the event.
3.  At this point in time, the MainController and the LoginController are still active controllers on the page.  MainController gets the broadcast (but not sure why, maybe to change the state of the menu navigation?).  LoginController runs the promise.then() function which sets it's own state variable for isLoggedIn (but not sure why here either as the view is about to change), then redirects the angular route so the new view is loaded with the ClientController to show the account info.

Why does the ClientController need the broadcast?  To get the phpData?  If this is true, I'd say put the object data on the factory instance and by injected it into the ClientController you will have access to the user's logged in data (or anywhere throughout the app).

factory.user = {
        isLoggedIn : false,
        data: null
    };

// in $http promise.then()
if (phpData.account_type === 'client') {
    // $timeout(function() {
    broadcast('clientAuthenticated', phpData);
    factory.user.data = phpData;
   // }, 1000);
   return phpData;
}


--
You received this message because you are subscribed to a topic in the Google Groups "AngularJS" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/angular/NaBscLyPqhY/unsubscribe.
To unsubscribe from this group and all its topics, send an email to angular+u...@googlegroups.com.

Billy Figueroa

unread,
Apr 20, 2014, 11:49:41 PM4/20/14
to ang...@googlegroups.com
I have not decided yet of how I am going to handle updating the user status button to login or logout. Not sure what controller I want to put it in yet so that is why you see multiple controllers with a "user.isLoggedIn" attribute, so just ignore that.

As far as why not grab the data from the auth service...

This is my Auth service which should only handle login/ logout etc The broadcasting is so I can put listeners on controllers who will then grab data from my "userData" or "dataFactory". This service will grab all the data I need from from the backend and then I can grab the data from both my "client" and "provider" controllers.

something like this inside the client controller

$scope.$on('clientAuthenticated', function(phpData) {
    $scope.clientData = userData.getData()
});

Luke Kende

unread,
Apr 21, 2014, 12:03:16 AM4/21/14
to ang...@googlegroups.com
The problem is that a controller specified for a route template only exists in memory at the time of the route match.  As soon as you leave and come back to a route/view, no state is maintained on the controller, the view runs the controller function all over again.  This is probably why you are confused about ClientController not getting the $broadcast event... it must be the active route/view to be listening.

You will have to store the user data in a service and inject it (or use rootScope or MainCtrl scope since those don't get removed).   Hence, you are rebuilding your scope each time you load the route: /client/:user_id/account

function clientController($scope, AuthFactory, DataService){
  $scope.userData = DataService.userData; //by putting object data on scope, any update to DataService.userData from other controller automatically binds to the service and therefor the loaded view template
}

Billy Figueroa

unread,
Apr 21, 2014, 12:21:24 AM4/21/14
to ang...@googlegroups.com
Hey Luke,

remember the way I had this set up MainController was on the body of the "shell" page. When I am on the /client/:user_Id/account route it is mapped to the clientController and it still doesnt register the $on event. The only time I saw it hit is when I put a $timeout function around it and delayed it for a second. Have an idea why?

I am trying to follow best practice by seperating things into their own controllers but I might have to put it on the main controller. I am trying to avoid doing that.

By the way are we allowed to communicate outside of the group? Maybe we can exchange emails
Reply all
Reply to author
Forward
0 new messages