I'm creating an Angular 2 SPA for learning purposes and integrating Auth0 for handeling the authentication. I have an auth.service.ts that is going to be called from difference places in my application, for example in the top-navbar to logout and on the auth-page to handle logins and registrations.
When trying to place the Auth0 container in a div by setting the container option I get the following error: Can't find element with id auth-container
How can I let the auth.service know how/where to look for the auth-container div? Placing all the logic inside the auth.component.ts is assumably not an option because the auth.service will be used for other functionality in other places where the lock variable is also used.
auth.service.ts
import { Injectable } from '#angular/core';
import { tokenNotExpired } from 'angular2-jwt';
import { myConfig } from './auth.config';
declare var Auth0Lock: any;
var options = { container: 'auth-container' };
#Injectable()
export class Auth {
lock = new Auth0Lock(myConfig.clientID, myConfig.domain, options);
constructor() {
this.lock.on('authenticated', (authResult) => {
localStorage.setItem('id_token', authResult.idToken);
});
}
public authenticated() {
return tokenNotExpired();
};
public logout() {
localStorage.removeItem('id_token');
};
}
auth.component.ts
constructor(public auth: Auth) {
auth.lock.show();
}
auth.component.html
<div id="auth-container"></div>
Well they did not make your life easy but by mistake I made it work.
Try this:
auth.component.ts
ngOnInit() {
this.auth.login()
}
Delete this from your constructor
auth.lock.show();
The auth.service is not a container, it's a service that provides a popup when the login function is invoked.
So, to reuse it wherever you like, you need to inject the auth service into the component where you want to call the auth service from. Then, you just call the method. For example, here is the html for my Start component. You can see that the click event for the signin button is bound to the "submitLogin()" method of the component (the Start component):
<div class="splash-back" *ngIf="!authService.authenticated()">
<div id="splash">
<div id="logo"><span class="silver">GCO</span>TeamKeeper
<p class="silver tagline">The other teams could make trouble for us if they win.</p>
<p class="silver attribution">~ Yogi Berra</p></div>
<div class="call">
<br>
<button class="btn-sign-in" (click) = "submitLogin()">Sign up or Log in</button>
</div>
<!--<mtm-authentication></mtm-authentication>-->
</div>
</div>
And here is the start component code (note the injection of the authentication service in the constructor):
#Component({
selector: 'tk-start',
templateUrl: './start.component.html',
styleUrls: ['./start.component.css']
})
export class StartComponent implements OnInit {
constructor(private authService: UserAuthenticationService) { }
ngOnInit() {
}
submitLogin(){
this.authService.login();
}
}
And to make this example complete, here is my auth service code:
import {Injectable} from "#angular/core";
import { tkConfig } from './user-authentication.config';
import {Router} from "#angular/router";
import {tokenNotExpired} from "angular2-jwt";
let Auth0Lock = require('auth0-lock').default;
#Injectable()
export class UserAuthenticationService {
// Configure Auth0
userProfile: Object;
lock = new Auth0Lock (tkConfig.clientID, tkConfig.domain, {
avatar: null,
theme: {
primaryColor: "#69BE28",
foregroundColor: "#000000"
},
languageDictionary: {
title: "GCO TeamKeeper"
}
}
);
constructor(
private router: Router) {
this.userProfile = JSON.parse(localStorage.getItem('profile'));
// Add callback for lock `authenticated` event
this.lock.on('authenticated', (authResult) => {
localStorage.setItem('id_token', authResult.idToken);
this.lock.getProfile(authResult.idToken, (error, profile) => {
if (error) {
alert(error);
return;
}
profile.user_metadata = profile.user_metadata || {};
localStorage.setItem('profile', JSON.stringify(profile));
this.userProfile = profile;
this.router.navigate(['/organization']);
});
})
}
public login() {
// Call the show method to display the widget.
this.lock.show();
};
public authenticated() {
// Check if there's an unexpired JWT
// It searches for an item in localStorage with key == 'id_token'
return tokenNotExpired();
};
public logout() {
// Remove token from localStorage
localStorage.removeItem('id_token');
localStorage.removeItem('profile');
this.userProfile = undefined;
this.router.navigate(['/start']);
};
}
Related
I read an article in which it states that, if you subscribe to subject when the event has been already emitted You would lose the emitted value. his demonstrated example is below.
let subject: Subject<string> = new Subject();
subject.next('test');
subject.subscribe((event) => {
console.log(event);
});
But ,I have a similar approach implemented, In which first subject will emit the value , then subscribes the subject. Value is there. not lost. code is below.
In the code, we are saving authentication information in a subject. we use that in header component to show logout button and links. Question is why my code is working even though article says it won't?
import { HttpClient, HttpErrorResponse } from "#angular/common/http";
import { Injectable } from "#angular/core";
import { Subject} from "rxjs";
import { User } from "./user.model";
#Injectable({providedIn : "root"})
export class AuthService{
constructor(private httpClient:HttpClient){}
user = new Subject<User>(); //emit new user when login or logout or token expired
login(email:string,password:string){
return this.httpClient
.post<SignUpResponse>('https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=AIzaSyBCK9B2RcL0D4Bn8YaFCxE9rBXejTLNYQY',
{
email:email,
password:password,
returnSecureToken:true
})
.pipe(tap( response => this.handleAuthentication(response.email,response.idToken,response.idToken,response.expiresIn)))
}
private handleAuthentication(email:string,userId:string,token:string,expiresIn:string){
const expirationDate = new Date(new Date().getTime() + +expiresIn * 1000)
const user = new User(email,userId,token,expirationDate);
this.user.next(user);
}
}
Header.component.ts
import { Component,Output, EventEmitter, OnInit, OnDestroy } from "#angular/core";
import { Subscription } from "rxjs";
import { AuthService } from "../auth/auth/auth.service";
import { DataStorageService } from "../shared/data-storage.service";
#Component({
selector : 'app-header',
templateUrl:'./header.component.html'
})
export class HeaderComponent implements OnInit,OnDestroy{
collapsed = true;
private subscription : Subscription;
isAuthenticated : boolean = false;
constructor(private dataStorageService:DataStorageService,
private authService:AuthService){
}
ngOnInit(){
this.subscription = this.authService.user.subscribe( user => {
this.isAuthenticated = !!user;
console.log("User Object"); console.log(user);
console.log("IsAuthenticated value"); console.log(!!user);
})
}
ngOnDestroy(){
this.subscription.unsubscribe();
}
}
The only reason your example subscribe is running is because you load the Header component before you start the login process/method. ngOnInit of component is called before login method in service.
Your code run like this not the way you have described in the quesiton:
user = new Subject<User>();
subject.next('test');
// then this runs in component ngOnInit
this.authService.user.subscribe((event) => {
console.log(event);
});
// when you try to login from inside the component then it runs the login method with next.
login(email:string,password:string){.....
this.user.next(user); // now this is triggered hence you don't loose the user response.
`I have an Angular 6 app using Bootstrap JS Tab. One of my tabs contains a list of notes. The user adds a note through a modal popup, and the list is refreshed with the new note. That works fine. However, in the header of the tab, I have an anchor tab reflecting the number of notes entered. My question is, how can update that number when a new note is added?
The app is arranged as so: There is a user-details.component.html that displays all the tabs. The notes tab is contained inn user-notes.component.html and there's a user-notes.component.ts (posted below).
For example, here's the html of some of the tabs in user-detail.component.html:
<ul id="tabs" class="nav nav-tabs" data-tabs="tabs">
<li class="active">Entitlements</li>
<li>Payment Instruments</li>
<li><a href="#notes" data-toggle="tab" >Notes ({{_notes.length}})</a></li> <!--style="display: none" -->
</ul>
Notice that the "Notes" link references {{_notes.length}}. I need to update _notes.length when I post, but I'm totally unsure how. Can someone help?
EDIT: Here's my component code:
import { AuthGuard } from '../../service/auth-guard.service';
import { Router } from '#angular/router';
import { Logger } from './../../service/logger.service';
import { Component, OnInit, Input } from '#angular/core';
import { UserDetailService } from '../../user/service/user-detail.service';
import { UserEntitlementService } from '../../user/service/user-entitlement.service';
import { Note } from '../../user/model/note.model';
import { NgForm } from '#angular/forms';
#Component({
selector: 'app-notes-component',
templateUrl: './user-notes.component.html'
})
export class UserNotesComponent implements OnInit {
#Input() asRegIdofUser;
#Input()
private notesModel: Note[]=[];
private actionResult: string;
private notesCount: number;
private currentNote: Note;
constructor(private _logger: Logger, private _userDetailService: UserDetailService,
private _router: Router, private _userEntitlementService: UserEntitlementService,
private authGuard: AuthGuard) {
}
ngOnInit(): void {
//read data....
this.currentNote= new Note();
if (this.asRegIdofUser)
this.refreshNotesData();
}
refreshNotesData(){
this.actionResult='';
this._userDetailService.getNotes(this.asRegIdofUser).subscribe(
responseData =>{
let embedded = JSON.parse(JSON.stringify(responseData));
let notes = embedded._embedded.note
this.notesModel=[];
notes.forEach(note => {
this.notesModel.push(note);
})
this.notesCount=this.notesModel.length;
},
error =>{
this._logger.error("error on loading notes "+error);
}
)
this.currentNote= new Note();
}
onCreateNote(notesModal){
this._userDetailService
.postNote(this.asRegIdofUser,this.currentNote).subscribe(
response => {
if (response==='OK')
this.actionResult='success';
else
this.actionResult='failure';
},error => {
this.actionResult='failure';
}
)
}
userHasEditRole(): boolean{
return this.authGuard.hasAccess('edituserdetails');
}
onDelete(noteId: string){
let deleteNoteId: number = Number.parseInt(noteId);
this._userDetailService.deleteNote(this.asRegIdofUser,deleteNoteId).
subscribe(
response =>{
if(response == 'OK')
this.refreshNotesData();
},
error =>{
this._logger.error("error on deleting notes "+error);
}
)
}
}
Create a DataService, that will have your private listOfItems, a private BehaviorSubject that can be used to notify other components about changes in the list and the same, exposed as a public Observable.
import { Injectable } from '#angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
#Injectable()
export class DataService {
private listOfItems: Array<string> = [];
private list: BehaviorSubject<Array<string>> = new BehaviorSubject<Array<string>>(this.listOfItems);
public list$: Observable<Array<string>> = this.list.asObservable();
constructor() { }
addItemToTheList(newItem: string) {
this.listOfItems.push(newItem);
this.list.next(this.listOfItems);
}
}
Inject this service in all the three Components, the Header, Add and List. And use it accordingly.
Here's a Working Sample StackBlitz for your ref.
Here you are trying to communicate between different angular components.
For this, You can use a service or listen to an event emitted from the component that adds the note.
You can find more info here: component-interaction
I have encountered a project in progress, let multiple unrelated components notify each other of the update data, is there a cleaner coding method?
There are 3 components (more likely later) and a common-data component. They have no parent-child relationship with each other and only show on the same screen.
The desired effect is to press the button of any component, update the contents of common-data, and notify yourself and other components to fetch new messages from common-data.
At present, my approach is to use Rx's Observable and Subscription, but they must be imported in the component.ts and service.ts files of each component, and a lot of duplicate code appears, it is very messy, I don't know what is better. practice?
Thanks!
My code :
The sample name is test-a-comp (a.b.c and so on, the code is the same)
test-a-comp.html
<p>
{{ownMessage}}
</p>
<button (click)="sendChange()">update</button>
test-a-comp.component
import { Component, OnInit } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
import { CommonData } from '../common-data/common-data';
import { TestACompService } from './test-a-comp.service';
import { TestBCompService } from '../test-b-comp/test-b-comp.service';
import { TestCCompService } from '../test-c-comp/test-c-comp.service';
#Component({
selector: 'app-test-a-comp',
templateUrl: './test-a-comp.component.html',
styleUrls: ['./test-a-comp.component.css']
})
export class TestACompComponent implements OnInit {
subscription: Subscription;
ownMessage;
constructor(
private testAService: TestACompService,
private testBService: TestBCompService,
private testCService: TestCCompService,
) {
this.subscription = this.testAService.getMessage()
.subscribe((test) => {
CommonData.message = test;
});
this.subscription = this.testBService.getMessage()
.subscribe(() => {
this.ownMessage = CommonData.message;
});
this.subscription = this.testCService.getMessage()
.subscribe(() => {
this.ownMessage = CommonData.message;
});
}
ngOnInit() {
}
sendChange() {
this.testAService.sendMessage();
}
}
test-a-comp.service:
import { Injectable } from '#angular/core';
import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';
import {Subscription} from 'rxjs/Subscription';
#Injectable()
export class TestACompService {
subscription: Subscription;
private subject = new Subject<any>();
constructor() {
}
getMessage(): Observable<any> {
return this.subject.asObservable();
}
sendMessage(): void {
this.subject.next('update message from A');
}
}
As far as i understand & you've mentioned in the above, there is a button in one of the component (test-a-component.html). If you update the button, you need to send message to other components which are subscribed.
The Components which have no Parent-Child relationship can communicate via a service:
Create a single service file (In your case: test-a-comp.service)
Create a Subject on what data you need to communicate via this service:
export class testMessageService {
constructor() {}
// Observable string sources
private message = new Subject<string>();
//Observable string streams
testMessage$ = this.message.asObservable();
constructor() {}
// Method to send message when a button is clicked
sendMessage(message: string) {
this.message.next(message);
}
/* You don't need "getMessage()" method as you've already subscribed to
the observables. There subscribed Observable string streams are
injected in your components (As below point 3) to display / do other
operation on the message. */
}
In your other Components, where you want to receive messages, do the following:
export class TestComponent 1 {
myMessage1: string;
constructor(private TestMessageService: testMessageService) {}
TestMessageService.testMessage$.subscribe(message => {
this.myMessage1 = message;
});
}
export class TestComponent 2 {
myMessage2: string;
constructor(private TestMessageService: testMessageService) {}
TestMessageService.testMessage$.subscribe(message => {
this.myMessage2 = message;
});
}
export class TestComponent 3 {
myMessage3: string;
constructor(private TestMessageService: testMessageService) {}
TestMessageService.testMessage$.subscribe(message => {
this.myMessage3 = message;
});
}
For more information/guidance refer Component interaction via a common
service: https://angular.io/guide/component-interaction
Hope this helps!
I'm using AngularFire2 for an app and I've gotten the registration/login functionality working with Firebase, however, every time I refresh the page, the logged in state is reset and won't persist. I can't quite find functionality to do this, though I feel I'm missing something very small.
Can I use the AngularFireAuth to check on page load somewhere?
Here is my auth provider code:
import { Injectable } from '#angular/core';
import {Observable, Subject, BehaviorSubject} from "rxjs/Rx";
import {AngularFireAuth, FirebaseAuthState} from "angularfire2";
import {AuthInfo} from "./auth-info";
import {Router} from "#angular/router";
#Injectable()
export class AuthService {
static UNKNOWN_USER = new AuthInfo(null);
authInfo$: BehaviorSubject<AuthInfo> = new BehaviorSubject<AuthInfo>(AuthService.UNKNOWN_USER);
constructor(private auth: AngularFireAuth, private router:Router) {
}
login(email, password):Observable<FirebaseAuthState> {
return this.fromFirebaseAuthPromise(this.auth.login({email, password}));
}
signUp(email, password) {
return this.fromFirebaseAuthPromise(this.auth.createUser({email, password}));
}
fromFirebaseAuthPromise(promise):Observable<any> {
const subject = new Subject<any>();
promise
.then(res => {
const authInfo = new AuthInfo(this.auth.getAuth().uid);
this.authInfo$.next(authInfo);
subject.next(res);
subject.complete();
},
err => {
this.authInfo$.error(err);
subject.error(err);
subject.complete();
});
return subject.asObservable();
}
logout() {
this.auth.logout();
this.authInfo$.next(AuthService.UNKNOWN_USER);
this.router.navigate(['/login']);
}
}
Thankyou in advance!
AngularFireAuth is an observable and emits FirebaseAuthState values. If a user is signed in and the page is refreshed, AngularFireAuth will emit an authenticated FirebaseAuthState; otherwise, it will emit null.
So something like this should come close to solving your problem:
constructor(private auth: AngularFireAuth, private router:Router) {
auth.subscribe((authState) => {
if (authState) {
const authInfo = new AuthInfo(authState.uid);
this.authInfo$.next(authInfo);
}
});
}
In my Angular2 app I am bootstrapping an auth service LocalStorage that I want shared across my components:
bootstrap(AppComponent, [
ROUTER_PROVIDERS,
LocalStorage
]);
LocalStorage is defined as follows:
import {JwtHelper} from 'angular2-jwt/angular2-jwt';
import { Injectable } from 'angular2/core';
#Injectable()
export class LocalStorage {
key:string = 'jwt';
jwtHelper:JwtHelper = new JwtHelper();
username:string;
constructor() {
let token = localStorage.getItem(this.key);
if (token == null) return;
if (this.jwtHelper.isTokenExpired(token)) {
localStorage.removeItem(this.key);
} else {
this.username = this.jwtHelper.decodeToken(token).username;
}
}
login(jwt:string) {
localStorage.setItem(this.key, jwt);
}
logout() {
localStorage.removeItem(this.key);
}
isLoggedIn():boolean {
return this.username != null;
}
getUsername():string {
return this.username;
}
getToken():string {
return localStorage.getItem(this.key);
}
}
The problem is, however, when I share and update it across components only the component that updates it recognizes the changes. It is injected into components and edited like this:
constructor(private router:Router, private localStorage:LocalStorage) {
...
}
logout(event) {
event.preventDefault();
this.localStorage.logout();
this.router.navigateByUrl(RoutingPaths.home.path);
}
Why is it that it seems multiple instances of this service are being created across components? Thanks.
Edit An example of a component template binding is:
Component:
import {Component} from 'angular2/core';
import {Router, RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
import {RoutingPaths} from './routing-paths';
import {LoggedInOutlet} from './logged-in-outlet';
import {LocalStorage} from './local-storage'
#Component({
selector: 'my-app',
templateUrl: 'app/app.template.html',
directives: [LoggedInOutlet, ROUTER_DIRECTIVES]
})
export class AppComponent {
registerName:string;
constructor(private router:Router, private localStorage:LocalStorage) {
this.registerName = RoutingPaths.register.name;
}
logout(event) {
event.preventDefault();
this.localStorage.logout();
this.router.navigateByUrl(RoutingPaths.home.path);
}
}
Template:
<a *ngIf="!localStorage.isLoggedIn()" [routerLink]="[registerName]">Register</a>
Final Edit
Well this is embarrassing, after actually editing the username in the service it now works:
login(jwt:string) {
localStorage.setItem(this.key, jwt);
this.username = this.jwtHelper.decodeToken(jwt).username; // here
}
logout() {
localStorage.removeItem(this.key);
this.username = null; // here
}
Sorry for wasting everyone's time. Thanks again.
It's because you assigned LocalStorage as provider somewhere in your code.
Check if any of your component is containing:
#Component({
providers: [LocalStorage]
})
This gives an Injector instruction to create a new Instance for that component and all children if child one again does not have an provided LocalStorage itself.
The problem is, however, when I share and update it across components only the component that updates it recognizes the changes
This is because of angular 2 component model is a Tree:
So only the component that changes and its sub components are rerendered. For stuff like singletons containing state used across components you need something like redux : https://medium.com/google-developer-experts/angular-2-introduction-to-redux-1cf18af27e6e#.yk11zfcwz
I forgot to actually modify username in the service itself:
login(jwt:string) {
localStorage.setItem(this.key, jwt);
this.username = this.jwtHelper.decodeToken(jwt).username; // here
}
logout() {
localStorage.removeItem(this.key);
this.username = null; // here
}