Lesson 26 - Our Login Component

We got our service done, now it's time to use it! You should already have a login component made, and it should be displayed inside your navigation component. If you haven't done that yet, do so now. You may want to have a login and logout button as well, but we'll go over that part.

Injecting our Service

Inside our login component, you should already have { Component } imported. We will need to bring in two things: Our login service we just made, and since we'll be using the firebase.User type we declared earlier, we should bring that in.

import { LoginService } from '../../services/login.service;

import * as firebase from 'firebase';

Your folder structrure may be different than mine so copying the file location of the service word for word may not work, use your intellisense if need be.

We have two steps we need before we prepare our methods: First we have to inject our service into this component. This will be the last time I go over how to do this, so you should know where we do that at this point: Inside the parentheses of our constructor:

constructor( private _login:LoginService ) { }

Injection complete. Now remember that we had a method in our service that will return our logged in user info as an observable with the type firebase.User. Therefore, we need to create a variable that will hold the resulting user once we subscribe to it:

public authState: firebase.User

Side note:

We did not make this an observable because we are going to execute the .subscribe in our login method. If you are taking the Async Pipe Challenge (it's the hottest thing these days), then we will need to change the type since we wouldn't be doing the .subscribe. That's all I will give you for the challenge, refer to the async pipe lesson if you need it.

Getting our User Info

The first thing we are going to set up is our subscription to our user information. We want to subscribe to this as soon as the component loads. Because this is a subscription, if there are any changes to our authstate such as logging in and out, it will update automatically. Let's create an ngOnInit if you haven't done so already:

ngOnInit() {

this._login.getLoggedInUser().subscribe(user => this.authState = user);

}

I'm using arrow notation shorthand here: I take the result of the subscription, pass it as the variable user and then throw it into our variable this.authState.

If you didn't previously have an ngOnInit, remember you will need to import it up top as a part of @angular/core and you will need to add the implements OnInit after your class declaration.

Introduction to async shenanigans

One important thing to go over here briefly is that our login and the getting of our login user are happening asynchronously. This means that our code is not waiting for one line to finish to go over to the next. Fortunately we are running our subscription in our OnInit and we aren't immediately calling that data anywhere else. But sometimes when dealing with asynchronous code, you will run into some shenanigans. I will explain them later.

Our Login Method

The first part should be easy. We simply need to call the login method from our service that we created. We gave injected our login service via the _login variable. Now we can call our login method by simply calling this._login.login().

public login() {

this._login.login()

}

Time to logout

So we have a method for logging in, but now we need one to log out. Just like before, we can call the logout method from our service quite easily. We do not need to change out authState to null manually here, that's the power of subscriptions. When someone logs out, that user information will change, and that change will trigger the code for anyone who is subscribed to it. Since we are subscribed, and we updated our variable inside our subscription, it will update on its own.

public logout() {

this._login.logout();

Easy enough. So we have our login methods, now we just need to make a click event inside our html to actually login:

<button (click)="login()">Login</button>

<button (click)="logout()">Logout</button>

I know it's been a little bit since we worked on our html stuff, so here's a good reminder: Click events in angular use the notation (click). Any time you see something in parentheses inside an html, it is almost always referring to some kind of event. Also, don't forget the () at the end of the function to indicate that we are to actually invoke it.

Try saving now and clicking on your login and logout buttons.

That's pretty cool. Now we also are getting that user information, and its set to the variable authState of our ts. We should be able to simply bind to that variable in the html, right? Lets put it inside a <pre> tag and use the | json pipe since we know the result should be a json.

<pre> {{ authState }} </pre>

Save and checkout your results, yours may vary depending on whether or not you are logged in at the time of refresh. If you aren't logged in, please do so we can see the result of our authstate.

Whew, that's a hefty object

It sure is, but if you study it, theres a lot of userful information in there. For now, lets only focus on the users name. There's an property in there called displayName, that seems to have the information we want. Lets adjust our html:

<pre> {{ authState.displayName }} </pre>

Now save and check out the result and y...

I'm getting console errors when I'm not logged in! It does seem to work otherwise though...

I was going to get to that.

Sorry :(

It's all good. So now we run into a problem. when we are not logged in, we set our authState to null. Or even worse, if we load the page without doing any logging whatsovere, authState is undefined. And the console doesn't like when you try to find a property of undefined or null.

There are two solutions two this. First, do you remember what we used in our html if we wanted to bind to something that may not be there, but we want the console to not get mad at us if it isn't? It was quite some time ago, but you can use the question mark ? on our variable name to make sure it doesn't throw an error:

<pre> {{ authState?.displayName }} </pre>

Notice that we didn't put the ? on the display name. The part of the data that we aren't sure is going to be there is the actual authState, so we have to put our mark there.

Now if we aren't log in, it should show null. Tht's okay, but it isn't what we want. We don't want our app displaying 'null' or 'undefined' if they aren't logged in. Therefore we only want that name to display if that authState variable as long as it has something in it? How do we do that? All the way back from lesson 1, it's our pal *ngIf!

<pre *ngIf="authState"> {{ authState.displayName }} </pre>

Now if that authstate contains a false value such as null or undefined, the entire <pre> tage won't even show up. This also removes the need for the ? on authState, because that code won't even attempt to display if there's nothing in our authState.

Making our Buttons a Little Smarter

Our buttons should work, but you probably noticed one issue: If we're logged in, we can still click the login button. We don't want to be logging in twice, so lets disable those buttons. We just used our authState as a condition as to whether or not to display the names, we can also use it as a condition to disable our buttons:

<button (click)="login()" [disabled]="authState">Login</button>

<button (click)="logout()" [disabled]="!authState">Logout</button>

As a reminder, much like how parentheses () around a word in an html element generally refers to an event, square brackets [] around a word in our html generally refers to a property. In this case, we are disabling our login button if our authState contains anything, and disabling the logout if there's nothing there.

Try it out. Your buttons should now disable appropriately.

Now we have a fully working login service, a component that handles the logging in and out, and we can now use that service to display login information and check our login status anywhere we want. Sweet!

You said you would go back to that async business...

The async problem in action

I sure did. Remember how I mentioned earlier that the subscription to our authState is happening asynchronously. This means that it is not waiting for that code to finish before executing the next line of code. For example, let's say we did not subscribe in our OnInit, and instead subscribed when we logged in, and then wanted to console log our authState so we can see what we are working with like we mentioned before. Let's say I entered the code like this:

You don't have to edit your code to fit this example. If you do, please make sure to change it back.

async login example 1

After we execute our subscribe, we console log out our user. Makes sense looking at it, right? But look at the console.log result:

async console example 1

What is that madness?!

Right? What happened is that the subscribe started and then while the browser was doing it's subscribe business, it immediately went on to the console.log before our subscribe was finished.

So how would we solve this problem? In this case, we would have to make sure our code is executed inside our subscribe. It would come out looking like this:

async login example 2

I added some text to our logs so you can see where in the code this is taking place. Check out the result:

async login example 2

Pretty wild right? Our code on the inside executed last because it didn't run until our subscription was complete.

This means whenever you want to execute code that is handling data from a subscription and only after that subscription is complete, you may find yourself needing to call a method inside a the .subscribe portion of your code. A function that is to be run immediately after an asynchronous task is complete is commonly referred to as a callback. In the strictest sense, the code inside the curly braces is technically our callback function, since () => {} is shorthand for a function.

If you look into promises in javascript, you will be dealing with this sort of thing quite a bit. I won't cover it in detail yet (maybe in the future), but since Angular has us dealing with observables quite a bit, it's important to understand the sequence and flow of your code.

Some more challenges if you want extra work (and I know you do)

Tryout the Async Pipe Challenge and see if you can get the user info display with the | async pipe in your html. Don't forget that this will require adjusting some things in your ts.

If thats not enough, try using .pipe and map from rxjs to shrink that huge object we're getting from google into a smaller object that only contains the data we want.

Here's a screenshot of our login component:

I made an update to the lesson on 5/18 at 5:00pm. I previously implemented the login subscription incorrectly. My apologies.

login component ss