angular 13 component does not show after clicking navigation button again - angular13

The scenario:
I have a navigation component that navigates to either one of two components depending on which link is clicked. For the writeup component i display a gridlist with cards initially. When a card is clicked, the gridlist is exchanged by the show component shown through the router outlet of the writeupcomponent. All good so far but...
what i'm expecting to happen:
The user clicks that navigation button again and the gridlist of cards is shown again.
what actually happens:
Nothing is shown. The router outlet remains black after navigating to the writeup component again. When i click the other navigation button, hence loading the backlog component and then click the writeup navigation button again, the gridlist is loaded normally.
navbar-component.html:
<mat-toolbar class="navbar navbar-dark bg-dark">
<mat-icon>menu</mat-icon>
<ng-container *ngIf="loggedIn ==='false' || loggedIn === null">
<button routerLink="register" class="btn btn-outline-info mx-1" type="button">Register</button>
<button routerLink="login" class="btn btn-outline-info mx-1" type="button">Login</button>
</ng-container>
<ng-container class="logged-in" *ngIf="loggedIn === 'true'">
<div class="sub-menu-left">
<!--<button (click)="openDialog()" class="btn btn-outline-info mx-1" type="button">New Backlog Item</button> -->
<button (click)="navigate('dashboard/backlog')" class="btn btn-outline-info mx-1" type="button">Backlog</button>
<button (click)="navigate('dashboard/writeup')" class="btn btn-outline-info mx-1" type="button">Write-up</button>
</div>
<div class="sub-menu-right">
<button mat-mini-fab color="warn" aria-label="logout button with icon"
(click)="logout()" class="btn btn-outline-info mx-1" type="button">
<mat-icon>logout</mat-icon>
</button>
<app-profile [profile]="profile"></app-profile>
</div>
</ng-container>
</mat-toolbar>
<router-outlet></router-outlet>
navbar-component.ts:
import { Component, EventEmitter, OnInit, Output, ViewChild } from '#angular/core';
import { Router, ActivatedRoute, ParamMap } from '#angular/router';
#Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.scss']
})
export class NavbarComponent implements OnInit {
constructor(
private router: Router
){}
navigate(route:any) {
return this.router.navigate([route]);
}
}
writeup-component.html:
<router-outlet></router-outlet>
writeup-component.ts:
import { Component, OnInit} from '#angular/core';
import { Router } from '#angular/router';
#Component({
selector: 'app-write-up',
templateUrl: './write-up.component.html',
styleUrls: ['./write-up.component.scss']
})
export class WriteUpComponent implements OnInit {
constructor( private router: Router) { }
ngOnInit(): void {
this.router.navigate(['dashboard/writeup/index'])
}
}
index-component.ts:
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { Writeup } from 'src/app/interfaces/writeup';
#Component({
selector: 'app-index',
templateUrl: './index.component.html',
styleUrls: ['./index.component.scss']
})
export class IndexComponent implements OnInit {
list: Writeup[] = [
{
id:1,
title:"title 1",
subTitle: "subtitle 1",
content: "some contnet",
link:"www.somelink.com"
},
{
id:2,
title:"title 1",
subTitle: "subtitle 1",
content: "some contnet",
link:"www.somelink.com"
},
{
id:3,
title:"title 1",
subTitle: "subtitle 1",
content: "some contnet",
link:"www.somelink.com"
}
]
constructor(private router:Router) { }
ngOnInit(): void {
}
showWriteUp(id:number) {
this.router.navigate(['dashboard/writeup/show'])
}
}
index-component.html:
<mat-grid-list class="writeup-grid-list" cols="3" gutterSize="10px">
<mat-grid-tile *ngFor="let writeup of list">
<mat-card (click)="showWriteUp(writeup.id)" class="writeup-card">
<mat-card-title>{{ writeup.title }}</mat-card-title>
<mat-card-subtitle>{{ writeup.subTitle }}</mat-card-subtitle>
<mat-card-content>
<p>{{ writeup.content }}</p>
</mat-card-content>
<mat-card-actions>
<button mat-button>{{ writeup.link }}</button>
</mat-card-actions>
</mat-card></mat-grid-tile>
</mat-grid-list>
app-routing.module.ts:
import { NgModule } from '#angular/core';
import { RouterModule, Routes } from '#angular/router';
...
import { WriteUpComponent } from './components/write-up/write-up.component';
import { BacklogComponent } from './components/backlog/backlog.component';
import { ShowComponent } from './components/writeup/show/show.component';
import { IndexComponent } from './components/writeup/index/index.component';
const routes: Routes = [
...
{ path: 'dashboard', component: DashboardComponent,canActivate: [AuthGuardGuard],
children: [
{ path: 'backlog', component: BacklogComponent },
{ path: 'writeup', component: WriteUpComponent,
children: [
{ path: 'index', component: IndexComponent },
{ path: 'show', component: ShowComponent }
]
},
]
}
];
#NgModule({
imports: [RouterModule.forRoot(routes,{onSameUrlNavigation: 'reload'})],
exports: [RouterModule]
})
export class AppRoutingModule { }
show-component is not implemented yet but 'it works'

After using this snippet as a route debug tool:
import { Component } from '#angular/core';
import { Router, Event, NavigationStart, NavigationEnd, NavigationError} from '#angular/router';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.sass']
})
export class AppComponent {
currentRoute: string;
constructor(private router: Router) {
this.currentRoute = "Demo";
this.router.events.subscribe((event: Event) => {
if (event instanceof NavigationEnd) {
this.currentRoute = event.url;
console.log(event);
}
});
}
}
i found the problem. Turned out i was being routed to dashboard/writeup instead of dashboard/writeup/index, i'm still not sure why it does work after loading another component. I think it has something to do with the writeup component not using the onInit method anymore. I removed the entire writeup component, just using path: 'writeup' in app-routing.module.ts. It was redundant :
{ path:'register', component: RegisterComponent},
{ path: 'login', component: LoginComponent },
{ path: 'dashboard', component: DashboardComponent,canActivate: [AuthGuardGuard],
children: [
{ path: 'backlog', component: BacklogComponent },
{ path: 'writeup',
children: [
{ path: 'index', component: IndexComponent },
{ path: 'show', component: ShowComponent }
]
},
]
}
];

Related

Angular Jasmine Karma Background Image Component Test

I am new to Angular JS and I would like to test if the background image that I have on my header component is loaded correctly. However, on the console log, it says that the image file is not found.
Here's the whole code for your reference:
HTML:
**<div class="flex-container" [ngStyle]="{'background-image' : 'url('+ bgImage +')'}">**
<div class="text-container">
<h1>{{ title }}</h1>
<p>{{ description }}</p>
<a [href]="buttonLink" target="_blank"><button>{{ buttonText }}</button></a>
</div>
</div>
Component.ts:
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {
#Input() title: string;
#Input() description: string;
#Input() buttonText: string;
#Input() buttonLink: string;
**#Input() bgImage: string;**
constructor() { }
ngOnInit(): void {
}
}
Component.spec.ts:
import { ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { HeaderComponent } from './header.component';
describe('HeaderComponent', () => {
let component: HeaderComponent;
let fixture: ComponentFixture<HeaderComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HeaderComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance;
});
it('should create', () => {
fixture.detectChanges();
expect(component).toBeTruthy();
});
it('should render all inputs', () => {
component.title = 'test'
component.description = 'test-description'
**component.bgImage = 'bgImageIllustrations.jpg'**
fixture.detectChanges();
const title = fixture.debugElement.query(By.css('h1')).nativeElement as HTMLElement
const description = fixture.debugElement.query(By.css('p')).nativeElement as HTMLElement
**const bgImage = fixture.debugElement.query(By.css('.flex-container')).nativeElement as HTMLElement;**
expect(title.textContent).toBe('test');
expect(description.textContent).toBe('test-description');
**console.log(getComputedStyle(bgImage).backgroundImage);**
});
});
Hope you can help me to fix this issue. Thanks!
I would like to see if I am loading the right background image

How to send array object input to firestore on Angular?

I'm just learning angular and trying to make a to-do-list app. I'm trying to send array object data to firestore. I have an array object input like this:
[
{
"isChecked": true
"title": "Todo 1",
},
{
"isChecked": true,
"title": "Todo 2"
}
]
I want to enter that into the input field. And here is my input field:
<form action="" #importForm="ngForm (ngSubmit)="importJson(importForm, $event)">
<div class="form-group">
<textarea ngModel name="importjson" #importjson="ngModel" class="form-control" id="exampleFormControlTextarea1" rows="10" required></textarea>
</div>
<button type="submit" class="btn btn-secondary" >Ok</button>
</form>
And this is my app component :
import { Component, ViewChild, OnInit, ElementRef } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { FormGroup } from '#angular/forms';
import { TodolistService } from '../../services/todolist.service';
import { Todolist } from '../../model/todolist.model';
export class TodolistComponent implements OnInit {
importjson: Todolist={};
constructor(private todolistService: TodolistService) { }
#ViewChild('editTitle', {static: false}) input: ElementRef;
ngOnInit(): void{
this.todolistService.getToDoList().subscribe(items => {
this.todos = items;
})
}
importJson(importForm: FormGroup, submit){
console.log(importForm.value.importjson);
this.todolistService.addImport(importForm.value.importjson);
this.importState = false;
}
}
And here is my app service:
import { Injectable } from '#angular/core';
import { AngularFireDatabase, AngularFireList } from '#angular/fire/database' ;
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from '#angular/fire/firestore';
import { Observable } from 'rxjs';
import { Todolist } from '../model/todolist.model';
#Injectable({
providedIn: 'root'
})
export class TodolistService {
itemsCollection: AngularFirestoreCollection<Todolist>;
items: Observable<Todolist[]>;
itemDoc: AngularFirestoreDocument<Todolist>;
constructor(private firebasedb: AngularFireDatabase, public firestore: AngularFirestore) {
this.itemsCollection = this.firestore.collection('titles');
this.items = this.itemsCollection.snapshotChanges().pipe(
map(actions => actions.map(a => {
const data = a.payload.doc.data() as Todolist;
data.id = a.payload.doc.id;
return data;
}))
);
}
addImport(item: Todolist) {
this.itemsCollection.add(item);
}
How can I do that?

Angular displaying correct array item in buttons

I have an array with 4 items, and 4 buttons on a dashboard. I want to assign item1 with button1, and item2 with button2 etc. Right now it displays 4 buttons for each "hero" for a total of 16 buttons. I tried {{hero.name[2]}} and similar things but that just grabs letters and not the actual array items. I would appreciate any help.
dashboard.component.html
<h3>Calibrations</h3>
<div class="grid grid-pad">
<a *ngFor="let hero of heroes" [routerLink]="['/detail', hero.id]">
<button style ="min-height: 70px" (click)="gotoClick(1)">{{hero.name}}</button>
<button style ="min-height: 70px" (click)="gotoClick(2)">{{hero.name}}</button>
<button style ="min-height: 70px" (click)="gotoClick(3)">{{hero.name}}</button>
<button style ="min-height: 70px" (click)="gotoClick(4)">{{hero.name}}</button>
</a>
</div>
dashboard.component.ts
import { Component, OnInit } from '#angular/core';
import { Hero } from '../hero.class';
import { HeroService } from '../hero.service';
import { StepService } from '../step.service';
#Component({
moduleId: module.id,
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
heroes: Hero[] = [];
constructor(private heroService: HeroService, private _stepService: StepService, private _teststepService: StepService) { }
ngOnInit() {
this.heroService.getHeroes().subscribe(heroes => this.heroes = heroes);
}
private test: number;
gotoClick(value: number){
this._stepService.setTest(value);
this._teststepService.apiURL();
}
}
hero.service.ts
import { Injectable } from '#angular/core';
import { Headers, Http, Response } from '#angular/http';
import { Hero } from './hero.class';
import { Observable } from "rxjs/Rx";
#Injectable()
export class HeroService {
private headers = new Headers({'Content-Type': 'application/json'});
private heroesUrl = 'api/heroes'; // URL to web api
constructor(private http: Http){ }
getHeroes(): Observable<Hero[]> {
return this.http.get(this.heroesUrl)
.map(response => response.json().data as Hero[]);
}
getHero(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/${id}`;
return this.http.get(url)
.map(response => response.json().data as Hero);
}
}
You can use the built-in index property of *ngFor:
<div class="grid grid-pad">
<a *ngFor="let hero of heroes; let i = index;" [routerLink]="['/detail', hero.id]">
<button style ="min-height: 70px" (click)="gotoClick(i)">{{hero?.name}</button>
</a>
</div>
Documentation: https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html

Cannot read property of undefined Angular 2

I am trying to do a simple import but I am getting a massive stack trace issue.
I have tried searching everywhere for issues related to this but to me, the stack trace doesn't provide much information.
EDIT: I have tried setting it a variable that isn't fetched from Firebase and it works fine. I guess the question now is how do I handle this information from Firebase so that it loads when it is ready.
Here are the relevant files:
main.ts:
import { bootstrap } from '#angular/platform-browser-dynamic';
import {AppComponent} from './app.component';
import { HTTP_PROVIDERS } from '#angular/http';
bootstrap(AppComponent, [HTTP_PROVIDERS]);
player.services.ts:
import { Injectable } from '#angular/core';
import {Player} from "../classes/player";
#Injectable()
export class PlayerService {
player: Player;
getPlayer()
{
return Promise.resolve(this.player);
}
createPlayer(uid: string, name: string, firebaseRef: Firebase)
{
this.player = {
'uid': uid,
'name': name,
'level': 1,
'maxHealth': 100,
'health': 100,
'maxEnergy': 50,
'energy': 50,
'fun': 1,
'skill': 1,
'knowledge': 1
}
firebaseRef.child('players').child(uid).set(this.player);
}
setPlayer(player: Player)
{
this.player = player;
}
}
app.component.ts
import { Component, OnInit } from '#angular/core'
import { PlayerDetailComponent } from './components/player-detail.component';
import {PlayerService} from "./services/player.service";
import {FirebaseEventPipe} from "./firebasepipe";
import {Player} from "./classes/player";
#Component({
selector: "my-app",
templateUrl: 'app/views/app.component.html',
directives: [PlayerDetailComponent],
providers: [PlayerService],
pipes: [FirebaseEventPipe]
})
export class AppComponent implements OnInit{
title = "title";
authData: any;
private firebaseUrl: string;
private firebaseRef: Firebase;
private loggedIn = false;
player: Player;
constructor(private playerService: PlayerService) {
this.firebaseUrl = "https://!.firebaseio.com/";
this.firebaseRef = new Firebase(this.firebaseUrl);
this.firebaseRef.onAuth((user) => {
if (user) {
this.authData = user;
this.loggedIn = true;
}
});
}
getPlayer() {
this.firebaseRef.once("value", (dataSnapshot) => {
if (dataSnapshot.child('players').child(this.authData.uid).exists()) {
this.firebaseRef.child('players').child(this.authData.uid).once("value", (data) => {
this.player = data.val();
this.playerService.setPlayer(this.player);
console.log(this.player);
});
} else {
this.playerService.createPlayer(this.authData.uid, this.getName(this.authData), this.firebaseRef);
this.playerService.getPlayer().then(player => this.player);
console.log(this.player);
}
});
}
ngOnInit() {
this.getPlayer();
}
authWithGithub() {
this.firebaseRef.authWithOAuthPopup("github", (error) =>
{
if (error) {
console.log(error);
}
});
}
authWithGoogle() {
this.firebaseRef.authWithOAuthPopup("google",(error) =>
{
if (error) {
console.log(error);
}
});
}
getName(authData: any) {
switch (authData.provider) {
case 'github':
return authData.github.displayName;
case 'google':
return authData.google.displayName;
}
}
}
player-detail.component.ts
import { Component, Input, OnInit } from '#angular/core';
import { Player } from '../classes/player';
#Component({
selector: "player-details",
templateUrl: "app/views/player-detail.component.html",
styleUrls: ['app/style/player-detail.component.css'],
})
export class PlayerDetailComponent implements OnInit{
#Input() player: Player;
ngOnInit() { console.log(this.player)}
}
app.component.html
<nav class="navbar navbar-default">
<div class="container">
<ul class="nav navbar-nav">
<li class="navbar-link">Home</li>
</ul>
</div>
</nav>
<div class="jumbotron" [hidden]="loggedIn">
<div class="container">
<h1>Angular Attack Project</h1>
<p>This is a project for the Angular Attack 2016 hackathon. This is a small project where set goals
in order to gain experience as a player and person. In order to begin, please register with on of the following services</p>
<button class="btn btn-social btn-github" (click)="authWithGithub()"><span class="fa fa-github"></span>Sign Up With Github </button>
<button class="btn btn-social btn-google" (click)="authWithGoogle()"><span class="fa fa-google"></span>Sign Up With Github </button>
</div>
</div>
<player-details [player]="player" [hidden]="!loggedIn"></player-details>
player-detail.component.html
<div id="player" class="panel panel-default">
<div id="player-stats" class="panel-body">
<img id="player-image" class="img-responsive" src="../app/assets/images/boy.png"/>
<div class="health-bars">
<div class="health-bar">HEALTH:<br/><progress value="{{ player.health }}" max="{{ player.maxHealth }}"></progress></div>
<div class="energy-bar">ENERGY:<br/><progress value="{{ player.energy }}" max="{{ player.maxEnergy }}"></progress></div>
<div class="player-attributes"><span class="fa fa-futbol-o player-attr fun">: {{ player.fun }} </span><span class="fa fa-cubes player-attr skill">: {{ player.skill }}</span> <span class="fa fa-graduation-cap player-attr knowledge">: {{ player.knowledge }}</span></div>
</div>
</div>
</div>
In your service you don't have to return with the promise. You can use a getter
private player: Player;
get CurrentPlayer()
{
return this.player;
}
Then in your component:
getPlayer() {
this.firebaseRef.once("value", (dataSnapshot) => {
if (dataSnapshot.child('players').child(this.authData.uid).exists()) {
this.firebaseRef.child('players').child(this.authData.uid).once("value", (data) => {
this.playerService.setPlayer(this.player);
console.log(this.player);
});
} else {
this.playerService.createPlayer(this.authData.uid, this.getName(this.authData), this.firebaseRef);
console.log(this.player);
}
});
ngOnInit() {
this.player = this.playerService.CurrentPlayer();
this.getPlayer();
}
If you setup the reference first, it should automatically update. You can also throw an *ngIf player-details component definition in the DOM and only show it once the player object isn't undefined.
Edit
Just saw someone else posted about *ngIf prior to me, so if that is the solution, please mark theirs.
The player variable was undefined when the PlayerDetailComponent was loaded therefore there was no such object as player.
To fix this, OnChanges can be implemented like this:
import { Component, Input, OnChanges, SimpleChange } from '#angular/core';
import { Player } from '../classes/player';
import {HealthBarComponent} from "./health-bar.component";
import {ChecklistComponent} from "./checklist.component";
#Component({
selector: "player-details",
templateUrl: "app/views/player-detail.component.html",
styleUrls: ['app/style/player-detail.component.css'],
directives: [HealthBarComponent, ChecklistComponent],
})
export class PlayerDetailComponent implements OnChanges{
#Input()
player: Player;
ngOnChanges(changes: {[propName: string]: SimpleChange}) {
}
}
and then we can add *nfIf="player" within the template to ensure that the player object isn't blank before loading the element.

Angular2. How to get access to the all level routeConfig elements form directives

I want to create a directives which should be check ACL defined on the routeConfig element by extend routerData
How can I get access to the routerData defined in routerConfig? (must have all levels of menu)
Example:
My ACL direvtives
#Directive({
selector: '[myAcl]'
})
export class MyAclDirective {
#Input('routerLink')
routeParams: any[];
/**
*
*/
constructor(private _elementRef: ElementRef, private router:Router) {
}
ngAfterViewInit(){
//Key name to find in 'routeConfig' acl definition of 'routerData'
let componentAliasName = this.routeParams;
//NEED TO GET ACCESS TO ROUTECONFIG DEFINITION
//
//Example:
// #RouteConfig([
// { path:'/user/...', name: 'UserLink', component: UserComponent, data: {res: 'user', priv: 'show'} } ])
// AND GET BY 'name' == 'componentAliasName' my extend acl definition "data: {res: 'user', priv: 'show'}"
console.log("CHECK ACL: " + data.res + " | " + data.priv);
//ACL service
let hasAccess = AclService.checkAccess(data.res, data.priv);
if (!hasAccess) {
let el : HTMLElement = this._elementRef.nativeElement;
el.parentNode.removeChild(el);
}
}
}
MainComponent
#RouteConfig([
{ path:'/home', name: 'HomeLink', component: HomeComponent, useAsDefault: true },
{ path:'/user/...', name: 'UserLink', component: UserComponent, data: {res: 'user', priv: 'show'} }
])
#Component({
selector: 'main-app',
template: `
<ul>
<li><a myAcl [routerLink]="['HomeLink']">Home</a></li>
<li><a myAcl [routerLink]="['UserLink']">User</a>
<ul>
<li><a myAcl [routerLink]="['ProfileLink']">Profile</a>
<ul>
<li><a myAcl [routerLink]="['SubProfileLink']">SubProfile</a>
</ul>
</li>
</ul>
</li>
</ul>
<router-outlet></router-outlet>
`,
directives: [ MY_ACL_DIRECTIVES, ROUTER_DIRECTIVES ],
})
export class MainComponent {
}
UserComponent
#RouteConfig([
{ path: '/profile/...', name: 'ProfileLink', component: ProfileComponent, data: {res: 'profile', priv: 'show'} }
])
#Component({
selector: 'user-id',
template: `<h1>User</h1>`,
directives: [ROUTER_DIRECTIVES, MY_ACL_DIRECTIVES]
})
export class UserComponent {
}
ProfileComponent
#RouteConfig([
{ path: '/subprofile', name: 'SubProfileLink', component: SubProfileComponent, data: {res: 'profile', priv: 'details'} }
])
#Component({
selector: 'profile-id',
template: `<h1>Profile</h1>`,
directives: [ROUTER_DIRECTIVES, MY_ACL_DIRECTIVES]
})
export class ProfileComponent {
}
SubProfileComponent
#Component({
selector: 'subprofile-id',
template: `<h1>Sub Profile</h1>`,
directives: [ROUTER_DIRECTIVES, MY_ACL_DIRECTIVES]
})
export class SubProfileComponent {
}
Related link
You should be able to use the CanActivate decorator. It would look something like this:
#Component({...})
#CanActivate((next, prev) => (hasAccess))
export class UserComponent {
// your component code
}
You can read more here: https://angular.io/docs/ts/latest/api/router/CanActivate-decorator.html

Resources