I store a dynamically generated HTMLElement in a variable in my component class. I would like to display it, but I have no idea how is that possible. I tried it like this:
Inside the component class:
elem: HTMLElement;
In the template:
<ng-container *ngTemplateOutlet="elem"></ng-container>
I get this error:
TypeError: templateRef.createEmbeddedView is not a function
If you have a naked HTMLElement, there's nothing Angular-y you can do. NgTemplateOutlet expected a TemplateRef, not a vanila HTMLElement object.
To insert an HTML Element into the DOM, simply use the appropriate DOM method for that. Which one you'll use entirely depends on the place where you want to use it.
For example, you could do
const wrapper = document.querySelector('div.wrapper')
wrapper.append(el) // pass in your element
You can also grab the reference to the wrapper in a more Angular way, by adding an Angular reference in the template on the element you want to select,
<div class="wrapper" #wrapper></div>
and using ViewChild decorator on a property where you want Angular to assign the ElementRef to.
#ViewChild('wrapper', { static: true, read: ElementRef })
public wrapperElRef: ElementRef<HTMLDivElement>
Once the view is initialized (you'll be notified when this happens via the ngAfterViewInit hook), you can grab the HTML element within by accessing `.nativeElement:
this.wrapperElRef.nativeElement //: HTMLDivElement
You can now do
this.wrapperElRef.nativeElement.append(elem)
That said, unless you know what you're doing (usually wrapping an existing library or integrating with an existing large system that you cannot yet refactor completely)
Most of the times it depends how you have created the html.
I suggest specifying in comments where are you receiving the variable which holds the HTMLElement, so I could give you the most effective way to do it.
But generally -
try using innerHTML binding in the parent element.
Typescript:
public elem: HTMLElement;
public getElement(): string {
return this.elem.innerHTML;
}
HTML:
<div innerHTML="getElement()"></div>
Related
I'm new to Angular, sorry if my questions sounds dumb. I always confused with ngOnInit() and ngAfterContentInit() lifecycle hooks. On the official docs, it says:
ngOnInit() : Initialize the directive/component after Angular first displays the data-bound properties and sets the directive/component's input properties.
ngAfterContentInit(): Respond after Angular projects external content into the component's view / the view that a directive is in.
My questions are:
Q1-For ngOnInit(), what does Initialize the directive/component after Angular first displays the data-bound properties mean? Does "Initialize " means create an instance of directive/component?
Q2-For ngAfterContentInit(), what's component's view means? does the view mean the associated template html in the component's templateUrl?
Q1: No, the creation of a class instance is the method constructor which happened before ngOnInit(), a component is a directive with a template data-bound properties, view nodes etc.. and ngOnInit() is called after data-bound properties is ready, and as you may known, ngAfterViewInit() is called after view ready.
Q2: I have one example for what the "content" mean:
You define a component selector inside app.component.html with a text inside :
<custom-component>
Some random text
</custom-component>
Now inside custom-component.component.hml you can display the text "Some random text" using <ng-content></ng-content> which act as a placeholder for the text you passed down
<ng-content></ng-content>
ngAfterContentInit() simply mean the passing of "Some random text" into custom-component.component.hml view is completed.
A1) First you need to know what data-bound properties is. This answer question should help you:
What is data-bound properties?
What creates first is the constructor() The constructor comes before the ngOnInit()
https://angular.io/guide/lifecycle-hooks
A2) Yes, the view is either the xxx.component.html or the template inside of the component.ts file
https://dev.to/devpato/displaying-data-in-angular-unofficial-docs-4nd4
What is the difference between ngAfterContentInit and ngAfterViewInit?
I want to do some data binding with Polymer. I defined a custom element:
<dom-module id="my-voltage">
<template is="auto-binding">
<div class="circle">{{volts}}</div>
</template>
<script>
class MyVoltage extends Polymer.Element {
static get is() {
return "my-voltage";
}
static get properties() {
return {
volts: {
type: String
},
}
}
constructor() {
super();
}
}
customElements.define(MyVoltage.is, MyVoltage);
</script>
</dom-module>
Now, I want to dynamically get the volts to be bound to a JS variable. So the JS is like:
volt = JSON.parse(httpGet('some-api-call'));
voltage = volt.value.toString();
and I want volts to display the value of voltage. I'm trying document.getElementById("volt").setAttribute("volts", voltage);, but I keep getting "Uncaught TypeError: Cannot read property 'setAttribute' of null" at the attribute assignment. What's the proper way to do this in polymer?
I do have the element in html:
<my-voltage id="volt" class="circle" volts="-1"></my-voltage>
I see that you're using Polymer 2.0+. I've only been using previous versions.
Anyway, so you want to assign data to a child element? The problem here is that during the "ready" method, there are no guarantees that the child elements have loaded. If you would like to do something more advanced, like doing a form validation, you have to create an interval that loops until the child element actually exists.
var voltElement = {};
var intervalId = setInterval((function() {
voltElement = this.$.volt;
if (voltElement) {
//enter code here
clearInterval(intervalId);
}
}).bind(this));
The .bind(this) is only there so you can use the correct scope inside the anonymous function in setInterval.
However, the problem isn't that complex for you. What you can instead do is to create a property variable and assign that to the voltage property in the my-voltage element when it's created.
<my-voltage id="volt" class="circle" volts="[[voltage]]"></my-voltage>
volt = JSON.parse(httpGet('some-api-call'));
this.set('voltage', volt.value.toString());
Using this.set() makes Polymer send an event to elements using that variable if the variable has been already set on beforehand, like been given a default value.
Inside Polymer's components you should use native polymer operators.
So, next js code should be rewritten via polymer's operators.
document.getElementById("volt").setAttribute("volts", voltage);
In Polymer 1.9.1, this line will have next view:
this.$.volt.set("volts", voltage);
or
this.$$('#volt').set("volts", voltage);
In Polymer 2.0 or 3.0, you could use next syntax:
this.shadowRoot.querySelector('#volt').set("volts", voltage);
More info about the node finding in Polymer 1.9.1, you can find there:
https://polymer-library.polymer-project.org/1.0/docs/devguide/local-dom#node-finding
More examples with this.shadowRoot.querySelector, you can find there: https://polymer-library.polymer-project.org/3.0/docs/devguide/dom-template
I updated from Angular 2 to Angular 4 and in the docs it's written to use the Renderer2 instead of Renderer which is deprecated.
Now am looking into the Renderer source, but cannot find a way to invoke the focus() method as I used to.
Old method:
this.renderer.invokeElementMethod(element, 'focus', []);
What is the new aproach?
EDIT
What if the element i am focusing onto is obtained via QuerySelector?
For instance:
let inputField = document.querySelectorAll('.dialog input');
if ( inputField[0] ) {
inputField[0].focus();
}
Since its obtained via QuerySelector, the focus() method doesn't exist on it.
The invokeElementMethod is deprecated, and will not find its way back into the new renderer. To set focus to an element, you can simply use this now:
element.nativeElement.focus();
If you are using document.querySelectorAll, you are doing something not the angular way, and you should find another way to do it. If there is no other way to do it, then the same principle applies. The focus() method is plain javascript, so you can use it within angular2/typescript. Be sure to do the querySelectorAll inside the ngAfterViewInit hook of the component though:
ngAfterViewInit() {
let inputField: HTMLElement = <HTMLElement>document.querySelectorAll('.dialog input')[0];
inputField && inputField.focus();
}
Also you can use selectRootElement method of Renderer2.
For example:
constructor(private renderer: Renderer2) {}
this.renderer.selectRootElement('#domElementId').focus()
, where domElementId is id='domElementId' in your html
template reference variable :#inlineEditControl
<input #inlineEditControl class="form-control form-control-lg" [placeholder]="label">
#ViewChild('inlineEditControl') inlineEditControl: ElementRef; // input DOM element
this.inlineEditControl.nativeElement.focus();
If you are using Angular CDK, you can set focus using FocusMonitor.focusVia method that is part of #angular/cdk/a11y Accessibility module (A11yModule).
This way you can avoid any DOM manipulation and avoid referencing nativeElement altogether.
import { FocusMonitor } from '#angular/cdk/a11y';
export class ExampleComponent implements AfterViewInit {
#ViewChild('elem', {static: false}) elemRef;
constructor(private focusMonitor: FocusMonitor) {
}
ngAfterViewInit() {
// Programmatically set focus. Focus source is 'program'.
this.focusMonitor.focusVia(this.elemRef, 'program');
}
}
At this time, it appears that this task is not achievable without directly modifying the DOM. As others have said, the invokeElementMethod() is deprecated. However, you will find issues with using selectRootElement as well, as this is not its intended purpose and you will end up losing the children inside of your DIV. Here is a link to a SO question that explains selectRootElement in more detail (since the angular docs are atrocious):
Renderer multiple selectRootElement Issue (with plnkr provided)
As for now, it appears the only way to easily achieve your goal is to use a template reference variable, a view child, and the yourViewChild.nativeElement.focus() (though this practice is also not recommended)
There is also a suggested workaround using a FocusService found on this GitHub issue: https://github.com/angular/angular/issues/15674
In this issue, Tytskyi mentions that you can implement a FocusService and provide different implementations of focus() based on AppModuleBrowser and AppModuleServer. I'm not sure of the fine details of how that would work but it may be the only way to achieve this without using nativeElement at this time.
If you declare the correct type of your inputField var, you can use any JS query selector.
For instances declaring:
let inputField: HTMLInputElement = document.querySelector('.dialog input');
enables you to call
inputField.focus();
that it will work and no TS error will be thrown.
I am trying to find a way to dynamically construct a template in Angular2. I was thinking templateRef might provide a way to do this. But I could be wrong.
I found an example of templateRef being used here.
I was looking at templateRef in this example. I noticed the syntax is [ng-for-template] I also tried [ngForTemplate] cause I know this has changed recently.
So at the moment I have this:
import {Component, TemplateRef} from 'angular2/core';
#Component({
selector : 'body',
template : `
<template [ngForTemplate]="container">
<div class="container"></div>
</template>
`
})
export class App
{
#ContentChild(TemplateRef) container;
constructor() {}
ngAfterContentInit()
{
console.log(this);
}
}
This example throws an error:
Can't bind to 'ngForTemplate' since it isn't a known native property
So firstly I am wondering. What is the right way to do this? The docs don't provide any examples.
Secondly, is there a good way I can add new template logic to my template or dynamically construct a template? The structure of the application can be a very large amount of different structural combinations. So if possible I would like to see if there is a way I can do this without having a huge template with a bunch of different ngIf and ngSwitch statements..
My question is really the first part about templateRef. But any help or suggestions on the second part is appreciated.
Creating your own template directive it's not difficult, you have to understand two main things
TemplateRef contains what's inside your <template> tag
ViewContainerRef as commented by Gunter, holds the template's view and will let you to embed what's inside the template into the view itself.
I will use an example I have when I tried to solve this issue, my approach is not the best for that, but it will work for explaining how it works.
I want to clarify too that you can use any attribute for your templates, even if they're already used by builtin directives (obviously this is not a good idea, but you can do it).
Consider my approach for ngIfIn (my poor approach)
<template [ngIfValue]="'make'" [ngIfIn]="obj">
This will print
</template>
<template [ngIfValue]="'notExistingValue'" [ngIfIn]="obj">
This won't print
</template>
We have here two templates using two inputs each ngIfIn and ngIfValue, so I need my directive to grab the template by these two inputs and get their values too, so it would look like this
#Directive({
selector : '[ngIfIn][ngIfValue]',
inputs : ['ngIfIn', 'ngIfValue']
})
First I need to inject the two classes I mentioned above
constructor(private _vr: ViewContainerRef, private _tr: TemplateRef) {}
I also need to cache the values I'm passing through the inputs
_value: any;
_obj: any;
// Value passed through <template [ngIfValue]="'...'">
set ngIfValue(value: any) {
this._value = value;
}
// Value passed through <template [ngIfIn]="...">
set ngIfIn(obj: any) {
this._obj = obj;
}
In my case I depend on these two values, I could have my logic in ngOnInit but that would run once and wouldn't listen for changes in any of the inputs, so I put the logic in ngOnChanges. Remember that ngOnChanges is called right after the data-bound properties have been checked and before view and content children are checked if at least one of them has changed (copy and paste from the docs).
Now I basically copy & paste NgIf logic (not so complex, but similar)
// ngOnChanges so this gets re-evaluated when one of the inputs change its value
ngOnChanges(changes) {
if(this._value in this._obj) {
// If the condition is true, we embed our template content (TemplateRef) into the view
this._vr.createEmbeddedView(this._tr);
} else {
// If the condition is false we remove the content of the view
this._vr.clear();
}
}
As you see it's not that complicated : Grab a TemplateRef, grab a ViewContainerRef, do some logic and embed the TemplateRef in the view using ViewContainerRef.
Hopefully I made myself clear and I made how to use them clear enough also. Here's a plnkr with the example I explained.
ngForTemplate is only supported with ngFor
<template [ngFor] [ngForOf]="..." [ngForTemplate]="container"
or
<div *ngFor="..." [ngForTemplate]="container"
not on a plain template. It is an #Input() on the NgFor directive
Another way to use TemplateRef
If you have a reference to ViewContainerRef you can use it to "stamp" the template
constructor(private _viewContainer: ViewContainerRef) { }
ngOnInit() {
this.childView = this._viewContainer.createEmbeddedView(this.templ);
this.childView.setLocal('data', this.data);
}
I have a simple script that would add a class to a dom selector.
showParent() {
console.log('show parent');
$("seasonsGrid").toggleClass("grid-layout");
}
How does angular allow you to target these dom nodes whether they are tags or named elements?
in this case $("seasonsGrid") is a reference to
#Component({ selector: 'seasonsGrid', ...
but would be useful to know how to dom traverse using tags and named el's.
what would the ng2 equivalent be for this
var seasonsGrid = $(seasonsGrid);
You could take use of ElementRef/Renderer to get component element
import {Component, View, ElementRef} from 'angular2/angular2';
import {NgClass} from 'angular2/common'; //import when you wanted to use ngClass
import {Renderer} from 'angular2/core';
#Component({ selector: 'seasonsGrid', ...})
export class MyComponent {
let el: any;
let componentElement:any;
constructor(private elementRef: ElementRef, private renderer: Renderer){
//el = elementRef.nativeElement;
componentElement = renderer.selectRootElement(); //will have component root element
}
ngAfterViewInit() {
console.log(el); //nativeElement to process
console.log(componentElement); //nativeElement to process
}
}
But in your case you can think of to have ngClass directive to have it in a place like [ngClass]="{className: expression}"
I must need to do some more homework but from the looks of it angular 2 is by far more convoluted if all that is required just to initialize the states of a toggle class on an element. jquery is 1 line
Angular 2 is a data-driven reactive framework. Instead of thinking about selectors and DOM manipulation code, I encourage you to embrace Angular-think, which is quite different from jQuery-think.
If you want to change a class in a data-driven framework, you first declaratively bind some data to that element's class property in a component template. Then, to change the class, you simply change the bound data. As others have already mentioned, NgClass is the way to do that.
Similarly, component logic should not manipulate the DOM (i.e., it shouldn't call things like toggleClass()), rather it should change data or emit an event (up to its parent).
When Angular change detection runs, it will notice the data changes and update the DOM (or native UI) or propagate the data change to a parent component (for an emitted event) or to a child component (for an input data property change).
Angular-think is all about data-driven changes. And Angular 2 pushes us further down that road than Angular 1 did. (That's a good thing.)
This probably won't answer the question as I'm very new to NG2 (like three days), but here's a few things I've found in the documentation:
<div [class.isStopped] = "isStopped"></div>
<div [style.color] = "isStopped ? 'red' : 'blue'"></div>
<button [attr.aria-label] = "ok"></button>
<div [ngClass] = "{selected:isSelected}"></div>
ngClass is a Directive Property, just like ngStyle and ngModel. For the most part, they just took out the dash.
While we're on Property Bindings:
<img [src] = "vehicle.imageUrl" />
<vehicle-detail [vehicle] = "currentVehicle"></vehicle-detail>
You tie the component (code) to the template so in this case it wouldn't be necessary to get the element in code and change it, just change the Component (view model).