Is there a way to change the OverlayContainer?
I have created a tooltip component, but sometimes I want to attach the overlay to a specific element (by default the overlay is attached to the document body).
Here is how I am creating the overlay:
private initOverlay(): void {
const positionStrategy = this.overlayPositionBuilder
.flexibleConnectedTo(this.elementRef)
.withPositions([this.resolvedConfig]);
this.overlayRef = this.overlay.create({positionStrategy});
}
And this is how I am attaching a template to it:
show(): void {
this.overlayRef.attach(new TemplatePortal(this.tpl, this.viewContainerRef));
}
Please reference this stackblitz example.
looks like you can accomplish this by extending the
OverlayContainer class via the following in app.module.ts
{ provide: OverlayContainer, useFactory: () => new AppOverlayContainer() }
Stackblitz
https://stackblitz.com/edit/angular-material2-issue-ansnt5?file=app%2Fapp.module.ts
This GitHub comment also provides an example of how to package this in a directive
GitHub comment
https://github.com/angular/material2/issues/7349#issuecomment-337513040
Revision 3/22/19 working directive example
Extend the OverlayContainer class via cdk-overlay-container.ts
Stub the class in app.module.ts
providers: [
{ provide: OverlayContainer, useClass: CdkOverlayContainer },
]
In your cdk-overlay-container.ts you are preventing the default _createContainer() from working, and providing your own custom public method myCreateContainer to replace it.
You are essentially creating an empty div here, adding a custom class to it my-custom-overlay-container-class and appending it to the
div the directive is attached to, then passing that container to the
private variable _containerElement in the true OverlayContainer
class.
/**
* Create overlay container and append to ElementRef from directive
*/
public myCreateContainer(element: HTMLElement): void {
let container = document.createElement('div');
container.classList.add('my-custom-overlay-container-class');
element.appendChild(container);
this._containerElement = container;
}
/**
* Prevent creation of the HTML element, use custom method above
*/
protected _createContainer(): void {
return;
}
Then in your cdk-overlay-container.directive.ts your are calling myCreateContainer() and passing the ElementRef as an argument.
this.cdkOverlayContainer['myCreateContainer'](this.elementReference.nativeElement);
Then in your HTML assign the directive where you want it to show up.
<div myCdkOverlayContainer
Stackblitz
https://stackblitz.com/edit/angular-material2-issue-6nzwws?embed=1&file=app/app.component.html
Related
I have a Polymer Template view which includes a TextArea on a Vaadin 14 frontend. To this view I have the corresponding Java companion file. The content of my text area is being appended each time an action/event occurs, so technically it is functioning as a console. When this text area has a certain amount of lines, the scrolling bar shows up, but adding new values to the text area results in representing the top values still. Is it possible somehow to autoscroll to the bottom of the text area each time its value is changed? I've checked out some solutions that was only for previous versions of Vaadin, not for flow. Can this be done using the Java API or there should be some css/javaScript workaround used?
What I tried for scrolling so far without success:
Page page = UI.getCurrent().getPage();
page.executeJs("document.getElementById('myTextAreaId').scrollTop =
document.getElementById('myTextAreaId').scrollHeight",
myTextArea.getElement());
Any help is appreciated.
I tried to reproduce your logic, where on some event a new value is appended to a textarea. I've defined an updateScrollPosition JS method inside the template to update the scrollTop property each time a new value is appended to a TextArea. The method contains :
let textAreaElement = this.shadowRoot.querySelector("vaadin-text-area").shadowRoot.querySelector("div[part='input-field']");
textAreaElement.scrollTop = Number.MAX_SAFE_INTEGER;
I am using for simplicity the maximum integer, as by the specification it sets scrollTop to the maximum value.
When a textarea value changes you would need to call the defined function using getElement().callJsFunction("updateScrollPosition");
Overall, my template looks like this :
import {html, PolymerElement} from '#polymer/polymer/polymer-element.js';
import '#vaadin/vaadin-text-field/src/vaadin-text-area.js';
import '#vaadin/vaadin-button/src/vaadin-button.js';
class TextareaComponent extends PolymerElement {
static get template() {
return html`
<style include="shared-styles">
:host {
display: block;
height: 100%;
}
</style>
<vaadin-text-area label="Write a description" placeholder="Add detailed explanation" id="textareaId"></vaadin-text-area>
<vaadin-button id="buttonID">
Add text
</vaadin-button>
`;
}
updateScrollPosition(){
let textAreaElement = this.shadowRoot.querySelector("vaadin-text-area").shadowRoot.querySelector("div[part='input-field']");
textAreaElement.scrollTop = Number.MAX_SAFE_INTEGER;
}
static get is() {
return 'textarea-component';
}
static get properties() {
return {
// Declare your properties here.
};
}
}
customElements.define(TextareaComponent.is, TextareaComponent);
and the java counterpart like this
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.polymertemplate.Id;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.templatemodel.TemplateModel;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.polymertemplate.PolymerTemplate;
/**
* A Designer generated component for the textarea-component template.
*
* Designer will add and remove fields with #Id mappings but
* does not overwrite or otherwise change this file.
*/
#Tag("textarea-component")
#JsModule("./src/textarea-component.js")
public class TextareaComponent extends PolymerTemplate<TextareaComponent.TextareaComponentModel> {
#Id("textareaId")
TextArea area;
#Id("buttonID")
Button button;
/**
* Creates a new TextareaComponent.
*/
public TextareaComponent() {
// You can initialise any data required for the connected UI components here.
area.setMaxHeight("150px");
button.addClickListener(event -> {
area.setValue(area.getValue() + " New button click!");
// this is important line
getElement().callJsFunction("updateScrollPosition");
});
}
/**
* This model binds properties between TextareaComponent and textarea-component
*/
public interface TextareaComponentModel extends TemplateModel {
// Add setters and getters for template properties here.
}
}
Let's say that I have a child component called inputComponent that has a single input element as follow
#Component({ template: `<input #count [(ngModel)]="item.count" />`})
export class inputComponent implements OnInit {
#Input item;
#ViewChild("count") count : ElementRef ;
focus(){
this.count.nativeElement.focus();
this.count.nativeElement.select();
}
}
and I'm including it in a parent container as follow
<app-input-component [item]="item" ></app-input-component>
What I'm trying to achieve is to select the text input on a certain event.
for example
#ViewChild("input") count : inputComponent ;
foo(){
this.item = item ;
this.count.focus();
}
The problem is when I call focus change right after changing the binding data (item) it doesn't select anything hover if I called focus() after a short timeout it works perfectly .
I know it's not the proper way to use setTimeOut to solve it.
Stackblitz url
https://stackblitz.com/edit/angular-svgmtg
Apparently, ngModel updates the view's value asynchronously when the model is changed. I.e. the <input> value is not changed until the next change detection cycle!
From the ngModel source code:
/**
* `ngModel` forces an additional change detection run when its inputs change:
* E.g.:
* ```
* <div>{{myModel.valid}}</div>
* <input [(ngModel)]="myValue" #myModel="ngModel">
* ```
* I.e. `ngModel` can export itself on the element and then be used in the template.
* Normally, this would result in expressions before the `input` that use the exported directive
* to have and old value as they have been
* dirty checked before. As this is a very common case for `ngModel`, we added this second change
* detection run.
*
* Notes:
* - this is just one extra run no matter how many `ngModel` have been changed.
* - this is a general problem when using `exportAs` for directives!
*/
const resolvedPromise = Promise.resolve(null);
Then when the model is updated, the view is updated asynchronously:
private _updateValue(value: any): void {
resolvedPromise.then(
() => { this.control.setValue(value, {emitViewToModelChange: false}); });
}
So the setTimeout ensured that the input was selected after its view was updated.
If you want to avoid this asynchronous behavior, you can use FormControl instead of ngModel (Demo StackBlitz):
import { Component, Input, ViewChild, ElementRef } from '#angular/core';
import { FormControl } from '#angular/forms';
#Component({
selector: 'hello',
template: `<input #input [formControl]="count" />`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent {
private _item;
#Input() set item(value) {
this._item = value;
this.count.setValue(value.count);
this.focus();
}
get item() {
return this._item;
}
#ViewChild('input') input: ElementRef;
count = new FormControl();
focus() {
this.input.nativeElement.focus();
this.input.nativeElement.select();
}
}
With this approach, you don't need to call focus() explicitly from the parent component; the child component will call its own focus method whenever the input changes.
As I understood, you trying to get an element before it has been rendered. That is impossible.
I advice you to read about Lifecycle hooks in Angular. https://angular.io/guide/lifecycle-hooks
You can solve this problem, calling your foo() function in lifecycle hook - ngAfterViewInit.
ngAfterViewInit() {
this.foo();
}
In my angular application I am attempting a workaround because the ag-Grid api getRowClass() is not working at intended. All I need to do is add a css class to an expanded row and remove it when the row is collapsed.
The original method using ag-Grid api that does not work looks as follows:
setRowNode(params) {
this.gridOptions.getRowStyle = (params) => {
if(params.node.expanded) {
return { background: 'red' };
}
}
}
Ideally I would be able to selected the DOM and append a class to it. I tried this with some JQuery and it worked, but for obvious reasons I do not want to have JQuery in this app. I wrote something along these lines:
$('.ag-row[row="'+params.node.id+'"]',self.api.grid.gridPanel.eBodyContainer).addClass('ag-row-focus');
How would I fulfill this req using vanilla JS?
You can do it by creating a custom directive, like this one :
//row-focus.directive.ts
import { Directive, HostBinding, HostListener } from '#angular/core';
#Directive({
selector: '[appRowFocus]' // you can target classes, id etc.: '.grid-Holdings' for example
})
export class RowFocusDirective {
#HostBinding('class.ag-row-focus') isFocused = false;
#HostListener('click') toggleOpen() {
this.isFocused = !this.isFocused;
}
}
Import this directive on your module, and then attach the directive to your elements :
// your-component.component.ts :
<div class="row" appRowFocus>
These elements will toggle the ag-row-focus on click. You can add different #HostListener for other events.
I have an Angular 2 app using Typescript but i am new to this, what i have is a table with a 'Delete' button,
I can pass the object data to my confirmation modal but when i 'Confirm' it, its still in my table.
delete-modal.component
import { Component, OnInit, Inject, Input } from '#angular/core';
import { TestService } from '../../ABC/TestService/TestService.service';
import { MdDialog, MdDialogRef, MD_DIALOG_DATA } from '#angular/material';
import { testModal } from 'models/test';
#Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.css']
})
export class testDeleteModalComponent implements OnInit {
#Input('test') test: testModal;
constructor(private TestService: TestService, private accountService: AccountService,
#Inject(MD_DIALOG_DATA) private dialogData: any) { }
ngOnInit() {
console.log('test', this.dialogData.beneficiary);
this.test = this.dialogData.test;
}
deleteTest() {
if (this.dialogData.test.identifier) {
// this.dialogData.beneficiary.splice(this.dialogData.beneficiary.indexOf(this.beneficiaryAnt), 1);
// this.dialogData.beneficiary.splice(this.beneficiary);
// delete this.beneficiary;
this.dialogData.test.splice(this.dialogData.test.indexOf(this.dialogData.test), 1);
} else {
this.dialogData.test.operation = 'X';
}
}
}
HTML
<button md-icon-button (click)="deleteTest()" name="deleteTestDetails">
<md-icon>delete forever</md-icon>
</button>
All other HTML is in a main component and the 'Delete' button is used as shown below
<app-test-main-page-delete-button [test]="test"></app-test-main-page-delete-button>
The 'deleteTest' method is called when the user click the confirm button.
I have also included above some ways i have tried in the IF but they always come back
... is not a function
It is good that you asked this question, my projects of three peoples also struggling with this. we have found is two ways. what i will show is two ways of doing typescriptdelete.
solution a.
because it is object, it will need identifier. First is
var objectdelete = {
identifier: 'Mydelte',
value: '168%'
}
Next what we need is now service. some people call them directives but from my experience they are the same thing. We have alert so user knows if they did not set identifier that they must go back. I do not see service on your side, i see array being deleted. if you combine the array and the service, this will then be working across whole website.
export class DeleteService
delete(objectToDelete: string) {
if (!objectToDelete.identifier) {
alert('No identifer');
}else {
// Delete from your array here.
}
}
Solution 2.
If the above does not meed your needs, our tema also experimented with interfaces in typescript. You can see them here https://www.typescriptlang.org/docs/handbook/interfaces.html
so it becomes
export class myDeleteService {
deleter: IDeleter
}
export interface IDeleter {
delete: this.delete.delete(deletE);
deleteArray: this.array =[];
}
then simply in your html it will be
<button (click)='delete(dieleter)'>Delete me!</button>
These are all common typescript behaviours for angular2/4/5 so we are hoping to become more used to them when we have hads more time to use them!
The easiest way to delete data object on button click and refresh instantly when it's done :
Your parent html has to call children like this :
<app-component [inputData]="dataTable" (inputDataChange)="resetData()"/>
Add dataTable as class variable and implement the output function :
resetData() { this.dataTable=[] }
Then in children html leave your code (you can use this changes)
<button class="fa fa-delete" (click)="deleteTest()" name="deleteTestDetails">Delete</button>
Finaly in your children ts file set your data object for each change, and implement your input function
myDataTable: any = [];
#Input set inputData(data: DataTable) {
if(data) {
this.myDataTable = data;
}}
#Output() inputDataChange: EventEmitter<any> = new EventEmitter();
deleteTest() {
this.inputDataChange.emit(true);
}
What does this code do ?
It will emit and event to the parent when the delete button is clicked, then your parent will delete the dataTable, and finally, your children input will refresh it, as setter will catch the changes and refresh the variable.
If you want to apply those rules to table changes, then simply emit your dataTable and reassign it instead of reset it.
I am in a project with and our team have struggled on this for a whiles.
First thing I will say is this, Angular has not made this an easy task, so we will attempt to ignore the framework and write pure Java instead to make our lives easyer on ourselves.
SO looking at your button, I can see that you have started on the right track.
If the button is calling your component like the following
Html/Java
<button ng-click="delete()">Click me<button>
Component.ts
function delete = deleteMethod(testIdentifier) {
var abc = this.beneficiary.beneficiaryIdentifier.test.splice(this.beneficiary.beneficiaryIdentifier.test.indexOf(testIdentifier));
component2.deleteFunction();
}
Component2.ts
Then we can pass our identifiers into our parent or child components and remove the beneficiary like so:
deleteMethod(deetle) {
this.beneficiary.removeAtIndex(testIdentifier.splice(1), 1);
}
Nice and easy looking back, but it took our team of threes a long whiles to figure that ones out.
I'm currently working on an Angular 2 Project where I have a menu that should be closable by a click on a button. Since this is not heavy at all, I would like to put it outside of Angular (without using a component for the menu).
But I'm not sure of how to do it, actually I've just put a simple javascript in my html header, but shouldn't I put it somewhere else?
Also, what the code should be? Using class, export something? Currently this is my code:
var toggleMenuButton = document.getElementById('open-close-sidebar');
var contentHolder = document.getElementById('main-content');
var menuHolder = document.getElementById('sidebar');
var menuIsVisible = true;
var updateVisibility = function() {
contentHolder.className = menuIsVisible ? "minimised" : "extended";
menuHolder.className = menuIsVisible ? "open" : "closed";
}
toggleMenuButton.addEventListener('click', function() {
menuIsVisible = !menuIsVisible;
updateVisibility();
});
Finally moved to something with MenuComponent and a service, but I'm still encountering an issue.
MenuService.ts
#Injectable()
export class MenuService {
isAvailable: boolean = true;
isOpen: boolean = true;
mainClass: string = "minimised";
sidebarClass: string = "open";
updateClassName() {
this.mainClass = this.isOpen ? "minimised" : "extended";
this.sidebarClass = this.isOpen ? "open" : "closed";
}
toggleMenu(newState: boolean = !this.isOpen) {
this.isOpen = newState;
this.updateClassName();
}
}
MenuComponent.ts
export class MenuComponent {
constructor(private _menuService: MenuService) { }
public isAvailable: boolean = this._menuService.isAvailable;
public sidebarClass: string = this._menuService.sidebarClass;
toggleMenu() {
this._menuService.toggleMenu();
}
}
MenuComponent.html
<div id="sidebar" [class]="sidebarClass" *ngIf="isAvailable">
...
<div id="open-close-sidebar"><a (click)="toggleMenu()"></a></div>
The action are rightly triggered, if I debug the value with console.log, the class name are right but it didn't change the value of the class. I thought the binding was automatic. And I still do not really understand how to change it. Do I have to use Emmit like AMagyar suggested?
The advantage of using angular2 above your own implementation, greatly outweigh the marginal benefit in performance you will get from using plane JavaSccript. I suggest not going on this path.
If you however do want to continue with this, you should export a function and import and call this function inside the ngAfterViewInit of your AppComponent. The exported function should add the click EventListener and (important) set the document.getElementById variables. Because your script possibly won't be able to find those elements yet when it's loaded.
But let me emphasise once more, that angular2 is optimised for exactly these tasks, and once you get more familiar with it, it will also be a lot easier to code it.
update
For inter component communication you should immediately think about a service. Just create a service which stores the menu state and add this to your global ngModule providers array. For instance:
export class MenuService {
public get menuOpen(): boolean {
return this._menuOpen;
}
private _menuOpen: boolean;
public openMenu() : void {
this._menuOpen = true;
}
public closeMenu() : void {
this._menuOpen = false;
}
public toggleMenu() : void {
this._menuOpen = !this._menuOpen;
}
}
You can then inject this service into your menu component and bind the classes open/closed and minimized/extended to the MenuService.menuOpen.
#Component({
selector : 'menu'
template : `
<button (click)="menuService.toggleMenu()">click</button>
<div id="open-close-sidebar" [class.open]="menuService.menuOpen"></div>
`
})
export class MenuComponent {
constructor(public menuService: MenuService){}
}
For other component you can use the same logic to see if the menu is open or closed
update #2
You have to use a getter to get the value from menuService. There is only one way binding:
export class MenuComponent {
constructor(private _menuService: MenuService) { }
public get isAvailable(): boolean {
return this._menuService.isAvailable;
}
public get sidebarClass(): string {
return this._menuService.sidebarClass;
}
toggleMenu() {
this._menuService.toggleMenu();
}
}
FYI, it's better practice to use [class.open] instead of a string class name. If you want to do it like that, it will only require minimal change in your current css.
The main reason of why I want to avoid using Angular component is the
fact that my manipulation should be done over all the website and not
just the "menu" component.
You can create many components in Angular 2, it's easy and very practical.
The action will change the class on my menu (located in my menu
component) and on my main content (located outside of the component).
I don't know how to do it, and I'm not sure that this is the best
way... Maybe by binding the service value directly... –
The main content can have a child that is the Menu itself.
Take a look in this link. There are many solutions, one of them is to "emit" the child changes to the parent.
If you need an example I can provide one quickly.