I want to call, depending on the value of the variable, this.loggedInService.isLoggedIn methods: login() and logout()
If the value of the variable !this.loggedInService.isLoggedIn then
call login()
If !this.loggedInService.isLoggedIn then this method logout().
How to implement it correctly in app.html ?
template:
<li class="nav-item">
<a class="btn btn-outline-success"
[class.btn-outline-success]="!this.loggedInService.isLoggedIn"
[class.btn-outline-danger]="this.loggedInService.isLoggedIn"
ngIf ....>
{{this.loggedInService.isLoggedIn ? 'Exit' : 'Enter'}}
</a>
</li>
app.ts:
export class AppComponent implements OnInit {
constructor(public loggedInService: LoggedinService, public router: Router) {}
ngOnInit() {}
login(): void {
this.loggedInService.login().subscribe(() => {
if (this.loggedInService.isLoggedIn) {
let redirect = this.loggedInService.redirectUrl
? this.loggedInService.redirectUrl
: '/gallery';
this.router.navigate([redirect]);
}
});
}
logout(): void {
this.loggedInService.logout();
}
}
You can use ternary operator to run a function based on state like this
<li (click)="this.loggedInService.isLoggedIn ? logout() : logIn()" >
{{this.loggedInService.isLoggedIn ? logout : logIn}}
</li>
Let this logic move ts file and Just create one function toggleLogin() in ts file and call it from the html.
In HTML
<li class="nav-item">
<a class="btn btn-outline-success"
[class.btn-outline-success]="!this.loggedInService.isLoggedIn"
[class.btn-outline-danger]="this.loggedInService.isLoggedIn"
(click)="toggleLogin()"
>
{{this.loggedInService.isLoggedIn ? 'Exit' : 'Enter'}}
</a>
</li>
in ts file
toggleLogin(): void {
if(this.loggedInService.isLoggedIn){
this.logout();
}else{
this.login();
}
}
call a logInOrOut function that will check this.loggedInService.isLoggedIn and then call the appropriate function
Related
How to use debounce time in an angular input search while searching through the list of items.Should i use RXJS and how to put it in my function filterReports? Can you please show me in code what i should do?
Code here:
protected reports: Array<ReportGroupModel> = [];
protected filteredReports: Array<ReportGroupModel> = [];
constructor(
protected route: ActivatedRoute,
protected httpService: HttpService
) {
super(route);
this.titleIcon = 'fa-bar-chart';
this.titleSufixKey = 'reports';
this.filterReports = debounce(this.filterReports, 500);
}
filterReports(filter: any) {
const searchText = filter.target.value?.toUpperCase();
if (searchText) {
let newFilteredReports: Array<ReportGroupModel> = [];
for (let reportGroup of this.reports) {
let foundItems = reportGroup.items.filter(x => x.title.toUpperCase().includes(searchText));
if (foundItems && foundItems.length > 0) {
newFilteredReports.push(new ReportGroupModel(reportGroup.header, foundItems));
}
}
this.filteredReports = newFilteredReports;
}
else
this.filteredReports = this.reports;
}
ngOnInit() {
super.ngOnInit();
this.filteredReports = this.reports;
}
and here is html
<div class="d-flex">
<input search="text" class="form-control mw-200p ml-auto" (keyup)="component.filterReports($event)" autofocus placeholder="{{ 'search' | translate }}"/>
</div>
<div class="d-flex flex-wrap pl-2">
<div *ngFor="let report of component?.filteredReports" class="pr-5 pt-2" style="width: 350px;">
<h3>{{report.header | translate}}</h3>
<ul class="list-unstyled pl-1">
<li *ngFor="let item of report.items">
<i class="fa {{item.icon}} mr-h"></i>
<a class="link" [routerLink]="item.path"> {{item.title}} </a>
<p *ngIf="item.description" class="text-muted">{{item.description}}</p>
</li>
</ul>
</div>
</div>
The easiest way to solve this issue is to use the reactive form module. however, if you want to stick with ngModel, you can do something like this.
searchChanged = new Subject();
protected reports: Array<ReportGroupModel> = [];
protected filteredReports: Array<ReportGroupModel> = [];
and update the subject every time the keyup event update
<div class="d-flex">
<input search="text" class="form-control mw-200p ml-auto" (keyup)="onKeyUp($event)" autofocus placeholder="{{ 'search' | translate }}"/>
</div>
//.ts
onKeyUp(value: string) {
this.searchChanged.next(value)
}
Now, you can use searchChanged subject to debounce the event update
constructor(
protected route: ActivatedRoute,
protected httpService: HttpService
) {
...
this.searchChanged.pipe(
debounceTime(300)
// You better add takeUntil or something to unsubscribe this observable when this component destroy
).subscribe(value => {
this.filterReports(value);
})
}
I am building an account information page. I want to allow edit and save the changes (ie: name), but i get the same old name back. My code is as follows
<div >
<mat-form-field class="simple-form-field-50" *ngIf="isEditEnable">
<input matInput placeholder="Name" [(ngModel)]="name">
</mat-form-field>
<span *ngIf="!isEditEnable">Name : {{user?.givenName}} </span>
<button style="border:0;" *ngIf="!isEditEnable" (click)="onEdit()"><span><mat-icon style="font-size:16px;" matSuffix>create</mat-icon></span></button>
<button *ngIf="isEditEnable" mat-raised-button color="primary" (click)="onEdit()">Submit</button>
</div>
TS code:
export class ProfileComponent implements OnInit {
user: User;
constructor(
private http: HttpClient,
) { }
isEditEnable = false;
ngOnInit() {
this.http.get<User>('/api/user/details', {})
.subscribe((user) => {
this.user = user;
});
}
onEdit() {
this.isEditEnable = !this.isEditEnable;
}
}
Code Ouput:
[1] (https://imgur.com/nxiExeH.png)
After clicking edit button:
[2] (https://imgur.com/599SIF4.png)
After clicking submit, it gives old name back without changing
[3] (https://imgur.com/nxiExeH.png)
Ok, it looks like you never update the name property on the user, as well as remember to update the user in database (for this you'll need to add an endpoint)
<div >
<mat-form-field class="simple-form-field-50" *ngIf="isEditEnable">
<input matInput placeholder="Name" [(ngModel)]="name">
</mat-form-field>
<span *ngIf="!isEditEnable">Name : {{user?.givenName}} </span>
<button style="border:0;" *ngIf="!isEditEnable" (click)="onEdit()">
<span><mat-icon style="font-size:16px;" matSuffix>create</mat-icon></span>
</button>
<button *ngIf="isEditEnable" mat-raised-button color="primary" (click)="onEdit()">
Submit
</button>
</div>
export class ProfileComponent implements OnInit {
user: User;
name: string;
constructor(
private http: HttpClient,
) { }
isEditEnable = false;
ngOnInit() {
this.http.get<User>('/api/user/details', {})
.subscribe((user) => {
this.user = user;
});
}
// assume you have or will add an endpoint to handle user update
updateUser(user) {
this.http.put((`/api/user/${this.user.id}`, this.user))
}
onEdit() {
if (this.isEditEnable && this.user.givenName !== this.name) {
// assign new name to the user
this.user.givenName = this.name;
// use api update the user in your database
this.updateUser()
}
this.isEditEnable = !this.isEditEnable
}
}
The following goes to backend portion (assume you use node with express maybe?)
Your router probably has file that is named users.js, there you have a get endpoint defined as following
this.router.get('details', someFunctionThatGetsUser);
You will need to add another one
this.router.put('/:id', functionToUpdateUserInDB);
and add a function in your user service that updates it in DB
I'm writing Angular 2 application and inside it I have dropdown menu written on Bootstrap
<li class="dropdown" dropdown>
<a class="dropdown-toggle" data-toggle="dropdown">
User <span class="caret"></span>
</a>
<ul class="dropdown-menu" aria-labelledby="download">
<li><a routerLink="/user/profile">My Profile</a></li>
<li><a (click)="logout()">Log Out</a></li>
</ul>
</li>
All what I want is to write down a small directive for toggling menu. End here is it:
#Directive({
selector: "[dropdown]"
})
export class DropdownDirective implements OnInit {
private isOpen = false;
private defaultClassName: string;
#HostListener('click') toggle() {
let that = this;
if (!this.isOpen) {
this.elRef.nativeElement.className = this.defaultClassName + " open";
document.addEventListener("click", () => {
that.elRef.nativeElement.className = that.defaultClassName;
that.isOpen = false;
document.removeEventListener("click");
});
this.isOpen = !this.isOpen;
}
}
constructor(private elRef: ElementRef) {
}
ngOnInit(): void {
this.defaultClassName = this.elRef.nativeElement.className;
}
}
Looks good. But doesn't work. After short debug I found that event listener, which was added to the document, fires just after it has been assigned.
document.addEventListener("click", () => {
that.elRef.nativeElement.className = that.defaultClassName;
that.isOpen = false;
document.removeEventListener("click");
});
As a fact menu closing just after it has been opened. How to fix it and why this happening?
I've solved this same situation with a #HostListener(). On the component holding the dropdown:
#HostListener('document:click', ['$event'])
private clickAnywhere(event: MouseEvent): void {
if (this.IsSelected && !this.elementRef.nativeElement.contains(event.target)) {
this.IsSelected = false;
}
}
this.IsSelected is the binding property I use to show the dropdown.
The condition in the if() is checking whether the user has clicked on the menu or the document body in general.
Make sure to inject elementRef into the constructor so you can access the rendered HTML to check if that is what was clicked:
public constructor(private elementRef: ElementRef) { }
You can find out more about HostListener here.
I have a navigation: Log in, Sign up, etc.
I have implemented sign up with Google in angular 2 and after I go through Google I want that my navigation dynamically changed on Logout, etc.
My nav in app.component.html
<ul id="navigation-menu">
<li routerLinkActive="active"><a routerLink="/about">About</a></li>
<li routerLinkActive="active"><a routerLink="/contact_us">Contact us</a></li>
<li routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}" *ngIf="logged">
<a routerLink="/login" class="loginLink">Log in</a>
</li>
<li routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}" *ngIf="logged">
<a routerLink="/signin" class="signLink">Sign up</a>
</li>
<li routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}" *ngIf="!logged">
<a routerLink="/uprofile">Profile</a>
</li>
<li routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}" *ngIf="!logged">
<a routerLink="/bprofile">BProfile</a>
</li>
<li *ngIf="!logged"><a routerLink="/login" class="loginLink" (click)="logout()">Logout</a></li>
</ul>
In my app.component.ts I use lifecycle hook ngDoCheck and check localStorage. If it is not empty, I change navigation.
My app.component.ts
export class AppComponent implements DoCheck {
logged: boolean = true;
changeMenuLink() {
if (localStorage.getItem("currentUser")) {
this.logged = false;
}
}
ngDoCheck() {
this.changeMenuLink();
}
When I enter via Google, page redirect to the search page, but nav doesn't change. Menu changes only after clicking on the logo or on another menu item.
fb-gplus-api.component.ts
public auth2: any;
public googleInit() {
gapi.load('auth2', () => {
this.auth2 = gapi.auth2.init({
client_id: 'APP_ID.apps.googleusercontent.com', // your-app-id
cookiepolicy: 'single_host_origin',
scope: 'profile email'
});
this.attachSignin(document.getElementById('googleBtn'));
});
}
public attachSignin(element) {
this.auth2.attachClickHandler(element, {},
(googleUser) => {
let profile = googleUser.getBasicProfile();
let userToken: SocialLogin = new SocialLogin();
userToken.uid = profile.getId();
userToken.token = googleUser.getAuthResponse().id_token;
this.httpToken.postToken(userToken)
.toPromise()
.then(resp => {
if (resp.status === 'OK') {
this.checkStatus(userToken);
}
})
},
(error) => {
alert(JSON.stringify(error, undefined, 2));
}
);
}
checkStatus(user) {
let token = this.randomToken.generateToken(40);
localStorage.setItem('currentUser', JSON.stringify({uid: user.uid, token: token}));
alert("Login success! Have a nice day!");
this.router.navigate(['/search']);
}
ngAfterViewInit(){
this.googleInit();
}
I think the problem with the change of menu starts after use ngAfterViewInit(). I really don't understand how to solve this problem. How can I do this?
Regards
That's happen because you are doing some action outside of ngZone. To solve this issue first import ngZone:
import {NgZone} from "#angular/core";
then inject it into component that doing async call for google login:
constructor(private zone: NgZone)
finally run the handling of all angular2 variables that you doing in callback inside ngzone:
(googleUser) => {
this.zone.run( () => {
....
});
}
I have Component which has a member array variable. This array is bind to DOM with *ngFor. When I add new variable to array my view changes accordingly. Array holds tab names and initially it is set to have only 1 tab. When I refresh page array reinitialized which is what I was expecting. But when I logout and then log back in(router navigation) I see all previous tabs. It is weird to me, because if I console.log(myTabs) array has only 1 element(homeTab).
UPDATE:
.html
<div style="display: table-caption" id="notify-tabs">
<ul class="nav nav-tabs" role="tablist" id="nav-bar">
<li role="presentation" data-toggle="tab" id="homeTab" [class.active]="activeTab==='homeTab'"><a (click)="setValues('home')">Home</a>
<li role="presentation" *ngFor="let tab of myTabs" data-toggle="tab" id={{tab}} [class.active]="activeTab===tab.toString()"><a (click)="setValues(tab)">{{tab}}</a>
</ul>
</div>
.component.ts
#Component({
selector: 'notify-homepage',
templateUrl: 'app/home/home.component.html',
styleUrls: ['styles/css/bootstrap.min.css', 'styles/home.css'],
directives: [DynamicComponent, TileComponent, MapComponent, HeaderComponent, ConversationComponent, ROUTER_DIRECTIVES]
})
export class HomeComponent{
public myTabs: number[] = [21442];
public activeTab: string = 'homeTab';
ngOnInit() {
//Assume fully operating MapService here
this.subscription = this.mapService.conversationId.subscribe(
(id: number) => {
this.myTabs.push(id);
this.setValues(id);
this.activeTab = id.toString();
})
}
ngOnDestroy() {
this.subscription.unsubscribe();
...
}
}
map.service.ts
#Injectable()
export class MapService {
private conversationIdSource = new ReplaySubject<number>();
public conversationId = this.conversationIdSource.asObservable();
...
showConversation(id: number) {
this.conversationIdSource.next(id);
}
}
The answer of #Andrei works, but in my opinion there's a better and more elegant solution.
Just use a combination of #ViewChild() and setters.
For example:
// component.html
<ng-el ... #myElement>
// component.ts
#ViewChild('myElement') set(el) {
if (el) {
console.log('element loaded!');
}
}
Check Lifecycle hooks:
OnChanges https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html#!#onchanges
DoCheck https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html#!#docheck
They help tracking changing in Input and local variables.
OnChanges for Input variables:
ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
for (let propName in changes) {
let chng = changes[propName];
let cur = JSON.stringify(chng.currentValue);
let prev = JSON.stringify(chng.previousValue);
this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
}
}
DoCheck for everything:
ngDoCheck() {
if (this.hero.name !== this.oldHeroName) {
this.changeDetected = true;
this.changeLog.push(`DoCheck: Hero name changed to "${this.hero.name}" from "${this.oldHeroName}"`);
this.oldHeroName = this.hero.name;
}
}