Angular Portal cdkPortal directive throw Expression Changed error - javascript

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.

Related

#Input variables inside UI components return null

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...

How to pass value of a binding variable to a method in the template on button clicked

I am learing how to make binding between the parent and the child using #Input, #Output and EventEmitter decorators.
in the html section posted below
<h1 appItemDetails [item]="currentItem">{{currentItem}}</h1>
currentItem has value equal to "TV". and i pass this value to the binding variable item.
i added console.log in ngOnInit that prints the value of item to make sure that the binding from the parent to the child is working as suppose to be.
in
<button (click) = addNewItem(item.value)></button>
in this button tag i am trying to pass the value of the binding variable item to the method addNewItem() as a parameter.
For the method addNewItem() it exists in the component and it should be invoked with the right parameter which is the value of the binding variable item
when i compile the App i revceive the error posted below.
please let me know how to pass the value of the binding variable to a method on button clicked
error
TS2339: Property 'item' does not exist on type 'AppComponent'.
2 <button (click) = addNewItem(item.value)></button>
~~~~
src/app/app.component.ts:5:16
5 templateUrl: './app.component.html',
~~~~~~~~~~~~~~~~~~~~~~
Error occurs in the template of component AppComponent.
app.component.ts:
import { Component, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'InputOutputBindings';
currentItem = 'TV';
#Output() newItemValue = new EventEmitter<string>();
addNewItem(val : string) {
this.newItemValue.emit(val);
console.log("add new item");
}
}
item-details.directive.ts:
import { Directive, Input, Output, EventEmitter } from '#angular/core';
#Directive({
selector: '[appItemDetails]'
})
export class ItemDetailsDirective {
#Input() item : string = "";
constructor() { }
ngOnInit() {
console.log("ngOnInit->:" + this.item)
}
}
app.coponent.html:
<h1 appItemDetails [item]="currentItem">{{currentItem}}</h1>
<button (click) = addNewItem(item.value)></button>
you can add exportAs like below:
#Directive({
selector: '[appItemDetails]'
exportAs: 'customdirective',
})
export class ItemDetailsDirective {
....
}
and in your html, you can add a reference to directive and get the binded value:
<h1 #test="customdirective" appItemDetails [item]="currentItem">{{currentItem}}</h1>
<button (click) = addNewItem(test.item)></button>

custom if directive does not show expected results

in this tutorial
https://www.sitepoint.com/practical-guide-angular-directives/
i am learning how to create a customised directive. i followed the steps as shown in the code posted below, but despite added the exact code as explained in the aforemenrtioned website, when i run the command
ng serve --open
i get something as shown in the image posted below.
please let me know why myCustomIf is not working. i say that myCustomIf is not working because what i got on the localhost:4200 is something as shown in the image posted
please let me know how to make the myCustomIf works as explained in the tutorial in the above posted link
app.component.ts:
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'ngDirective1';
name = 'Angular';
condition = false;
}
app.myCustomeIfDirective.ts:
import { Directive, Input, TemplateRef, ViewContainerRef } from '#angular/core';
#Directive({
selector: '[myCustomIf]'
})
export class MyCustomeIfDirective{
constructor(private templateRef: TemplateRef<any>,private viewContainer: ViewContainerRef){ }
#Input()
setMyCustomIf(condition : boolean) {
if(condition) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
}
app.module:
import { Directive, Input, TemplateRef, ViewContainerRef } from '#angular/core';
#Directive({
selector: '[myCustomIf]'
})
export class MyCustomeIfDirective{
constructor(private templateRef: TemplateRef<any>,private viewContainer: ViewContainerRef){ }
#Input()
setMyCustomIf(condition : boolean) {
if(condition) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
}
app.component.html:
<h1 my-error>Hello {{name}}</h1>
<h2 *myCustomIf="condition">Hello {{name}}</h2>
<button (click)="condition = !condition">Click</button>
image:
If you open console it should show smth like:
NG0303: Can't bind to 'myCustomIf' since it isn't a known property of
'h2'
An Angular structural directive, that is written in a short syntax(with *) and that takes one input or more inputs, must have an #Input with the same name as directive's attribute selector(other inputs follow another rule described here What is the exact grammar for Angulars structural directives), e.g.:
#Directive({
selector: '[anyAttr]'
})
export class MyCustomeIfDirective{
#Input()
anyAttr: any;
or
#Directive({
selector: '[anotherAttr]'
})
export class MyCustomeIfDirective{
#Input()
set anotherAttr(val: any) {}
Why is it so?
That's because *ngIf is just a shortcut for expanded version:
<ng-template [ngIf]="...">...
or
*anyAttr => <ng-template [anyAttr]="...">...
Now, let's look at your code:
#Directive({
selector: '[myCustomIf]'
})
export class MyCustomeIfDirective{
#Input()
setMyCustomIf(condition : boolean) {
Several things to notice:
setMyCustomIf is just a method in your case
if you convert it to a setter set MyCustomIf then MyCustomIf doesnt match myCustomIf because js is case-sensitive.
The solution is:
#Input()
set myCustomIf(condition : boolean) {
Ng-run Example
in your directive (app.myCustomeIfDirective.ts), you need to match the name of your input to the name of the directive (because the condition is passed with that attribute):
#Input("myCustomIf")
set myCustomIf(condition : boolean) {
if(condition) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
(note you can also change the name of the function to match the directive name)
stackblitz demo

Access text (not instance of another component) with ContentChild

How can I access a string of text given within the tags of a component
<my-custom-component>THIS TEXT</my-custom-component>
Within a template, I can use ng-content, or if it is an instance of some other class I can access it within the component definition like demonstrated in these examples. However I am interested in detecting if there is a string of text there or not, which I believe would make providedText undefined. However, I am always getting undefined.
#ContentChild(Element, { static: true }) providedText: Text | undefined;
I have tried Text as the first element passed to #ContentChild. Passing any will not work (I don't know why).
StackBlitz
I am interested mostly in finding if there is a string or undefined, but am also curious why ContentChild(Text... isn't working.
Edit:
I have added a potential solution, but it seems pretty imperfect, so I hope something better comes along.
Edit 2:
I now understand that #ContentChild is not a mechanism for selecting whatever native HTML I want without wiring it up to Angular’s dependency graph with a ref, directive, etc.
I am still curious if my proposed solution below is a bad idea for any reason.
My solution for now (since I wish to capture all transcluded content) is to wrap ng-content in a containing element, then get its innerText.
#Component({
selector: "app-parent",
template: `
<span #transcludedContainerRef>
<ng-content></ng-content>
</span>
`
})
export class ParentComponent implements AfterViewInit {
#ViewChild("transcludedContainerRef", { static: false })
transcludedContainerRef: ElementRef | undefined;
buttonText: string;
ngAfterViewInit() {
const isButtonTextPresent = this.transcludedContainerRef.nativeElement
.innerText;
if (isButtonTextPresent) {
console.log(isButtonTextPresent); // successfully logs content
}else {
console.log('No text set');
}
}
}
It does feel hacky, but it works. I am holding out for something better.
it's difficult if I don't know about your <my-custom-component>
In general if your custom component it's only
<ng-content></ng-content>
You can inject in constructor the elementRef
constructor(public el:ElementRef){}
From a parent
<hello >
Start editing to see some magic happen :)
</hello>
You can use
#ViewChild(HelloComponent,{static:false}) helloComponent:HelloComponent
click()
{
console.log(this.helloComponent.el.nativeElement.innerHTML)
}
If your component has any variable -or ViewContent-, you can access this variables in a similar way
So the other way to read the inner text from the component is that child component emit the value whatever it get's as input from other component. See below:
hello.component.ts
import { Component, Input, Output, EventEmitter, OnInit } from '#angular/core';
#Component({
selector: 'hello',
template: `<h1>Hello {{name}}!</h1>`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent implements OnInit {
#Input() name: string;
#Output() innerText: EventEmitter<string> = new EventEmitter();
ngOnInit() {
this.innerText.emit(this.name);
}
}
app.component.ts
import { Component, ContentChild, AfterContentInit, OnInit } from "#angular/core";
#Component({
selector: "app-parent",
template: "content from <code>app-parent</code>"
})
export class ParentComponent implements AfterContentInit {
#ContentChild(Element, { static: true }) providedText: Text | undefined;
ngAfterContentInit() {
console.log("ngAfterContentInit Content text: ", this.providedText);
}
}
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
name = "Angular";
_innerText: string;
ngAfterContentInit() {}
get childContent(): string {
return this._innerText;
}
set childContent(text) {
this._innerText = text;
}
innerTextFn(innertext: string) {
this.childContent = innertext;
console.log('Event: ', innertext);
}
}
app.component.html
<hello name="{{ name }}" (innerText)="innerTextFn($event)"></hello>
<app-parent>This is the content text</app-parent>
Here is stackblitz url to check: https://stackblitz.com/edit/angular-bacizp
I hope this may helpful for you and if yes then accept this as correct answer.

Angular #Output not working

Trying to do child to parent communication with #Output event emitter but is no working
here is the child component
import { Component, OnInit, Output, Input, EventEmitter } from '#angular/core';
#Component({
selector: 'app-emiter',
templateUrl: './emiter.component.html',
styleUrls: ['./emiter.component.css']
})
export class EmiterComponent implements OnInit {
#Output() emitor: EventEmitter<any>
constructor() { this.emitor = new EventEmitter()}
touchHere(){this.emitor.emit('Should Work');
console.log('<><><><>',this.emitor) // this comes empty
}
ngOnInit() {
}
}
this is the html template
<p>
<button (click)=" touchHere()" class="btn btn-success btn-block">touch</button>
</p>
The console.log inside the touchHere it shows nothing
even if I put this inside the parent component it show nothing as well
parent component
import { Component , OnInit} from '#angular/core';
// service I use for other stuff//
import { SenderService } from './sender.service';
// I dont know if I have to import this but did it just in case
import { EmiterComponent } from './emiter/emiter.component'
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
user: any;
touchThis(message: string) {
console.log('Not working: ${message}');
}
constructor(private mySessionService: SenderService) { }
}
and here is the html template
<div>
<app-emiter>(touchHere)='touchThis($event)'</app-emiter>
</div>
Parent component template:
<app-emitor (emitor)='touchThis($event)'></app-emiter>
In parent template #Output should be 'called', not the child method.
Also, see: https://angular.io/guide/component-interaction#parent-listens-for-child-event
Here’s an example of how we write a component that has outputs:
#Component({
selector: 'single-component',
template: `<button (click)="liked()">Like it?</button>`
})
class SingleComponent {
#Output() putRingOnIt: EventEmitter<string>;
constructor() {
this.putRingOnIt = new EventEmitter();
}
liked(): void {
this.putRingOnIt.emit("oh oh oh");
}
}
Notice that we did all three steps: 1. specified outputs, 2. created an EventEmitter that we attached
to the output property putRingOnIt and 3. Emitted an event when liked is called.
If we wanted to use this output in a parent component we could do something like this:
#Component({
selector: 'club',
template: `
<div>
<single-component
(putRingOnIt)="ringWasPlaced($event)"
></single-component>
</div>`
})
class ClubComponent {
ringWasPlaced(message: string) { console.log(`Put your hands up: ${message}`);
} }
// logged -> "Put your hands up: oh oh oh"
Again, notice that:
putRingOnIt comes from the outputs of SingleComponent
ringWasPlaced is a function on the ClubComponent
$event contains the thing that wasemitted, in this case a string
<app-emiter (emitor)="touchThis($event)" ></app-emiter>
By using #Output() you should apply the event you need to emit in the directive of the emitter component.Adding the name of the variable to the the directive and but the emitted over function inside the quotation passing the $event.
touchHere() is the method from which you are binding some value to emit with your EventEmitter. And your EventEmitter is 'emitor'.
So your code will work if you simply do the below:
<app-emiter (emitor)='touchThis($event)'></app-emiter>

Categories