Angular - Dynamic size of component not being updated - javascript

I'm using an internal private reusable component. My problem is that the width is not being dynamically updated when the viewport is updated. Here are snippets of relevant code:
component.ts
export class Component {
modalWidth: string | undefined;
ngOnInit() {
this.breakpointServiceSubscription$ = this.breakpointService.breakpoint$.subscribe(() => {
if (this.breakpointService.isSmall()) {
console.log("small")
this.modalWidth = "50px";
}
else {
this.modalWidth = "500px";
}
}
}
component.html
<modal [width]="modalWidth">...</modal>
The width and height are supposed to change dynamically as the browser is resized, but it stays the same size as when it was rendered. If I open the modal in a specific viewport the size is always correct, it's only a problem once I am trying to resize with the modal open.
When logging the subscription to the breakpoint service, it is always correct and will log dynamically.
I've tried converting modalWidth and modalHeight to observables and using an async pipe in the html but it still has the same behaviour.
Any tips or suggestions?

you can inject ChangeDetectorRef in the component and after changing modalWidth, call changeDetectorRef.detectChanges() to let angular apply the change immediately to the view.
constructor(private cdr: ChangeDetectorRef) {}
ngOnInit() {
this.breakpointServiceSubscription$ = this.breakpointService.breakpoint$.subscribe(() => {
if (this.breakpointService.isSmall()) {
console.log("small")
this.modalWidth = "50px";
}
else {
this.modalWidth = "500px";
}
// apply change immediately
this.cdr.detectChanges();
}

Related

Angular variables

I've got a line of text right now, and when that line of text is being overflown something gets set to true which let's me load in a tooltip! Code below:
Template
<div>
<p #tooltip [tooltip]="/* ShowToolTipSomeHow? ? name : null */" delay="300">{{name}}</p>
</div>
This is where it should check if it should show the tooltip or not. As you can see it should somehow detect if the tooltip should be shown or not, I have no idea how and that's my question right now.
Component
#ViewChildren('tooltip') private tooltips!: QueryList<ElementRef>;
ngAfterViewInit(): void {
this.tooltips.changes.subscribe((tts: QueryList<ElementRef>) => {
tts.forEach((tooltip, index) => {
this.checkTooltipTruncated(tooltip);
});
});
}
private checkTooltipTruncated(tooltip: ElementRef) {
// Checks if the text has overflown
const truncated = this.isTextTruncated(tooltip);
if (truncated) {
// Change the ShowToolTipSomehow? value?
}
}
In the component it somehow changes some value that the tooltip can detect so that it can update itself to hide the tooltip. The additional problem is that it's not 1 tooltip to change, but infinite tooltips (so basically 1 or more).
My question is, how would I do this because I'm pretty stuck.
Create an array that holds boolean values and using the tooltip elements index set the value in the array.
ts:
#ViewChildren('tooltip') private tooltips!: QueryList<ElementRef>;
tooltipsVisible: boolean[];
ngAfterViewInit(): void {
if (!this.tooltipsVisible) {
this.tooltipsVisible = new Array(this.tooltips.length).fill(false);
}
this.tooltips.changes.subscribe((tts: QueryList<ElementRef>) => {
tts.forEach((tooltip, index) => {
this.checkTooltipTruncated(tooltip, index);
});
});
}
private checkTooltipTruncated(tooltip: ElementRef, index) {
// Checks if the text has overflown
const truncated = this.isTextTruncated(tooltip);
if (truncated) {
this.tooltipsVisible[index] = true;
// Change the ShowToolTipSomehow? value?
}
}
html:
<div>
<p #tooltip [tooltip]="tooltipsVisible[i] ? ...." delay="300">{{name}}</p>
</div>
You should create your p elements using *ngFor you can have the index available in your template...

Using ngClass on an element that uses getElementById, element is null

I've got an html element with overflow: auto, but I want to give it a border only when it's scrollable.
How do I evaluate the element's size from within ngClass without getting any kind of null errors?
Note: The element's enclosing div doesn't get rendered until after getting a response from an observable.
Attempt 1:
The html element is set up like this:
<div ngIf="!loading">
<div id="{{someID}}" [ngClass]="{'border-class': isScrollable}"> ... </div>
</div>
In my ngOnInit, I call a function to see if the given element can be scrolled.
ngOnInit() {
// this.loading gets set to false after an observable is returned
/* ... */
// scroll check
let e = document.getElementById(`${this.someID}`);
if (element !== null) {
this.isScrollable = e.scrollHeight > e.clientHeight;
}
}
If I don't check for null, I get errors. If I do check for null, then even if I have scrollable content, when the page is loaded, the border doesn't show up.
I thought the issue might be with this.loading, so I added the scroll check within the observable response, but after loading was set to false. Still no border.
Attempt 2:
<div #textDiv [ngClass]="{'border-class': isScrollable}"> ... </div>
#ViewChild('textDiv') element: ElementRef;
/* ... */
ngAfterViewInit() {
this.isScrollable = this.element.scrollHeight > this.element.clientHeight;
}
But the border still doesn't show up on scrollable content when the page is loaded.
Attempt 3:
The only thing that has worked, is this hot mess:
setTimeout( () => {
this.isScrollable = this.element.scrollHeight > this.element.clientHeight;
});
Is there a way I can get this to work without calling setTimeout?
The problem is here:
<div ngIf="!loading">
<div id="{{someID}}" [ngClass]="{'border-class': isScrollable}"> ...
</div>
The isScrollable is not updating the value after it changes.
You can fix this, using a get and returning the value when ngAfterViewInit was already executed:
export class CustomComponent implements AfterViewInit {
private afterViewInitExecuted = false;
#ViewChild('textDiv') element: ElementRef;
public get isScrollable() {
if(this.afterViewInitExecuted) {
return this.element.scrollHeight > this.element.clientHeight;
}
return false;
}
ngAfterViewInit() {
this.afterViewInitExecuted = true;
}
}
Then in your html:
<div ngIf="!loading">
<div id="{{someID}}" [ngClass]="{'border-class': isScrollable() }"> ...
</div>
With that, it should work.

Angular Dynamic Component Issue

I make a dynamic component in one of my components and it was made and here it's in the html I place it in the (ng-template) :
<div type="text" class="form-control" contenteditable="true" name="phrase" (input)="triggerChange($event)">
<ng-container #container></ng-container>
</div>
Code of triggerChange :
triggerChange(event) {
let newText = event.target.innerText;
this.updateLineAndParentLineAndCreateComponent(newText);
}
Which made what the function says literally update the line with the new text and update the parent component with this changes and also make the on the fly component
Code for create Component :
compileTemplate(line: any) {
// console.log(line[4]);
let metadata = {
selector: `runtime-component-sample`,
template: line[4]
};
let factory = this.createComponentFactorySync(this.compiler, metadata);
if (this.componentRef) {
this.componentRef.destroy();
this.componentRef = null;
}
this.componentRef = this.container.createComponent(factory);
let instance = <DynamicComponent>this.componentRef.instance;
instance.line = line;
instance.selectPhrase.subscribe(this.selectPhrase.bind(this));
}
private createComponentFactorySync(compiler: Compiler, metadata: Component, componentClass?: any): ComponentFactory<any> {
let cmpClass;
let decoratedCmp;
if (componentClass) {
cmpClass = componentClass;
decoratedCmp = Component(metadata)(cmpClass);
} else {
#Component(metadata)
class RuntimeComponent {
#Input() line: any;
#Output() selectPhrase: EventEmitter<any> = new EventEmitter<any>();
showEntities(lineIndex, entityIndex) {
this.selectPhrase.emit(entityIndex);
}
};
decoratedCmp = RuntimeComponent;
}
#NgModule({ imports: [CommonModule], declarations: [decoratedCmp] })
class RuntimeComponentModule { }
let module: ModuleWithComponentFactories<any> = compiler.compileModuleAndAllComponentsSync(RuntimeComponentModule);
return module.componentFactories.find(f => f.componentType === decoratedCmp);
}
and I display a text inside theis div based on the data I calculate and it's a string with html tags like that:
Data My name is foo
I trigger the blur event of the div that is contenteditable and I see the changes and based on that I generate a new string with new spans and render it again the same div
the problem comes when I delete all the text from the contenteditable div the component removed from the dom and can't be reinstantiated again even if I try to type again in the field but it just type inside the div not the created component
how I can solve this problem and can generate the component when the user delete all text from field and try to type again ?
Here is a stackblitz for the project :
https://stackblitz.com/edit/angular-dynamic-stack?file=src%2Fapp%2Fapp.component.ts
I found the solution is by handling keystrokes in the contenteditable div especially the DEL , BackSpace Strokes so when the input is empty and the stroke is one of them you just create a new component , It still has problems that dynamic components is not appearing when have it's empty or have only a tag but that's the workaround that I came up with untill now

Check browser width in component, store in variable then use in template in Angular

I am trying to say whether a sidenav should be opened or not based on the deviceWidth property which comes from the component. For some reason it is not working.
Here is the html:
<md-sidenav #sidenav mode="side" opened="deviceWidth > 960">
Here is what my component looks like:
export class AppComponent {
deviceWidth: any;
ngOnInit() {
this.deviceWidth = window.innerWidth;
}
}
wrong way (not exactly wrong but not proper),
[opened]="{{deviceWidth>960}}"
Correct way,
[opened]="(deviceWidth>960)"
And maybe resize function of window would also be needed (later) as shown,
#HostListener('window:resize', ['$event'])
onResize(event: any) {
console.log(event.target.innerWidth);
this.deviceWidth = event.target.innerWidth;
}
if opened is declared as #Input in your md-sidenav component then your syntax is wrong. It has to be
<md-sidenav #sidenav mode="side" [opened]="deviceWidth > 960">
check these links for more details
https://toddmotto.com/passing-data-angular-2-components-input
https://angular.io/docs/ts/latest/cookbook/component-communication.html

Page transition animations with Angular 2.0 router

In Angular 2 I am trying to animated in new components via the Router onActivate method.
I have set up a Plunk with a demonstration of the issue here:
http://plnkr.co/FikHIEPONMYhr6COD9Ou
An example of the onActivate method in one of the page components:
routerOnActivate(next: ComponentInstruction, prev: ComponentInstruction) {
document.getElementsByTagName("page3")[0].className='animateinfromright';
}
The issue that I'm having is that I want the new components to animate in on top of the existing component, but the old component is removed from the DOM before the new component is added.
Is there any way to delay the removal of the previous page while the new one animates in?
I found this similar issue: Page transition animations with Angular 2.0 router and component interface promises
but the technique just delays the removal of the previous component before the new one is added.
Eventually I will have different animations depending on which page we are moving from / to, hence having the onActivate in each of the page components.
Many thanks for any help!
You could add an "EchoComponent" where your <router-outlet> is, create a <canvas> in it and drawImage() on routerOnDeactivate()... Something like:
#Component({
template: `<canvas #canvas *ngIf="visible"></canvas>`
})
class EchoComponent {
#ViewChild("canvas") canvas;
public visible = false;
constructor(private _shared: SharedEmitterService) {
this._shared.subscribe(el => el ? this.show(el) : this.hide(el));
}
show(el) {
this.canvas.drawImage(el);
this.visible = true;
}
hide() {
this.visible = false;
}
}
#Component({...})
class PrevRoute {
constructor(private _eref: ElementRef,
private _shared: SharedEmitterService) {}
routerOnDeactivate {
this._shared.emit(this._eref.nativeElement);
}
}
#Component({...})
class NextRoute {
constructor(private _eref: ElementRef,
private _shared: SharedEmitterService) {}
routerOnActivate {
this._shared.emit(false);
}
}
This is just a pseudo code (writing it from memory), but it should illustrate what would you need for this approach.

Categories