ngIf doesn't evaluate async code Angular and Firebase - javascript

I have a property in my AuthService wich tell you if you are log in or not.
export class AuthService {
token: string;
isLogIn = new Subject<boolean>();
When I login correctly I set the isLogIn to true in the other hand when I click logout I set the property to false. I have this logic in my AppComponent(the first component been loaded).
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User is signed in.
this.authService.user = new User(user.displayName, user.email, user.photoURL, user.uid);
console.log(user);
this.isLoading = false;
this.authService.isLogIn.next(true);
} else {
// No user is signed in.
console.error('no user is login');
this.authService.isLogIn.next(false);
}
});
In my HeaderComponent I have:
export class HeaderComponent implements OnInit {
isActivated = false;
constructor(private authService: AuthService) { }
ngOnInit() {
this.authService.isLogIn.asObservable().subscribe(
(data: boolean) => {
this.isActivated = data;
}
);
// this.isActivated = this.authService.isLogin();
}
}
the template:
<ng-template [ngIf]="!isActivated">
<a class="nav-item nav-link text-white anchor-hover"
routerLink="/login"
routerLinkActive="active font-weight-bold">
<i class="fa fa-sign-in pr-2" aria-hidden="true"></i>Ingresar
</a>
</ng-template>
<ng-template [ngIf]="isActivated">
<!--DROPDOWN PROFILE-->
<div class="btn-group" role="group" *ngIf="authService.user">
<button id="btnGroupDrop1" type="button" class="btn btn-light dropdown-toggle pointer" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
{{authService.user.displayName}}
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="btnGroupDrop1">
<a class="dropdown-item" href="#">Dropdown link</a>
<a class="dropdown-item" href="#">Dropdown link</a>
</div>
</div>
<a class="nav-item nav-link text-white anchor-hover"
routerLink="/dashboard"
routerLinkActive="active font-weight-bold">
<i class="fa fa-sign-in pr-2" aria-hidden="true"></i>Dashboard
</a>
</ng-template>
What I'm approching here is that when the user is log in I show him a dashboard button and hide the login button but this is only seen if you refresh the page or chage the current route. I don't want to change the route or refresh the page to see differents UI components depending if you are log in or not.
Now I let you the entire files:
AppRouting Module:
import { NgModule } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import {HomeComponent} from './shared/home/home.component';
import {LoginComponent} from './public/login/login.component';
import {DashboardComponent} from './auth/dashboard/dashboard.component';
const routes: Routes = [
{path: '', component: HomeComponent},
{path: 'login', component: LoginComponent},
{path: 'dashboard', component: DashboardComponent}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule{}
AppComponent.ts
import {Component, OnInit} from '#angular/core';
import * as firebase from 'firebase';
import {AuthService} from './public/services/auth.service';
import {User} from "./auth/models/user.model";
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
isLoading = true;
constructor(private authService: AuthService) {}
ngOnInit() {
// this.authService.isLogIn.next(localStorage.length > 0);
firebase.auth().onIdTokenChanged(
(data) => {
console.log('TOKEN HAS CHANGED', data);
if(data) {
data.getIdToken().then(
(tk) => {
this.authService.token = tk;
}
);
} else {
console.log('You don\'t have a token yet, please login...');
// TODO this means that the user is new or have logout.
}
}
);
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User is signed in.
this.authService.user = new User(user.displayName, user.email, user.photoURL, user.uid);
console.log(user);
this.isLoading = false;
this.authService.isLogIn.next(true);
} else {
// No user is signed in.
console.error('no user is login');
this.authService.isLogIn.next(false);
}
});
// check localstorage, so we can see if there is a logged user
if (this.authService.getLocalUserInfo()) {
this.authService.isLogIn.next(true);
} else {
this.authService.isLogIn.next(false);
}
}
}
AppComponent Template:
<div>
<app-header></app-header>
</div>
<div>
<router-outlet></router-outlet>
</div>
AppHeader.ts
import { Component, OnInit } from '#angular/core';
import {AuthService} from '../../public/services/auth.service';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit {
isActivated = false;
constructor(private authService: AuthService) { }
ngOnInit() {
this.authService.isLogIn.asObservable().subscribe(
(data: boolean) => {
this.isActivated = data;
}
);
// this.isActivated = this.authService.isLogin();
}
}
AppHeader Template
<nav class="navbar navbar-expand-lg navbar-dark bg-custom-nav px-5">
<a class="navbar-brand" routerLink="/">MovieApp</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="container-fluid">
<div class="collapse navbar-collapse d-lg-flex justify-content-lg-between row" id="navbarNavAltMarkup">
<div class="navbar-nav px-sm-4 px-lg-0">
<a class="nav-item nav-link text-white"
routerLink="/"
routerLinkActive="active font-weight-bold"
[routerLinkActiveOptions]="{exact: true}">Inicio</a>
</div>
<div class="col-lg-8">
<app-search-bar></app-search-bar>
</div>
<div class="row">
<ng-template [ngIf]="!isActivated">
<a class="nav-item nav-link text-white anchor-hover"
routerLink="/login"
routerLinkActive="active font-weight-bold">
<i class="fa fa-sign-in pr-2" aria-hidden="true"></i>Ingresar
</a>
</ng-template>
<ng-template [ngIf]="isActivated">
<!--DROPDOWN PROFILE-->
<div class="btn-group" role="group" *ngIf="authService.user">
<button id="btnGroupDrop1" type="button" class="btn btn-light dropdown-toggle pointer" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
{{authService.user.displayName}}
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="btnGroupDrop1">
<a class="dropdown-item" href="#">Dropdown link</a>
<a class="dropdown-item" href="#">Dropdown link</a>
</div>
</div>
<a class="nav-item nav-link text-white anchor-hover"
routerLink="/dashboard"
routerLinkActive="active font-weight-bold">
<i class="fa fa-sign-in pr-2" aria-hidden="true"></i>Dashboard
</a>
</ng-template>
</div>
</div>
</div>
</nav>
AuthService.ts
import * as firebase from 'firebase';
import {Router} from '#angular/router';
import { Injectable } from "#angular/core";
import {Subject} from "rxjs/Subject";
import {User} from "../../auth/models/user.model";
#Injectable()
export class AuthService {
token: string;
isLogIn = new Subject<boolean>();
user: User;
constructor(private router: Router){}
signinWithFacebook() {
const provider = new firebase.auth.FacebookAuthProvider();
provider.addScope('user_location');
return firebase.auth().signInWithPopup(provider)
.then(
(res) => {
console.log(res);
this.getTokenId();
localStorage.setItem('userInfo', JSON.stringify(firebase.auth().currentUser.providerData[0]));
const userInfo = firebase.auth().currentUser.providerData[0];
this.user = new User(userInfo.displayName, userInfo.email, userInfo.photoURL, userInfo.uid);
this.isLogIn.next(true);
this.router.navigate(['/dashboard']);
}
);
}
getTokenId() {
firebase.auth().currentUser.getIdToken()
.then(
(tk) => {
return this.token = tk;
}
);
}
logout() {
return firebase.auth().signOut();
// handle in component
}
getLocalUserInfo(): boolean {
if(localStorage.getItem('userInfo')) {
const transformStoredUser = JSON.parse(localStorage.getItem('userInfo'));
this.user = new User(transformStoredUser.displayName, transformStoredUser.email, transformStoredUser.photoURL, transformStoredUser.uid);
return true;
} else {
return false;
}
}
isLogin():boolean {
if (localStorage.getItem('userInfo')) {
return true;
}
return false;
}
}
You can find the full project at: https://github.com/lucasdavidferrero/movieApp
One more thing, I've used the async pipe but the template doesn't reflect the chages. In the github project you'll find that I'm using async in the template but still the same bug. I hope someone could help me.

I'm going to try to help/answer, mind you there is a lot of code to sift through, so I may have missed something obvious. Anyway to hopefully neaten up your code and point you to the right direction you may want to look into the AsyncPipe.
I want to first modify your AuthService to get inline with some better practices, by first changing isLogIn = new Subject<boolean>(); to private _isLogIn = new Subject<boolean>();. We don't want to share the Subject to other components that will modify it, so everywhere in our AuthService you'll just need to add the underscore... Finally to export our Subject to be used we can add a get function like this:
get isLogIn(): Observable<boolean> {
return this._isLogIn.asObservable();
}
And now we don't need to chain the asObservable() to where ever we use it.
Also we want to move where you watch for changes in your user state from the AppComponent to your AuthService. Then you can instantiate the service in the constructor of your AppModule so your service will start to watch for changes in the auth state as soon as your app starts up.
You may also run into an issue where the resolve of the auth state may be falling out of zone, so while this may not be necessary you can/should import NgZone from core and pass it into your constructor of your AuthService constructor(private router: Router, private _zone: NgZone) { } so when you update the value of the auth state you can wrap it in this function:
this._zone.run(() => {
// your code here
}
Now if I was to write your header.component.ts I would have done something like this:
export class HeaderComponent implements OnInit {
constructor(private authService: AuthService) { }
ngOnInit() {}
get isActivated(): Observable<boolean> {
return this.authService.isLogIn;
}
}
So here I am just passing the observable straight to our template with a get function. So in the template I would implement the AsyncPipe like so:
<div *ngIf="!(isActivated | async) else user">
<a class="nav-item nav-link text-white anchor-hover" routerLink="/login"
routerLinkActive="active font-weight-bold">
<i class="fa fa-sign-in pr-2" aria-hidden="true"></i>Ingresar
</a>
</div>
<ng-template #user>
<!--DROPDOWN PROFILE-->
<div class="btn-group" role="group" *ngIf="authService.user">
<button id="btnGroupDrop1" type="button" class="btn btn-light dropdown-toggle pointer" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
{{authService.user.displayName}}
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="btnGroupDrop1">
<a class="dropdown-item" href="#">Dropdown link</a>
<a class="dropdown-item" href="#">Dropdown link</a>
</div>
</div>
<a class="nav-item nav-link text-white anchor-hover"
routerLink="/dashboard"
routerLinkActive="active font-weight-bold">
<i class="fa fa-sign-in pr-2" aria-hidden="true"></i>Dashboard
</a>
</ng-template>
The AsyncPipe will automatically subscribe and unsubscribe to your Observable and handle the refresh/zone updates.
Now I gave some overview/basic changes I would do to make sure the code behaves as expected. I invite you to try this and see if your *ngIfs work more "neatly." However you may also want to look up if else in Angular... => https://stackoverflow.com/a/43006589/5076023

Related

Angular 12 ngx-translate / loader isn't working

https://www.npmjs.com/package/#ngx-translate/core
https://www.npmjs.com/package/#ngx-translate/http-loader
I installed translate package from here and added ngx-translate module with its forRoot , translate service inside component ts (app & header) but it isn't working also
Header component
<header>
<nav class="navbar navbar-expand-lg navbar-light" id="header">
<div class="container">
<a class="navbar-brand" href="#">
<img src=".." alt="logo">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item" [routerLinkActive]="['active']" [routerLinkActiveOptions]="{ exact: true }">
<a [routerLink]="['home']" class="nav-link">Home</a>
</li>
<li class="nav-item dropdown" [routerLinkActive]="['active']" [routerLinkActiveOptions]="{ exact: true }">
<a class="nav-link dropdown-toggle" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
page 2
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" [routerLink]="['page']">page</a></li>
</ul>
</li>
<li class="nav-item" [routerLinkActive]="['active']" [routerLinkActiveOptions]="{ exact: true }">
<a class="nav-link" [routerLink]="['features']">Features</a>
</li>
<li class="nav-item" [routerLinkActive]="['active']" [routerLinkActiveOptions]="{ exact: true }">
<a class="nav-link reserve-btn" [routerLink]="['reserve']">Reserve</a>
</li>
</ul>
</div>
<button (click)="translate.use('en')">
En
</button>
<button (click)="translate.use('ar')">
Ar
</button>
</div>
</nav>
</header>
Header.component.ts
import { DOCUMENT } from '#angular/common';
import { Component, HostListener, Inject, OnInit } from '#angular/core';
import { TranslateService } from '#ngx-translate/core';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'],
})
export class HeaderComponent implements OnInit {
// lang: string;
constructor(#Inject(DOCUMENT) private document: Document,public translate:TranslateService) {}
ngOnInit(): void {
// this.lang = localStorage.getItem('lang') || 'en';
}
}
app.module.ts
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { GeneralPagesModule } from './general-pages/general-pages.module';
import { RentalPageModule } from './rental-page/rental-page.module';
import { SharedModule } from './shared/shared.module';
import { BrowserAnimationsModule } from '#angular/platform-browser/animations';
import { TranslateModule, TranslateLoader } from '#ngx-translate/core';
import { TranslateHttpLoader } from '#ngx-translate/http-loader';
import { HttpClientModule, HttpClient, HTTP_INTERCEPTORS } from '#angular/common/http';
export function HttpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, './assets/i18n/', '.json');
}
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
SharedModule,
GeneralPagesModule,
RentalPageModule,
BrowserAnimationsModule,
// TranslateModule,
HttpClientModule,
TranslateModule.forRoot({
defaultLanguage:'en',
loader: {
provide: TranslateLoader,
useFactory: createTranslateLoader,
deps: [HttpClient]
}
}),
],
providers: [
// { provide: HTTP_INTERCEPTORS, useClass: HttpInterceptorService, multi: true },
],
bootstrap: [AppComponent]
})
export class AppModule { }
export function createTranslateLoader(http:HttpClient){
return new TranslateHttpLoader(http,'./assets/i18n/','.json')
}
I also tried same steps in app.component.ts
import { Component, Inject } from '#angular/core';
import * as AOS from 'aos';
import { DOCUMENT } from '#angular/common';
import { TranslateService } from '#ngx-translate/core';
import { ActivatedRoute, NavigationEnd, Router } from '#angular/router';
import { filter} from 'rxjs/operators';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
ngOnInit(){
AOS.init({
duration: 1200,
});
}
// Inject document which is safe when used with server-side rendering
constructor(#Inject(DOCUMENT) private document: Document,public translate:TranslateService,
private router: Router, private route: ActivatedRoute) {
}
}
in home.compoent.html
{{"HOME" || translate}}
home.compoent.ts
import { Component, OnInit } from '#angular/core';
import { TranslateService } from '#ngx-translate/core';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
constructor(public translate:TranslateService) { }
ngOnInit(): void {
}
}
The pipe syntax is {{ 'my-translation-key' | translate }} - you have double || in your template, please change and try again.
Alternatively you can inject the translate service and use myTanslation = this.translateService.instant('my-translation-key') if you need to set a variable in your controller.
I Face the same problem and nothing works i tried all the ways it's was issue of angular/core12.0.0. I waste 3 days in it. It was working fine in angular 11 but no one angular 12. So Create custom pipe. :)
import { Pipe, PipeTransform } from '#angular/core';
import { TranslatePipe } from "#ngx-translate/core";
#Pipe({
name: 'myTranslate',
pure: false
})
export class MyTranslate extends TranslatePipe implements PipeTransform {
transform(value: any, args?: any[]): any {
return super.transform(value, args)
}
}
{{ myvalue | myTranslate }

Why props given from parent don't actualise in child VueJS?

I am a beginner in VueJS and I am facing a problem.
Here in main.js I pass the user variable to App.vue via a props.
By default its value is {}
In main.js the getLoginStatus() method listens to the firebase authentication status, when a user logs in or out main.js.this.user changes.
user in main.js is passed to App.vue thanks to template, but when it changes as a result of the getLoginStatus() call it is not changed in App.vue (the child), whereas in main.js it does change.
How can this be done?
Thanks
My main.js :
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import firebase from "firebase";
Vue.config.productionTip = false
var firebaseConfig = {
CONFIGURATION
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
firebase.analytics();
new Vue({
el: '#app',
router,
template: '<App :user="user" ></App>',
components: { App },
data(){
return {
user : {}
}
},
methods:{
getLoginStatus(){
firebase.auth().onAuthStateChanged(function(u) {
if (u) {
this.user = u
console.log("// User is signed in by Phone Number : ", u.phoneNumber)
} else {
this.user = null
console.log("// No user is signed in.")
}
});
console.log("this.user new value : "+this.user);
},
},
updated(){
this.getLoginStatus()
},
created(){
this.getLoginStatus()
}
})
App.vue :
<template>
<div id="app">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<router-link class="nav-link" to="/">Home</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" to="/register">Register/About old</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" to="/dashboard">Dashboard</router-link>
</li>
<li class="nav-item">
<button class="nav-link btn" #click="logout">Logout</button>
</li>
</ul>
</div>
</nav>
<div class="container mt-5">
<router-view />
</div>
<p v-if="user">logged{{user}}</p>
<p v-else>not logged{{user}}</p>
</div>
</template>
<script>
import firebase from 'firebase'
export default {
name: 'app',
props: ['user'],
methods:{
logout() {
firebase
.auth()
.signOut()
.then(() => {
alert('Successfully logged out');
if(this.$router.currentRoute.path!='/'){
this.$router.push('/')
}
})
.catch(error => {
alert(error.message);
if(this.$router.currentRoute.path!='/'){
this.$router.push('/')
}
});
},
},
}
</script>
You're using an unbound function here:
firebase.auth().onAuthStateChanged(function(u) {
So when setting this.user inside this function, this is not pointing to your Vue instance. You can either .bind(this) your function or just use an arrow function which will auto bind:
firebase.auth().onAuthStateChanged(u => {

ERROR TypeError: Cannot read property 'items' of null. for displaying total quantity of items in my Shopping Cart

I am having problems in displaying total number of items in my shopping-cart.I am getting data from firebase as an object and use one of its properties to get i.e 'quanity' to get total quanity of items and then displaing it on the ShoppingCart badge on my top navbar.
shopping-cart.service.ts
import { map, take } from 'rxjs/operators';
import { AdminProducts } from './models/admin-products';
import { Injectable } from '#angular/core';
import { AngularFireDatabase } from '#angular/fire/database';
import { ShoppingCart } from './models/shopping-cart';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class ShoppingCartService {
constructor(private db:AngularFireDatabase) {
}
private create(){
return this.db.list('/shopping-cart').push({
dateCreated: new Date().getTime()
});
}
async getCart(): Promise<Observable<ShoppingCart>> {
let cartId = await this.getOrCreateCartId();
return this.db
.object('/shopping-carts/' + cartId).valueChanges()
.pipe(map((x:any) => new ShoppingCart(x.items))); // Error: Cannot read property 'items' of null.
}
private getItem(cartId: string, productId: string){
return this.db.object('/shopping-cart/' + cartId + '/items/' + productId);
}
private async getOrCreateCartId(): Promise<string> {
let cartId = localStorage.getItem('cartId');
if (cartId) return cartId;
let result = await this.create();
localStorage.setItem('cartId', result.key);
return result.key;
}
addToCart(product: AdminProducts){
this.updateQuantity(product, 1);
}
removeFromCart(product: AdminProducts){
this.updateQuantity(product, -1);
}
async updateQuantity(product: AdminProducts, change: number){
let cartId = await this.getOrCreateCartId();
let item$ = this.getItem(cartId, product.key);
item$.valueChanges().pipe(take(1)).subscribe((item) => {
if (item) {
item$.update({quantity: item['quantity'] + change});
} else {
item$.set({ product, quantity: 1 });
}
});
}
}
shopping-cart.ts
import { ShoppingCartItem } from './shopping-cart-item';
export class ShoppingCart {
items :ShoppingCartItem[];
constructor(public itemsMap: { [productId:string]: ShoppingCartItem }) {
this.itemsMap = itemsMap || {};
for (let productId in itemsMap)
this.items.push(itemsMap[productId]);
}
get totalItemCount(){
let count = 0;
for (let productId in this.itemsMap)
count += this.itemsMap[productId].quantity;
return count;
}
}
shopping-cart-item.ts
import { AdminProducts } from "./admin-products";
export interface ShoppingCartItem {
products: AdminProducts;
quantity: number;
}
top-navbar.component
import { ShoppingCartService } from './../shopping-cart.service';
import { AutheService } from './../services/authe.service';
import { Component, OnInit } from '#angular/core';
import { ShoppingCart } from '../models/shopping-cart';
import { Observable } from 'rxjs';
#Component({
selector: 'top-navbar',
templateUrl: './top-navbar.component.html',
styleUrls: ['./top-navbar.component.css']
})
export class TopNavbarComponent implements OnInit {
cart$: Observable<ShoppingCart>;
constructor(public authe: AutheService, private cartService: ShoppingCartService) {
}
async ngOnInit(){
this.cart$ = await this.cartService.getCart();
}
logout(){
this.authe.logout();
}
}
top-navbar.component.html
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
<div class="container-fluid">
<a class="navbar-brand" routerLink="/">TheStore</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
<ul class="navbar-nav me-auto mb-2 mb-md-0">
<ng-template #anonymousUser>
<li class="nav-item">
<a class="nav-link" routerLink="/login">login</a>
</li>
</ng-template>
<li class="nav-item">
<a class="nav-link" routerLink="/shopping-cart">
Shopping Cart
<span class="badge bg-warning text-dark badge-pill" *ngIf="cart$ | async as cart">
{{ cart.totalItemCount}}
</span>
</a>
</li>
<li ngbDropdown *ngIf = "authe.user$ | async as user; else anonymousUser" class="nav-item dropdown">
<a ngbDropdownToggle class="nav-link dropdown-toggle" id="dropdown01" data-bs-toggle="dropdown" aria-expanded="false">
{{user.displayName}}</a>
<div ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdown01">
<a class="dropdown-item" routerLink="/my/orders">My Orders</a>
<a class="dropdown-item" routerLink="/admin/orders">Manage Orders</a>
<a class="dropdown-item" routerLink="/admin/products">Manage Products</a>
<a class="dropdown-item" (click)= "logout()">Log Out</a>
</div>
</li>
</ul>
<form class="d-flex">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
</div>
</div>
</nav>

Modal does not open: TypeError: Cannot read property 'open' of undefined

I am trying to display a pop up that I tried to implement as modal, but I get the error:
TypeError: Cannot read property 'open' of undefined
I created the pop up as component:
import { Component, OnInit, ViewChild, ElementRef, Input } from '#angular/core';
import User from '../types/user'
#Component({
selector: 'app-modal',
templateUrl: './chat-user-profile.component.html',
styleUrls: ['./chat-user-profile.component.css']
})
export class ChatUserProfileComponent {
me: User;
ngOnInit() {}
#ViewChild('ChatUserProfile') modal: ElementRef;
open() {
this.modal.nativeElement.style.display = 'block';
}
close() {
this.modal.nativeElement.style.display = 'none';
}
}
<div #myChatUserProfile class="container">
<div class="content">
<p>Some content here...</p>
<div class="text-center mb-2">
<h4><span class="badge badge-pill">{{me?.username}}</span></h4>
</div>
<!-- <button (click)="save()">Save</button> -->
<button (click)="close()">Close</button>
</div>
</div>
Now I try to open it on clicking a button
#Component({
selector: 'app-nav',
templateUrl: './nav.component.html',
styleUrls: []
})
export class NavComponent {
isLoggedIn = false;
constructor(private amplifyService: AmplifyService, public router: Router) {
this.amplifyService.authStateChange$.subscribe(authState => {
const isLoggedIn = authState.state === 'signedIn' || authState.state === 'confirmSignIn';
if (this.isLoggedIn && !isLoggedIn) {
router.navigate(['']);
} else if (!this.isLoggedIn && isLoggedIn) {
router.navigate(['/chat']);
}
this.isLoggedIn = isLoggedIn;
});
}
public signOut() {
this.amplifyService.auth().signOut();
}
//Stuff for Profile Button
#ViewChild('app-modal') modal: ChatUserProfileComponent
openModal() {
this.modal.open();
}
}
<nav class="navbar navbar-toggleable-md navbar-inverse navbar-dark bg-dark fixed-top">
<a class="navbar-brand text-white" routerLink='/'>
<img src="../../assets/img/chat.png" width="30" height="30" class="d-inline-block align-top" alt="http://freepngimg.com/png/11489-chat-free-download-png">
<strong>ChatQL</strong>
</a>
<!--STUFF TO INTEGRATE THE POP UP-->
<app-modal #modal></app-modal>
<button (click)="openModal()">Open Modal</button>
<!------------->
<ul class="nav navbar-nav">
<li *ngIf="isLoggedIn" class="nav-item">
<button class="btn btn-primary" (click)="signOut()">Sign Out <i class="ion-log-in" data-pack="default" data-tags="sign in"></i></button>
</li>
</ul>
</nav>
I also tried adding the modal to the constructor but that gave me a No Provider error.
I am based on the chatQL project and using this as template for the pop up. I think something is wrong when initializing but I can not figure out where exactly.
In your HTML code, you have used template reference #modal
<app-modal #modal></app-modal>
So in typescript file you have to replace '#ViewChild('app-modal')' with '#ViewChild('modal')' like below
#ViewChild('modal') modal: ChatUserProfileComponent
Currently, you are using the wrong template reference so you are getting this undefined error because there is no template with reference 'app-modal'.

Binding a spinner href method in nav bar to a view-model to publish a message for subscriber

I'm trying to implement a web application with aurelia and typescript.
I started from the aurelia-skeleton-typescript-webpack project as basis.
In the nav-bar i have also inserted a spinner for choose various city location, which should call a method which in turn publishes a message so that in the app.js a subscriber should display the view of the corresponding city.
I have implemented the view nav-bar-spinner.html and the view-model nav-bar-spinner.ts, which creates the spinner in the view nav-bar.html. The nav-bar.html is then inserted in the app.html as a template.
Each spinner item has a method that calls the publishLocation('city'), wich are bindet with the view-model nav-bar-spinner.ts.
Now when i click on a spinner item i receive the error: "Error: publishNavLocation is not a function"
I thing is a binding problem. I make a custom instantiation from object nav-bar-spinner in app.ts.
How i can do this binding correct?
I would be glad for some tips.
Here the code:
app.html
<template>
<require from="nav-bar.html"></require>
<nav-bar router.bind="router"></nav-bar>
<div class="page-host">
<div class="row">
<router-view swap-order="after" class="col-md-12"></router-view>
</div>
</div>
</template>
app.ts
import {Redirect} from 'aurelia-router';
import {Router, RouterConfiguration} from 'aurelia-router';
import {EventAggregator} from 'aurelia-event-aggregator';
import {inject} from 'aurelia-framework';
import {NavBarSpinner} from './nav-bar-spinner';
#inject(EventAggregator)
export class App
{
navBarSpinner;
constructor(private ea: EventAggregator)
{
this.navBarSpinner = new NavBarSpinner('hello world')
}
router : Router;
configureRouter(config: RouterConfiguration, router: Router)
{
config.title = 'bbv AmbientMonitor';
config.map([
{ route: '', name: 'login', moduleId: './login', nav: true, title: 'Anmeldung' },
{ route: 'live-view-all', name: 'live-view-all', moduleId: './live-view-all', nav: true, title: 'Live-Ansicht' },
{ route: 'live-view-zg', name: 'live-view-zg', moduleId: './live-view-zg', nav: true, title: 'Live-Ansicht' },
.
.
.
.
.
.
{ route: 'historical-view', name: 'historical-view', moduleId: './historical-view', nav: true, title: 'Historie-Ansicht'}
]);
this.router = router;
}
attached()
{
this.ea.subscribe('nav::toggleLogin', (data) =>
{
console.log('Subscribe data is: ' + data.nav);
this.router.navigateToRoute(data.nav);
});
}
}
nav-bar.html
<template bindable="router">
<require from="./nav-bar-spinner"></require>
<!-- <require from="nav-bar-spinner.html"></require> -->
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#skeleton-navigation-navbar-collapse">
<span class="sr-only">Toggle Navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">
<i class="fa fa-home"></i>
<span>${router.title}</span>
</a>
</div>
<div class="collapse navbar-collapse" id="skeleton-navigation-navbar-collapse">
<ul class="nav navbar-nav">
<div class="pull-left">
<compose class="nav navbar-nav" view="nav-bar-spinner.html" view-model.bind="navBarSpinner"></compose>
</div>
<li repeat.for="row of router.navigation" class="${row.isActive ? 'active' : ''}">
<a data-toggle="collapse" data-target="#skeleton-navigation-navbar-collapse.in" href.bind="row.href">${row.title}</a>
</li>
</ul>
<!-- <ul class="nav navbar-nav navbar-right">
<li class="loader" if.bind="router.isNavigating">
<i class="fa fa-spinner fa-spin fa-2x"></i>
</li>
</ul> -->
</div>
</nav>
</template>
nav-bar-spinner.html
<template bindable="navBarSpinner">
<li class="dropdown">
<div as-element="compose" view-model.bind="navBarSpinner"></div>
Standort <span class="caret"></span>
<ul class="dropdown-menu">
<li>Zug</li>
<li>Zürich</li>
<li>Bern</li>
<li>Luzern</li>
<li>München</li>
</ul>
</li>
</template>
nav-bar-spinner.ts
import { EventAggregator } from 'aurelia-event-aggregator';
import { inject } from 'aurelia-framework';
import { View } from "aurelia-framework";
#inject(EventAggregator)
export class NavBarSpinner {
ea;
constructor(msg) {
this.ea = new EventAggregator();
}
publishNavLocation(navToCity) {
this.ea.publish('nav::toggleLogin', {nav: navToCity});
console.log("Method call publishLocationZug()");
}
}
Your problem lies with the following line:
view-model.bind="navBarSpinner"
Aurelia doesn't process this correctly. It is the name of the class, but you need to address it differently in an HTML attribute.
view-model.bind="nav-bar-spinner"
This tells Aurelia to look for a class named NavBarSpinner.
P.S. I also recommend you look into how Aurelia Dependency Injection works, you have quite some unnecessary (and false) code right now.
Thanks for the tips. After reading better the aurelia doc i resolved my problem.
Here my changes for typescript:
Chanded inject with autoinject
import { autoinject } from 'aurelia-framework';`
-
-
-
#autoinject( EventAggregator )
export class NavBarSpinner {
constructor(private ea: EventAggregator) {}
-
-
-
and in nav-bar.html i inserted the nav-bar-spinner.html with the binding view-model="nav-bar-spinner"
<div class="pull-left">
<compose class="nav navbar-nav" view-model="nav-bar-spinner"></compose>
</div>
and removed the other unnecessary bindings and requirements.

Categories