1

I'm trying to create a simple dashboard that mimics the functionality shown here. I'd like to stick within an angular construct, but I've successfully confused myself into oblivion.I'm pretty new to Angular in general.

I am creating a simple dashboard based off of data from the Github API. I would like to reference the following data sources (where user is the value from the search bar and repo is the value clicked on from a repo list -- see linked example in 1st paragraph):

"https://api.github.com/users/" + user                          ----> returns general user info
"https://api.github.com/users/" + user + "/repos"               ----> used for repo list
"https://api.github.com/repos/" + user + "/" + repo + "/events" ----> list of events per repo

Essentially, the app is supposed to work the following way:

  1. The user types in the Github username in the search bar.

  2. An API call is made to return the user information and repo list (the first two urls I listed)

So far, I have this working.

  1. THEN, based on the first selected repo in the returned dropdown list OR the selected value, the 3rd url will be called to return more data.

As far as I can tell, I need to incorporate Angular promises, since my 3rd Get request is not being recognized.

Can someone help me restructure my app.js code to ensure that: - I have a set "repo" on page render (i.e. the 1st listed repo will be the default selected) - The events api is called again after user interaction with the repo list

I was trying to follow something as explained here but I was a little confused at how to incorporate the username and selected repo. If someone could walk me through how I could add in those parameters (specified by the user) in my code, I would really appreciate it!

Here is my current code, for reference:

app.js

angular.module('myApp', ['ui.router'])
    .controller('DashboardCtrl', function($scope, $state, $http){
        // Set search model to 'mbostock' and the fetch function to contact the
        // remote API and ensure the view is initialized. Load results when the search box changes.
        $scope.$watch('search', function() {
            initialFetch();
        });
        $scope.search = "mbostock";


    // Make calls to the API for Users and Repo List
    function initialFetch(){
        $http.get("https://api.github.com/users/" + $scope.search)
            .then(function(response){ $scope.userinfo = response.data; });

        $http.get("https://api.github.com/users/" + $scope.search + "/repos")
            .then(
                function(response){ $scope.repolist = response.data;

                // Create call for events listing based on repo choice
                var repo = "";

                // console.log(document.getElementById("repo1").value);

                $(function() {
                    //For showing default url
                    MakeUrl();
                    // On repository selection, call events
                    $('#repo-select').on('change', function () {
                        if ($(this).val() == 0) {
                            repo = document.getElementById("repo1").value;
                        } else {
                            repo = $(this).val();
                        }
                        MakeUrl();
                        return false;
                    });

                });

                function MakeUrl() {
                    var finalUrl = "https://api.github.com/repos/" + $scope.search + "/" + repo + "/events";
                    console.log(finalUrl);
                    $http.get(finalUrl)
                        .then(function (response) { $scope.eventinfo = response.data; });
                }


                });
    }


    // Function select which ensures that the entire
    // text is selected when the user clicks in the text input.
    $scope.select = function(){
        this.setSelectionRange(0, this.value.length);
    }
})

index.html

<body>
<div class="container-fluid outerdiv" ng-app="myApp" ng-controller="DashboardCtrl">

    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container-fluid">
            <div class="navbar-header">
                <a class="navbar-brand"><b>Github User Information</b> <span class="span-style"></span></a>
            </div>

            <div class="input-group search-bar">
                <input type="text" ng-model="search" ng-model-options="{ debounce: 800 }" onclick="select()" class="form-control" placeholder="Enter Github user login" autofocus />
                <span class="input-group-addon bar-style"><i class="glyphicon glyphicon-search"></i></span>
            </div>
        </div>
    </nav>

    <noscript>
        <div class="nojs">Javascript is either disabled or not supported in your browser. Please enable it or use a Javascript enabled browser.</div>
    </noscript>

    <div class="animated zoomInRight">


        <div id="user-bio" class="col-sm-4 col-md-4">
            <div>
                <div class="avatar">
                    <img src="{{ userinfo.avatar_url }}" class="thumbnail animated flip movie-poster">
                </div>

                <span class="span-outer">
                    <a href="{{userinfo.html_url}}" target="_blank">{{ userinfo.login }}</a>
                </span><br>{{ userinfo.name }}

                <p><strong>Joined:</strong><br> {{ userinfo.created_at }}</p>
                <p><strong>Last Updated:</strong><br> {{ userinfo.updated_at }}</p>

                <p>{{ userinfo.bio }}</p>

                <p class="outer-p">
                <div class="inner-p">
                    <span class="label label-primary">Public Repos :</span> {{ userinfo.public_repos }}
                </div>
                <div class="inner-p">
                    <span class="label label-primary">Followers :</span> {{ userinfo.followers }}
                </div>
                <div class="inner-p">
                    <span class="label label-primary">Following :</span> {{ userinfo.following }}
                </div>
                </p>

            </div>

            <div ng-if="userinfo.message==='Not Found'">
                No results found.
            </div>
        </div>

        <div class="col-sm-8 col-md-8">
            <h5><strong>Repositories:</strong></h5>
            <select id="repo-select">
                <option ng-repeat="repo in repolist" id="repo{{ $index + 1 }}" value="{{ repo.name }}" onchange="MakeUrl();">{{ repo.name }}</option>
            </select>

            <h5><strong>Events:</strong></h5>
            <ul class="event-results" id="event-select" style="height:400px; overflow-y:auto;">
                <li ng-repeat="event in eventinfo">
                    <a id="{{ $index + 1 }}" value="{{ event.type }}">{{ event.type }}
                    </a>, {{ event.created_at }} <!--ng-click="update(movie)"-->
                </li>
            </ul>
        </div>

    </div>
</div>

</body>

EDIT Here is the error I'm seeing -- again, they seem to indicate I need to implement promises. Then again, I'm not sure why I can't specify a default selected repo.

Possibly unhandled rejection: {"data":{"message":"Not Found","documentation_url":"https://developer.github.com/v3"},"status":404,"config":{"method":"GET","transformRequest":[null],"transformResponse":[null],"jsonpCallbackParam":"callback","url":"https://api.github.com/repos/mbostock//events","headers":{"Accept":"application/json, text/plain, /"}},"statusText":"Not Found"}

UPDATE AND EDIT Via @mikwat 's suggestion, I tried using ng-model to bind the repo variable.

My new app.js file looks like this:

angular.module('myApp', ['ui.router'])
    .controller('DashboardCtrl', function($scope, $state, $http, DataService){
        // Set search model to 'mbostock' and the fetch function to contact the
        // remote API and ensure the view is initialized. Load results when the search box changes.
        $scope.$watch('search', function() {
            initialFetch();
                // .then(MakeUrl);
        });

        var user = $scope.search;
        $scope.search = "mbostock";
        $scope.repo = "array-source";


        // Make calls to the API for Users and Repo List
        function initialFetch(){
            $http.get("https://api.github.com/users/" + $scope.search)
                .then(function(response){ $scope.userinfo = response.data; });

            $http.get("https://api.github.com/users/" + $scope.search + "/repos")
                .then(
                    function(response){ $scope.repolist = response.data; },
                    $http.get("https://api.github.com/repos/" + $scope.search + "/" + $scope.repo + "/events")
                        .then(function (response) { $scope.eventinfo = response.data; })
                );
        }


        // Function select which ensures that the entire
        // text is selected when the user clicks in the text input.
        $scope.select = function(){
            this.setSelectionRange(0, this.value.length);
        }
    });

While this is getting the data to render, I cannot figure out how to dynamically assign the 1st repo list value as my default value (I tried document.getElementById("repo1").value but I got 'undefined') AND the function does not call the API again on dropdown change.

UPDATE 5/5/2017 -- Personal Solution Big thanks to @mikwat for all the help. I ended up using a slightly different solution than he did below, but both work.

angular.module('myApp', [])
.controller('DashboardCtrl', function($scope, $http){
    // Set search model to 'mbostock' and the fetch function to contact the
    // remote API and ensure the view is initialized. Load results when the search box changes.
    $scope.$watch('search', function() {
        initialFetch();
        // .then(MakeUrl);
    });

    // NOTE: watch for changes to repo
    $scope.$watch('repo', function() {
        $http.get("https://api.github.com/repos/" + $scope.search + "/" + $scope.repo + "/events")
            .then(function (response) {
                $scope.eventinfo = response.data;
            });

    });

    var user = $scope.search;
    $scope.search = "mbostock";

    // Make calls to the API for Users and Repo List
    function initialFetch(){
        $http.get("https://api.github.com/events")
            .then(function(response){ $scope.publicevents = response.data; console.log(response.data);})
            .catch(function (err) {
                        console.log(err)
                    });

        $http.get("https://api.github.com/users/" + $scope.search)
            .then(function(response){ $scope.userinfo = response.data; })
            .catch(function (err) {
                        console.log(err)
                    });

        $http.get("https://api.github.com/users/" + $scope.search + "/repos")
            .then(
                function(response){
                    $scope.repolist = response.data;

                    // NOTE: select first repo
                    if ($scope.repolist && $scope.repolist.length > 0) {
                        var repo = $scope.repolist[0].name;
                    } else {
                        console.log("Something went wrong here!");
                        var repo = "undefined"
                    }
                    $scope.repo = repo;
                    return repo
                }).then(function (repo) { 
                        $http.get("https://api.github.com/repos/" + $scope.search + "/" + repo + "/events")
                        .then(function (response) { $scope.eventinfo = response.data; console.log(response.data);})
                        return repo; 
                    }).then(function (repo) {
                        $http.get("https://api.github.com/repos/" + $scope.search + "/" + repo + "/languages")
                        .then(function (response) { $scope.languages = response.data; console.log(response.data);})
                    }).catch(function (err) {
                        console.log("Here!" + err);
                    });
    };


    // Function select which ensures that the entire
    // text is selected when the user clicks in the text input.
    $scope.select = function(){
        this.setSelectionRange(0, this.value.length);
    }


});
Community
  • 1
  • 1
KateJean
  • 428
  • 5
  • 17
  • It looks like you're calling `https://api.github.com/repos/mbostock//events` instead of `https://api.github.com/repos/mbostock/preamble/events` (for example). Try using Angular's select module with `ng-model` rather than trying to mix in jQuery: https://docs.angularjs.org/api/ng/directive/select – mikwat May 03 '17 at 04:23
  • @mikwat thanks for the tip! I had already started heading that direction when you suggested it. It definitely inched me forward, but I still am having issues (see the "Update and Edit" section in my main question). – KateJean May 03 '17 at 06:04
  • Ok, I think I have a working solution for you. See my post below. – mikwat May 03 '17 at 16:19

1 Answers1

1

Here's a working solution. I removed some of the dependencies just to get it to work in this sandbox. I used NOTE: comments to help describe the important changes.

angular.module('myApp', [])
    .controller('DashboardCtrl', function($scope, $http){
        // Set search model to 'mbostock' and the fetch function to contact the
        // remote API and ensure the view is initialized. Load results when the search box changes.
        $scope.$watch('search', function() {
            initialFetch();
                // .then(MakeUrl);
        });
        
        // NOTE: watch for changes to repo
        $scope.$watch('repo', function() {
            $http.get("https://api.github.com/repos/" + $scope.search + "/" + $scope.repo + "/events")
                 .then(function (response) {
                     $scope.eventinfo = response.data;
                  });
                  
            // NOTE: additional request to fetch languages
            $http.get("https://api.github.com/repos/" + $scope.search + "/" + $scope.repo + "/languages")
                 .then(function (response) {
                     console.log(response.data);
                     // TODO: display results
                  });

        });

        var user = $scope.search;
        $scope.search = "mbostock";

        // Make calls to the API for Users and Repo List
        function initialFetch(){
            $http.get("https://api.github.com/users/" + $scope.search)
                .then(function(response){ $scope.userinfo = response.data; });

            $http.get("https://api.github.com/users/" + $scope.search + "/repos")
                .then(
                    function(response){
                        $scope.repolist = response.data;
                        
                        // NOTE: select first repo
                        if ($scope.repolist && $scope.repolist.length > 0) {
                            $scope.repo = $scope.repolist[0].name;
                        }
                    },
                    $http.get("https://api.github.com/repos/" + $scope.search + "/" + $scope.repo + "/events")
                        .then(function (response) { $scope.eventinfo = response.data; })
                );
        }


        // Function select which ensures that the entire
        // text is selected when the user clicks in the text input.
        $scope.select = function(){
            this.setSelectionRange(0, this.value.length);
        }
    });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<div class="container-fluid outerdiv" ng-app="myApp" ng-controller="DashboardCtrl">

    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container-fluid">
            <div class="navbar-header">
                <a class="navbar-brand"><b>Github User Information</b> <span class="span-style"></span></a>
            </div>

            <div class="input-group search-bar">
                <input type="text" ng-model="search" ng-model-options="{ debounce: 800 }" onclick="select()" class="form-control" placeholder="Enter Github user login" autofocus />
                <span class="input-group-addon bar-style"><i class="glyphicon glyphicon-search"></i></span>
            </div>
        </div>
    </nav>

    <noscript>
        <div class="nojs">Javascript is either disabled or not supported in your browser. Please enable it or use a Javascript enabled browser.</div>
    </noscript>

    <div class="animated zoomInRight">


        <div id="user-bio" class="col-sm-4 col-md-4">
            <div>
                <div class="avatar">
                    <img src="{{ userinfo.avatar_url }}" class="thumbnail animated flip movie-poster">
                </div>

                <span class="span-outer">
                    <a href="{{userinfo.html_url}}" target="_blank">{{ userinfo.login }}</a>
                </span><br>{{ userinfo.name }}

                <p><strong>Joined:</strong><br> {{ userinfo.created_at }}</p>
                <p><strong>Last Updated:</strong><br> {{ userinfo.updated_at }}</p>

                <p>{{ userinfo.bio }}</p>

                <p class="outer-p">
                <div class="inner-p">
                    <span class="label label-primary">Public Repos :</span> {{ userinfo.public_repos }}
                </div>
                <div class="inner-p">
                    <span class="label label-primary">Followers :</span> {{ userinfo.followers }}
                </div>
                <div class="inner-p">
                    <span class="label label-primary">Following :</span> {{ userinfo.following }}
                </div>
                </p>

            </div>

            <div ng-if="userinfo.message==='Not Found'">
                No results found.
            </div>
        </div>

        <div class="col-sm-8 col-md-8">
            <h5><strong>Repositories:</strong></h5>
            
            <!-- NOTE: use ng-model and ng-repeat and don't clobber repo variable on scope -->
            <select id="repo-select" ng-model="repo">
                <option ng-repeat="r in repolist" id="repo{{ $index + 1 }}" ng-value="r.name" onchange="MakeUrl();">{{ r.name }}</option>
            </select>

            <h5><strong>Events:</strong></h5>
            <ul class="event-results" id="event-select" style="height:400px; overflow-y:auto;">
                <li ng-repeat="event in eventinfo">
                    <a id="{{ $index + 1 }}" value="{{ event.type }}">{{ event.type }}
                    </a>, {{ event.created_at }} <!--ng-click="update(movie)"-->
                </li>
            </ul>
        </div>

    </div>
</div>
mikwat
  • 533
  • 3
  • 11
  • you're a lifesaver! thank you! You comments really helped me understand your process. As far as scalability goes -- how would I go about making another GET request based on the repo and search values (like the last GET request was? i.e. "api.github.com/repos/"; + $scope.search + "/" + $scope.repo + "/languages" ) I'm worried my original construct isn't easy to extend, hence my question early on referencing Angular promise-chaining – KateJean May 04 '17 at 14:01
  • I don't think there's any need for promise-chaining if you want to make additional API calls when the repo is selected. See my updated answer. The `$http.get` calls will run async, so there isn't really a scaling issue either. Am I missing something? – mikwat May 05 '17 at 16:58
  • Nope, that definitely works! I ended up figuring this out how to do this last night, albeit not as simply as you did! I really appreciate you taking the time to respond back. See my updated question for my solution. – KateJean May 05 '17 at 17:03
  • Sure thing. I see your solution and that works, but since there's really no reason that the "events" and "languages" calls can't be made in parallel, I don't think they should be chained. Anyway, glad I could help. – mikwat May 05 '17 at 18:20