I am building an app in Angular 6 + PWA. I have to implement Google Authentication for logging in. The google login screen should be the first screen when we click on the app icon. I don't want the screen where Google button appears for and on its click the login screen appears. Post successful login, landing page will appear. From what i could gather, only on click of the following button, will the logins screen appears
<div class="g-signin2" data-onsuccess="onSignIn"></div>
When we click on this button, control is given to onSignIn() and then the flow is carried on. I want this function to be called on ngOnInit() of angular
app.component.ts as it is the root component. Basically i don't want the following:
<div class="g-signin2" data-onsuccess="onSignIn"></div>
and yet i want to call its method onSignIn()
How can i achieve this?
In my AuthService I have a method that looks like this:
googleLogin() {
const provider = new auth.GoogleAuthProvider()
return this.oAuthLogin(provider);
}
In the component you want to use this you could do this:
import { Component, OnInit } from
'#angular/core';
import { AuthService } from
'../auth/auth.service';
#Component({
selector: 'user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.css']
})
export class UserProfileComponent implements
OnInit {
constructor(public auth: AuthService) {
}
ngOnInit() {
this.auth.googleLogin();
}
}
Don't forget to import the AuthService into the component you're using it in.
You may not be using firebase, but just call the method for google login in your particular auth api in either an OnInit function. No need to 'have' to click a button to call the method.
Related
I'm working on rewriting an AngularJS application in Angular8. I have read of the different ways to communicate between components but can't seem to find the proper way to achieve this given my current requirements.
I currently have 2 sibling components which both use a common service that handles basic crud functionality. The form component calls a create method in the service and the list component calls a fetch method.
<page-component>
<form component></form component>
<list-component></list-component>
</page-component>
In AngularJS this would have been achieved by using a $scope.broadcast() and $scope.on() which is the effect that I'm looking to reproduce.
What I'm trying to figure out is the best way for the form component to emit an event to which the list component would (i assume) subscribe in order to tell it to refresh itself.
To be clear, I don't want to pass the updated values to the list component. I would like to simply communicate to it that its dataset has been updated and that it needs to re-fetch its records.
I have tried using an #Output in my form component:
export class FormComponent implements OnInit {
#Output() valueChange = new EventEmitter();
onUpdate() {
this.valueChange.emit(true);
}
...
}
But I'm confused on how to implement the event listener as all the documentation I have read seems to point to events passed between parents and children and not to siblings as it were. Also the examples I have seen all seem to be focused on passing the actual data to the component instead of simply having it watch for an event and do the work on its own, as I require.
I have also seen methods that use #Input which require parameters to be passed into them on declaration. Somehow I feel that these components should be able to work on their own and not depend on this.
I believe there are multiple ways to achieve this.
In case of unrelated components, like the sibling components, one method would be to use a Subject Observable of RxJS module in the common service to which Form component will push a new value that can be subscribed to by the List component.
Service:
import { Subject } from 'rxjs/Subject';
#Injectable()
export class SampleService {
public triggerSource = new Subject<boolean>();
public trigger$ = this.triggerSource.asObservable();
}
Form Component:
import { SampleService } from '../services/sample.service';
#Component({
selector: 'form-component',
templateUrl: './form.component.html',
styleUrls: ['./form.component.css'],
})
export class FormComponent implements OnInit {
constructor(private _sampleService: SampleService) {
}
private setTriggerState(state: boolean) {
this._sampleService.triggerSource.next(state);
}
}
List Component:
import { SampleService } from '../services/sample.service';
#Component({
selector: 'list-component',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css'],
})
export class ListComponent implements OnInit {
constructor(private _sampleService: SampleService) {
this._sampleService.trigger$.subscribe(value => {
if (value) {
// value is true (refresh?)
} else {
// value is false (don't refresh?)
}
});
}
}
And since we are returning an Observable, there are numerous operators to refine the outflow of data. For eg., in your case operator distinctUntilChanged() can be used to avoid pushing redundant information. Then the declaration of the observable would be
public trigger$ = this.triggerSource.asObservable().distinctUntilChanged();
Answer to this question could be a topic for a nice holywar, you know )
My opinion based on experience is in following:
Let's assume user typing something and than performs refresh page. Ask ourself what should happen?
If User's changes should recover to "before editing" state - than the parent component is enough to share such events.
If User's changes should be as it was before refresh page action, so if you need to store it localStorage or database or elsewhere, than it's better to involve services for it.
Create a #Input property in your ListComponent and pass all the items that list component will render from PageComponent. Whenever create component emit a value fetch the new list in PageComponent and update passed property.
Implement the OnChange lifecycle hook in ListComponent and capture the property change in ngOnChange(changes) method.
If you don't like to pass the list from PageComponent then there are few options that can be used to solve the issue.
Option 1:
instead of passing a list:Array<any> pass a list:Observable<Array<any>> and subscribe the observable in your ListComponent so technically you http call will happen inside list component. But you have to reassign the list with new Observable<Array<any>> everytime FormComponent emit a value so ngOnChange will notified.
Option 2 :
You can pass a Subject to ListComponent as a propery and Subscribe to subject in ListComponent. Then you can fetch the product in ListComponent. When ever your FormComponent emit value call next() method in subject you passed. Every time you call next method your subscribles will notify and you can fetch the details from API.
reference links
https://angular.io/api/core/OnChanges
https://angular.io/api/core/Input
#Component({
//Component Metadata
})
export class ListComponent implement OnChange{
#Input() listOfAny:Array<any>=[];
ngOnChanges(changes: SimpleChanges) {
this.listOfAny =changes.listOfAny.currentValue
}
}
#Component({
//Component Metadata
})
export class FormComponent {
#Output() valueChange = new EventEmitter();
onUpdate() {
this.valueChange.emit(true);
}
}
#Component({
//Component Metadata
template:`
<page-component>
<form component (valueChange)="valueChanges($event)" ></form component>
<list-component [listOfAny]="list"></list-component>
</page-component>`
})
export class PageComponent {
list=[]
valueChanges(newValue){
// you can fetch items from API if needed.
this.list.push(newValue)
}
}
Have a look at the Rxjs Subjects. Subjects makes the inter-components communication easier.
Since Forms and List component use a common CRUD service in your case, you can have a subject (say, newRecordSubject) and push any new records created into that subject. Now the List component should subscribe to this subject to be notified of any new records whenever it is created by the Forms component.
Have a look at the sample CRUD service below. I beleieve everything is very much self explainatory from the code snippets below.
crud.service.ts
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
import { Record } from './record.model';
#Injectable({providedIn: 'root'})
export class CrudService {
private newRecordSubject = new Subject<Record>();
get newRecordListener() {
return this.newRecordSubject.asObservable();
}
public insertRecord(record: Record): void {
// logic to pass the record to the backend
this.newRecordSubject.next(record);
}
public fetch(): Record[] {
// logic to fetch the inital records from the backend
return [];
}
}
Now in your List component, inject this CRUD service and subscribe to the newRecordsSubject as below.
list.component.ts
import { OnInit, Component, OnDestroy } from '#angular/core';
import { Subscription } from 'rxjs';
import { CrudService } from './crud.service';
import { Record } from './record.model';
#Component({
selector: 'list-component',
template: ''
})
export class ListComponent implements OnInit, OnDestroy {
records: Record[];
private sub: Subscription;
constructor(private service: CrudService) {}
ngOnInit() {
this.records = this.service.fetch();
this.sub = this.service.newRecordListener.subscribe(record => this.records.push(record));
}
ngOnDestroy() {
if (this.sub) {
this.sub.unsubscribe();
}
}
}
Note:
I'm converting Subject to Observable so that the subject is available as read-only to methods outside Crud Service.
It is always a best practice to unsubscribe to the Subscriptions of Subjects and Observables, to avoid memory leaks.
Hope this helps! Cheers and Happy Coding.
You can create a function in parent that sets the value in the child component that you need.
In the component
<page-component>
<form component (value change)="formValueFunction($event)" emitedformValue="formEmitiedValue"></form component>
<list-component (list change)="listValueFunction($event)" emitedListValue="listEmitedValue"></list-component>
</page-component>
In the component you can set the value to the other components like this
formEmitiedValue:any;
listEmitedValue:any;
formValueFunction(event){
this.formEmitiedValue= event;
}
listValueFunction(event)
{this.listEmitedValue=event;
}
check the event value using logs and if there are changes in form input use OnChange event hook in the components
When I was migrating my app from AngularJS to Angular 2+. I choose to use RxJS, super easy to use, but there are learning curve.
You create service and RxJS BehaviorSubject. Any component can now subscribe for changes if needed, and any component can send updated data using .next() method.
This is your service:
import {Injectable} from "#angular/core";
import { BehaviorSubject } from "rxjs/BehaviorSubject";
#Injectable()
export default class yourService {
valueChange = new BehaviorSubject<object>({});
//add service functions if needed
...
}
This is your component:
import { Component, OnInit, OnDestroy } from "#angular/core";
import YourService from "../path to your service";
#Component({
selector: "name of component",
template: require("your.html")
})
export class nameOfComponent implements OnInit, OnDestroy {
private sub;
privat val = "Your value";
constructor( private YourService: YourService) {
}
ngOnInit() {
this.sub = this.YourService.valueChange.subscribe( (value) => {
this.YourService.valueChange.next(this.val); // If you need to pass some value to the service.
this.val = value;
})
}
ngOnDestroy() {
this.sub.unsubscribe();
}
I want to add a back button in Ionic4(Angular 7).
But I can't find the proper method in Angular Router.
import {Router} from '#angular/router';
How do we go back when clicking a button, in the component handler?
I'd like to implement it using '#angular/router' not '#angular/common' => Location
Since you are using ionic 4 then to go backward, you can do the following:
constructor(private navCtrl: NavController) {}
btnClick(){
this.navCtrl.navigateBack('/home');
}
With Angular routing you could use the Location API:
constructor(private location: Location){}
and then when you need to navigate back call:
this.location.back();
Keep in mind that for Angular 7 you have to import Location from #angular/common
Just do the following if you wanna go back to your previous route.
constructor(private navCtrl: NavController) {}
goBack(){
this.navCtrl.pop();
}
This'll pop the current page from the navigation stack and return you to the previous page and the part of the page from where you had navigated forward.
I am trying to write a service in which some events are triggered via respective methods in it so that any component in my application can listen to it.
So, I created a custom event in a service as follows
export class EventsService {
#Output() expandNav: EventEmitter<Number> = new EventEmitter();
trigExpandNav() {
this.expandNav.emit(1);
}
constructor() { }
}
Then I included this service as a provider in my component in which I wanted to emit this event via the trigExpandNav method as follows :
import {
Component,
OnInit,
Output,
EventEmitter
} from '#angular/core';
import {
EventsService
} from '../../services/events.service';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css'],
providers: [EventsService]
})
export class HeaderComponent implements OnInit {
eventsService: EventsService;
fun() {
this.eventsService.trigExpandNav();
}
constructor() {}
ngOnInit() {}
}
But I am getting this error
ERROR in src/app/layout/header/header.component.ts(21,7): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'EventEmitter<Number>' has no compatible call signatures.
I don't know where I am doing it wrong, so please help and Thanks in advance.
IMPORTANT
What I am trying to achieve is :
Suppose I have 2 components. I want to trigger an event from 1st component when a button or link is clicked and listen that event in 2nd component from the html file like this sda
For that I made a service in which I am emitting the event through a method. The thing is it's working as expected.
You shouldn't be using event emitters for Service's this is a better place for a RxJS BehaviorSubject or Subject. depending on your need.
#Ouput()s and EventEmitters are for Components only
So your service should become something along the lines of:
export class EventsService {
private expandNavSubject = new BehaviorSubject<number>(null);
trigExpandNav() {
this.expandNavSubject.next(1);
}
get expandNav$(){
this.expandNavSubject.asObservable();
}
constructor() { }
}
}
then in your header component inject the service via the constructor with:
constructor(private eventsService: EventsService){}
and call the trigger expand nav function with this.eventsService.trigExpandNav(); in your fun() function
I have a legacy script that I need to include in my angular application.
The thing about this script is that it relates to a specific component, and it has to be loaded only after the view of the component is loaded.
As for today, I succeeded to include it on OnInit function but sometimes (not always for some reason) the CLI throws an error about it.
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-player-page',
templateUrl: './player-page.component.html',
styleUrls: ['./player-page.component.scss']
})
export class PlayerPageComponent implements OnInit {
public itemId: string;
constructor() {}
ngOnInit() {
//We loading the player srcript on after view is loaded
require('assets/scripts/player/player.js');
}
}
The script assumes that elements in the UI exists and it searches them by the id.
When adding it on top of the page, it doesn't work.
What is the best way to achieve the desired behavior?
There are multiple solutions to this issue.
declare the require const on top of your component
declare const require: any;
import { Component, OnInit } from '#angular/core';
#Component({})
...
use the dynamic import() function from typescript
ngAfterViewInit() {
//We loading the player script on after view is loaded
import('assets/scripts/player/player.js');
}
change the library to only start running after you call a function from the component, this way you can add it to the scripts array of your angular.json
First of all, sorry for the weird title. I couldn't find a better summary.
So what I want is to create an application which consists of a basic 3 part structure (header / content / footer). Depending on the route that is active, the header part should change (every header is a component on its own).
So far the header that is displayed is determined by an [ngSwitch] statement in the "mainApp.component" and looks like this:
<span [ngSwitch]="currentLocation">
<span *ngSwitchWhen="'/login'"><login-header></login-header></span>
<span *ngSwitchWhen="'/home'"><home-header></home-header></span>
</span>
<div class="content"><router-outlet></router-outlet></div>
<app-footer></app-footer>
Where the "mainApp.component.ts" looks like this:
import { Component, OnInit } from '#angular/core';
import {ROUTER_DIRECTIVES} from '#angular/router';
import {HomeHeaderComponent} from './home-header';
import {LoginHeaderComponent} from './login-header';
import {FooterComponent} from './footer';
import {Router} from '#angular/router';
#Component({
moduleId: module.id,
selector: 'app',
templateUrl: 'mainApp.component.html',
styleUrls: ['mainApp.component.css'],
directives: [HomeHeaderComponent, LoginHeaderComponent, FooterComponent, ROUTER_DIRECTIVES],
providers: []
})
export class mainApp implements OnInit {
public currentLocation: string;
constructor(public router: Router) {
this.currentLocation = location.pathname;
}
ngOnInit() {
}
}
This works just fine when I go to my localhost:4200/login or localhost:4200/home manually as it renders the mainApp.component, checks which is the current route and displays the header accordingly. However, when I change the route by e.g. a button click via
<span class="btn btn-default headerButton homeButton" (click)="navigateToHome()"></span>
Where navigateToHome() is simply
navigateToHome() {
this.router.navigate(['/home']);
}
The header stays the same as the change detection doesn't take the route change into concideration and therefore doesn't rerun the [ngSwitch] / rerender the mainApp.component.
I already thought about including the header inside the components template they belong to but if there is a way to do it in the main component I would prefer that.
If you guys got any idea of how to solve this, or ways to do it better / another way / the way its best practice I'm glad to hear of it.
Thanks.
The answer here is to subscribe to the router events like so:
this.router.events.subscribe((e) => {e instanceof NavigationEnd? this.currentLocation = '/' + e.url.split('/')[1] : ''});
This subscribes to the routers events in general and changes the currentLocation variable whenever the navigation ends succesfully by checking the url property of the returned value.
I implemented this in the (at the time of this post) current router version #angular/routerv3.0.0-alpha8 which works just fine.