#Input variables inside UI components return null - javascript

Hint: Im very new to angular. I hope this question gets answered, as its the most detailed explanation of my issue I can give with my current knowledge/vocabulary in Angular.
I have the following Setup:
My App component defines a PageNavigation array and fills it in the constructor:
PageNavigation Interface
import {Route} from "#angular/router";
export interface PageNavigation {
displayName : string;
route : Route;
}
App Component
import {Component} from '#angular/core';
import {Router} from "#angular/router";
import {PageNavigation} from "src/app/navigation/page-navigation";
import {TemplatesComponent} from "src/app/pages/templates/templates.component";
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.less']
})
export class AppComponent {
pageNavigations: PageNavigation[];
constructor(public router: Router) {
this.pageNavigations = [
{displayName: "Templates", route: {path: "templates", component: TemplatesComponent}}
]
this.pageNavigations.forEach(pageNavigation => { //<-- I use the Array for Routing but that's probably not interesting for my problem
this.router.config.push(pageNavigation.route);
});
}
}
I then want to create a Sidebar inside my App Component, and hand over the array
SideBar Component
import {Component, Input, OnInit} from '#angular/core';
import {PageNavigation} from "src/app/navigation/page-navigation";
import {Router} from "#angular/router";
#Component({
selector: 'tara-sidebar',
templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.less']
})
export class SidebarComponent {
public selectedRoute?: any;
constructor(public router : Router) { }
#Input('pageNavigations') pageNavigations : PageNavigation[] | any;
navigate(): void {
alert(this.selectedRoute);
// this.router.navigate([this.selectedRoute]).then(function () {
// // success
// }, function () {
// // error
// });
}
}
The corresponding html:
app.component.html
<tara-sidebar [pageNavigations]="pageNavigations"></tara-sidebar>
<router-outlet></router-outlet>
In the app.component.html I just "hand" the values over
sidebar.component.html
<p-listbox [options]="pageNavigations" [(ngModel)]="selectedRoute" optionLabel="displayName" optionValue="route" (ngModelChange)="navigate()" ></p-listbox>
In the SideBar, I create a prime-ng listbox. It displays the correct Label (in my case "Templates"). However, clicking on it, triggering the navigate() method, I get an alert with either "null" or "[object] [object]" (it alternates between the two, meaning first click = null, second = [object] [object], third = null,...
If possible, can you please explain me whats going on, as I expected to either always get null (with Input beeing "triggered" after the navigate()), or the code to just be working and giving me the data I have clicked on.
Clicked on Templates first time
Clicked on Templates secondtime
I have already tried changing the variable type of selectedRoute from any to PageNavigation, but without success it just was giving me undefined in the alert.

This is can happen because an alert gets the old value of selectedRoute. Try to do this:
navigate(value): void {
alert(value);
}
and inside a template:
(ngModelChange)="navigate($event)"

As the returned type of the listbox wasnt PageNavigation but route, when I used alert(value.path) I was presented with a value. However, it still works only once and after that returns null and throwing an error...

Related

Why 'emit' do not show a name only the first time I click a button?

I want to use eventemiter to send a name when a click a button but does not work. The first time I click the button the name is not showed but if a click again then the name appears.
https://stackblitz.com/edit/angular-ue9dzk-extrange-emit
app.component.html
<hello *ngIf="name"></hello>
<show></show>
app.component.ts
import { Component } from '#angular/core';
import { NameService } from './name.service';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
})
export class AppComponent {
name: string;
constructor(private nameService: NameService) {
}
ngOnInit() {
this.nameService.show.subscribe(name => {
this.name = name;
})
}
}
hello.component.ts
import { Component, Input } from '#angular/core';
import { NameService } from './name.service';
#Component({
selector: 'hello',
template: `<h1>Hello {{name}}!</h1>`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent {
name: string;
constructor(private nameService: NameService) {
}
ngOnInit() {
this.nameService.show.subscribe(name => {
this.name = name;
})
}
}
name.service.ts
import {EventEmitter} from '#angular/core';
export class NameService {
show = new EventEmitter();
}
show.component.ts
import { Component, Input } from '#angular/core';
import { NameService } from './name.service';
#Component({
selector: 'show',
template: `<button (click)="show()">Click to emit</button>`,
styles: [`h1 { font-family: Lato; }`]
})
export class ShowComponent {
name: string;
constructor(private nameService: NameService) {
}
show() {
this.nameService.show.emit('World');
}
}
EventEmitter is intended for use with #Output() bindings, because it has the option to emit values asynchronously. Don't use it with shared services.
<hello *ngIf="name"></hello>
The above creates the component when a value is emitted. After the value is emitted the component will subscribe to the service, but EventEmitter does not keep a history of previously emitted values. So nothing is received by the component.
See constructor:
https://angular.io/api/core/EventEmitter#constructor
It is rare that you need this feature, but there are cases were an #Output() binding needs to emit a value during the next change detection cycle. So in those cases you need to use EventEmitter, but you can use other Observable subjects as emitters for bindings.
but if a click again then the name appears
After the component is created it will receive emitted values, and the second time you click the button a second value is emitted which appears in the template of the component.
Most people who create a shared service will use BehaviorSubject with a default value, or ReplaySubject if they need to wait until a value can be emitted.
In the above example you have the logic *ngIf="name" which means you want to wait until a value is emitted. So you would use a new ReplaySubject(1) instead of EventEmitter.
export class NameService {
show = new ReplaySubject(1);
}
That's because you are using *ngIf on hello app selector. Which will not initialize the hello component until the name variable in app.component.ts has value in it. So as you click on emit first time the name variable will be assigned a value first then your hello component will be initialized.
Then after your second click the name variable inside hello app will get assigned value "world" and then it will be displayed.
Solution is as below.
<hello>
<label *ngIf="name">
{{name}}
</label>
</hello>
<show></show>

Angular Portal cdkPortal directive throw Expression Changed error

I use Angular Material Portal to move element in another place.
I use cdkPortal and cdkPortalOutlet.
I don't understand why angular throw expression changed in this super simple example
import { Component, ViewChild } from '#angular/core';
import { CdkPortal } from '#angular/cdk/portal';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
#ViewChild(CdkPortal, { static: false }) portal: CdkPortal
}
Check code here:
https://stackblitz.com/edit/angular-f6sb21
open console to see error:
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'portal: undefined'. Current value: 'portal: [object Object]'
You are referencing the same object to the cdkPortalOutlet. You actually need another ng-template with its ViewChild
<ng-template #templatePortalContent>Hello, this is a template portal</ng-template>
and in the ts file:
// This is the reference for the ng-template that we added
#ViewChild("templatePortalContent", { static: false }) templatePortalContent: TemplateRef<any>;
// This is the variable that will hold the new template
templatePortal;
constructor(private _viewContainerRef: ViewContainerRef, private cdr: ChangeDetectorRef) {}
ngAfterViewInit() {
this.templatePortal = new TemplatePortal(
this.templatePortalContent,
this._viewContainerRef
);
this.cdr.detectChanges();
}
This is for the TemplatePortal.
If you need a ComponentPortal, it means if you have already a component, then you will need to create the portal and assign it in the cdkPortalOutlet instead of templatePortal variable.
this.componentPortal = new ComponentPortal(ComponentPortalExample);
and
<ng-template [cdkPortalOutlet]="componentPortal"></ng-template>
You can check here for more info:
Here is the demo.

Is it possible to determine which modal / component I will show when I click on a component?

I'm working on small Angular 5 project, and I have a simple component which is representing an food product and it looks like this:
[![enter image description here][1]][1]
This component is nested component, it is part of my main component.
When I click on this component, quantity component/modal is shown:
<app-product-item (onInfoEdit)="InfoModal.show($event)"></app-product-item>
But now I would like to show another modal if some condition is satisfied, for example
If condition is satisfied then show this quantity modal (which is component itself) otherwise show some another modal (which is another component), so:
<app-product-item "if something is satisfied than this code on the right is ok otherwise lets display another modal with different method" (onInfoEdit)="InfoModal.show($event) (onSomethingElse)="AnotherModal.show($event)></app-product-item>
So I'm not sure if this is even possible because I need to show 2 different modals ( different components ) on same component's click, so basically if product has some property defined than show quantity info, otherwise show product info, and quantity info and product info are separated components..
Thanks guys
Cheers
You would be looking at something like this:
<app-product-item #productItem
(click)="productItem?.product?.property ? InfoModal.show($event) : AnotherModal.show($event)"
(onInfoEdit)="InfoModal.show($event)"
(onSomethingElse)="AnotherModal.show($event)">
</app-product-item>
where "property" would be a property of "product", which would be an object defined inside your Product Item Component.
Please also note that the conditional statements inside templates are not recommended and should be carried to the component.
You can do it by creating component which will conditionally render one of two components. One condition will load info component and other condition will load quantity component. Let's call this conditional modal something like: food-modal, inside food-modal.component.html template the code could look like:
<h1>CONDITIONAL MODAL CONTAINER</h1>
<ng-container *ngIf="product.name === 'Chicken'">
<app-info [product]="product"></app-info>
</ng-container>
<ng-container *ngIf="product.name === 'Cow'">
<app-quantity [product]="product"></app-quantity>
</ng-container>
And inside food-modal.component.ts the code could look like:
import { Component, OnInit, Input } from '#angular/core';
import { Product } from '../models/Product.model';
#Component({
selector: 'app-food-modal',
templateUrl: './food-modal.component.html',
styleUrls: ['./food-modal.component.css']
})
export class FoodModalComponent implements OnInit {
#Input()
public product: Product;
constructor() {}
ngOnInit() {}
}
All you have to do now is call this component where you want it and pass in Product model. For sake of demonstration I'll put everything inside app component in order to load food-modal. The app.component.html could look like:
<app-food-modal [product]="chickenProduct">
</app-food-modal>
And inside app.component.ts code could be like this:
import { Component } from '#angular/core';
import { Product } from './models/Product.model';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
public chickenProduct: Product;
constructor() {
this.chickenProduct = {
id: 1,
quantity: 2,
name: 'Chicken'
};
}
}
Now app component will pass Product object named chickenProduct to food-modal component and bind it to modal's product property. After that the conditional rendering will happen.
Rest of the code could look like:
info.component.html:
<p>
This modal is for INFO. Product name is: {{ product.name }}
</p>
info.component.ts:
import { Component, OnInit, Input } from '#angular/core';
import { Product } from '../models/Product.model';
#Component({
selector: 'app-info',
templateUrl: './info.component.html',
styleUrls: ['./info.component.css']
})
export class InfoComponent implements OnInit {
#Input()
public product: Product;
constructor() { }
ngOnInit() {
}
}
quantity.component.html:
<p>
This modal is for QUANTITY. Product name is: {{ product.name }}
</p>
quantity.component.ts:
import { Component, OnInit, Input } from '#angular/core';
import { Product } from '../models/Product.model';
#Component({
selector: 'app-quantity',
templateUrl: './quantity.component.html',
styleUrls: ['./quantity.component.css']
})
export class QuantityComponent implements OnInit {
#Input()
public product: Product;
constructor() { }
ngOnInit() {
}
}
product.model.ts:
export interface Product {
id: number;
quantity: number;
name: string;
}
I've implemented solution like this and everything worked fine! Change name property of product to Cow and conditional fire will happen and load quantity component, take it back to Chicken and conditional fire will load info component.
I assume you just wanted to know how to trigger conditional firing, so that's why I've done this by hard coding string, checking whether name of product is Chicken or Cow.

Angular 4/5 Observable - How to update component template depending on observable property?

Ok I am still a newbie. I have successfully created a 'dashboard' component that has a left sidebar with links. On the right is where I have content/components displayed that I want to change dynamically depending on what link was clicked on the left sidebar (see bootstrap sample of what this dashboard looks like here, click on the toggle button to view the sidebar: https://blackrockdigital.github.io/startbootstrap-simple-sidebar/).
I have created a DashboardService that has a Subject and an Observable to allow for sibling component communication. This works great since I have a console.log() that shows this communication working (when I click on link on sidebar in SidebarComponent, I console.log() a value 'emitted' by the DashboardService that is being listened to by the SidebarComponent's sibling, DashboardSectionComponent).
The problem that I am having is that the template in DashboardSectionComponent loads the correct component section ONLY on initial load of page - once I click on a link on the side bar the content is blank and nothing is rendered.
Here is the service that allows the componenent communication:
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class DashboardService {
private selectedComponentAlias = new Subject<string>();
constructor() {}
setSelectedComponentAlias(alias: string) {
this.selectedComponentAlias.next(alias);
}
getSelectedComponentAlias(): Observable<string> {
return this.selectedComponentAlias.asObservable();
}
}
Here is the SidebarComponent:
import { Component, OnInit } from '#angular/core';
import { DashboardService } from '../dashboard.service';
#Component({
selector: 'app-sidebar',
templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.css']
})
export class SidebarComponent implements OnInit {
constructor(private dashboardService: DashboardService) { }
ngOnInit() {
}
onShowSection(event) {
event.preventDefault();
const componentAlias = event.target.getAttribute('data-componentAlias');
this.dashboardService.setSelectedComponentAlias(componentAlias);
}
}
here is the DashboardSectionComponent (the one that subscribes to the observable and I want to set property that controls the template views depending on the value that was caught)
import { Component, OnInit, OnDestroy } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
import { DashboardService } from '../dashboard.service';
#Component({
selector: 'app-dashboard-section',
templateUrl: './dashboard-section.component.html',
styleUrls: ['./dashboard-section.component.css']
})
export class DashboardSectionComponent implements OnInit, OnDestroy {
private subscrition: Subscription;
selectedComponentAlias: string = 'user-profile';
constructor(private dashboardService: DashboardService) {
}
ngOnInit() {
this.subscrition = this.dashboardService.getSelectedComponentAlias()
.subscribe((selectedComponentAlias: string) => {
this.selectedComponentAlias = selectedComponentAlias;
console.log('user clicked: ',this.selectedComponentAlias);
});
}
ngOnDestroy() {
this.subscrition.unsubscribe();
}
}
Finally here is the template for DashboardSectionComponent which might have wrong syntax:
<div *ngIf="selectedComponentAlias == 'my-cards'">
<app-cards></app-cards>
</div>
<div *ngIf="selectedComponentAlias == 'user-profile'">
<app-user-profile></app-user-profile>
</div>
<div *ngIf="selectedComponentAlias == 'user-settings'">
<app-user-settings></app-user-settings>
</div>
Again, this works great (selectedComponentAlias is 'user-profile' on page load by default). But it goes blank after I click on a Sidebar link....
Thanks.
this was easy - like #RandyCasburn pointed out, this was a matter of getting the routing working properly.

Two way binding on angular material slide toggle not working as expected (Angular 4)

I have implemented the angular material slide-toggle that all seems to work except for some reason it isn't binding the value to the relevant variable for some reason?
// other irrevelant imports above..
import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '#angular/material';
#Component({
selector: 'app-calendar',
templateUrl: './calendar.component.html',
styleUrls: ['./calendar.component.scss'],
host: {
'(document:click)': 'handleClickEvent($event)',
}
})
export class CalendarComponent implements OnInit {
filteringSchedule: boolean = false;
filteringSent: boolean = false;
filteringFailed: boolean = false;
}
// component
<mat-slide-toggle
class="calendar-filter-types"
[ngModel]="(filteringSchedule)"
[color]=""
[checked]="">
Scheduled : {{ filteringSchedule }}
</mat-slide-toggle>
Once I check or uncheck the toggle I would expect the filteringSchedule value to change to true or false accordingly? However within the component it always stays as false for some unknown reason - can anyone suggest why is this?
I am Angular 4
Just update your html to
[(ngModel)]="filteringSchedule"
This works on Angular8+
[(ngModel)]="filteringSchedule"
And make sure you already imported FormsModule
import {FormsModule} from '#angular/forms';
[ngModel]="(filteringSchedule)"
Change this to:
[(ngModel)]="filteringSchedule"

Categories