I am tetsing a template driven form in angular, just testing not validating it.
I have read that it can be done using a viewChild property but it seems to not work for me.
I create a reference like this in my one of my forms label:
<label #ref id=.. class=...>
And now in my component I do this:
#ViewChild('ref') ref:ElementRef;
So, I suppose I created a valiable of type ElementRef that is viewChild of my input. So now I can use ref in my tests.
Inside my tests I do this:
let ref: HTMLElement:
it(....=>
{
ref = fixture.debugElement.query(By.css('ref')).nativeElement;
fixture.detectChanges();
expect(ref.innerHTML)toContain('Name');// or whatever
}
)
Now consider that the test, html and component files are separated from one another.
I still get errors of nativeElemnt property cannot be read. eventhough I have imported ElemntRef.
Is this the right way to access the DOM elemnts?? Or this viewChild doesnt make a referece to my label??
And again can I use the ID to access the form elements? What I have read they use a reference with #.
Thanks!!
For direct access to DOM in Angular you can make judicious use of ElementRef
However Direct access to DOM elements is not a good practice because
it leaves you vulnerable to XSS attacks.
Your AppComponent
import {Component, ElementRef} from '#angular/core';
#Component({
selector: 'my-app'
})
export class AppComponent implements ngOnInit {
constructor(private _elementRef : ElementRef) { }
ngOnInit(): void
{
this.ModifyDOMElement();
}
ModifyDOMElement() : void
{
//Do whatever you wish with the DOM element.
let domElement = this._elementRef.nativeElement.querySelector(`#someID`);
}
}
Your HTML
<p id="someID"></p>
Related
I have had a look at this but it does not work -- maybe because the elements I am targeting are dynamically generated inside a custom component (<c-tabs>).
I have also had a look at this but this wouldn't work, I think, as i am not able to touch the custom component's code.
Relevant Elements
HTML
<c-tabs #mainComponentTabs1 [items]="tabItems"></c-tabs>
I have tried the following ways to target it. None works.
Method 1. Using plain old Javascript:
ngAfterViewInit() {
let att = document.createAttribute("id");
att.value = "yada1";
document.querySelectorAll(".c-tabs .hydrated")[0].setAttributeNode(att);
}
I have tried to have the above in the ngOnInit and ngOnViewInit methods but that didn't help.
This method, oddly enough, works in the Chrome console window after the page has rendered. That is, the querySelected items get id attribute.
Method2. Using Angular's Renderer2. (Admittedly, I don't know how to get to the particular native elements that need the id's.
//first, declare target custom component element with viewchild:
export class MainComponent implements OnInit, AfterViewChecked, AfterViewInit {
#ViewChild('mainComponentTabs1', { static: true }) c_tabs1: ElementRef;
...
ngAfterViewChecked() {
//neither of these approaches works.
const c_tabs1El = this.c_tabs1.nativeElement;
this.renderer.setAttribute(c_tabs1El.items[0], 'id', 'dashboard-tab-link-search');
const c_tabs1El2 = document.querySelectorAll("button.c-item");
this.renderer.setAttribute(c_tabs1El2[0].nativeElement, 'id', 'dashboard-tab-link-dashboard');
}
...
}
I'm not sure what elements are you trying to target, if you're trying to target the elements by class or tag but I think the following code will help you:
export class MainComponent implements OnInit, AfterViewChecked, AfterViewInit {
// the static property must be true only if you are using it in ngOnInit
#ViewChild('mainComponentTabs1', { static: false }) c_tabs1: ElementRef;
...
ngAfterViewInit() {
const targetElements = this.c_tabs1.nativeElement.querySelectorAll('button.c-item')
targetELements.forEach((el, index) => this.renderer.setAttribute(el, 'id', `dashboard-tab-link-search-${index}`));
}
...
}
Don't touch the DOM directly.
Note: since the problem is a little complex, the code is abstracted for readability
We've a <parent-component> like this:
<child-component></child-component>
<button (click)="doSomeClick()"> Do Some Click </button>
The template of the <child-component> is:
<textarea #childComponentElement #someField="ngModel" name="someName" [(ngModel)]="someModel"></textarea>
We're trying to access the value of this element inside the parent-component.component.ts like this:
export class ParentComponent implements AfterViewInit {
#ViewChild('childComponentElement') el:ElementRef;
ngAfterViewInit() {
console.log(this.el.nativeElement.value);
}
doSomeClick(){
}
}
However it throws this error:
Cannot read property 'nativeElement' of undefined
What have we tried so far:
This gives access to <parent-component>, we need <textarea> of <child-component>
It's not about angular-tree-component
The directive name is camelCased
ElementRef seems to be an old thing
This throws Cannot read property 'nativeElement' of undefined
How is the reference between this.element.nativeElement & <input> element is getting established?
There is no *ngIf or *ngSwitchCase
There is no *ngIf used with #childComponentElement
The call is inside ngAfterViewInit only
Time out is a very dirty approach
There's no easy way to this with a nested component, you'll have to create an EventEmitter that emits the ElementRef of the element you are trying to get access to:
child.component.ts
class ChildComponent implements AfterViewInit {
#Output()
templateLoaded: EventEmitter<ElementRef> = new EventEmitter()
#ViewChild('childComponentElement') el: ElementRef
ngAfterViewInit(): void {
this.templateLoaded.emit(this.el)
}
}
parent.component.html
<child-component (templateLoaded)="templateLoaded($event)"
parent.component.ts
class ParentComponent {
templateLoaded(template: ElementRef): void {
// do stuff with the `template`
}
}
Original Answer
Try using the read property in the 2nd parameter of ViewChild
#ViewChild('childComponentElement', {read: ElementRef}) el: ElementRef
If you are wondering about the second parameter, this answer gives a very good explanation: What is the read parameter in #ViewChild for
Use the #Output decorator or a service instead of trying hopelessly to access the textarea directly from the parent component
child template
<textarea #childComponentElement #someField="ngModel" name="someName" [(ngModel)]="someModel"></textarea>
child component
#ViewChild('childComponentElement') el:ElementRef;
#Output() textarea = new EventEmitter();
constructor(){
this.textarea.emit(this.el.nativeElement.value);
}
parent template
<child-component (change)="getdata($event)"></child-component>
parent component
export class ParentComponent {
getdata(e) {
console.log(e);
}
}
I have written a reusable component in Angular 2 to display the Summernote WYSIWYG editor in my application. That component accepts 3 input parameters which are being set as attributes for a rendered textarea as id, name and last one used as the body. My problem is that within this component I am initializing the Summernote plugin and creating the editor. Here, I do not want to hard code the selector name and want the dynamic values that the component received as the input parameters to the component. Relevant code is as follows.
import {Component, ElementRef, OnInit, EventEmitter, Input, Output, Inject, ComponentRef} from '#angular/core';
import {Http} from '#angular/http';
declare var $: any;
#Component({
selector: 'editor',
template: `<textarea id="{{eid}}" name="{{ename}}" class="form-control">{{body}}</textarea>`
})
export class EditorComponent {
#Input() body: string;
#Input() eid: string;
#Input() ename: string;
#Output() onContentChanged: EventEmitter<any>;
constructor(){}
ngAfterViewInit()
{
$(document).on("pageLoaded", function (){
console.log("pageLoaded");
$("#body").summernote({
height: '200px',
callbacks: {
onChange: function(contents, $editable) {
$("#body").val(contents);
}
}
});
});
}
}
Here, you can see that I have used $("#body") twice inside the ngAfterViewInit block. I want this to be replaced by the eid variable. I have tried {{eid}} but it doesn't work and throws the following error in browser console.
EXCEPTION: Syntax error, unrecognized expression: #{{eid}}
this.eid can't be used here either since we're inside the javascript method and not typescript.
I'm using this component in my other view file as a directive.
<editor [body]="page.body" eid="body" ename="body"></editor>
The template block in component is set properly with dynamic values. Only the javascript part is my issue.
Is there any other way I'm missing here?
P.S. So far it works great. I just want to make the initialization fully dynamic, so that I can use it anywhere with different IDs.
You can try using arrow functions instead of function to keep the same context.
$(document).on("pageLoaded", () => {
console.log("pageLoaded");
$(this.eid).summernote({
height: '200px',
callbacks: {
onChange: (contents, $editable) => {
$(this.eid).val(contents);
}
}
});
});
}
When I put an anchor element in somewhere in an Angular component like this:
<a [routerLink]="['/LoggedIn/Profile']">Static Link</a>
everything is working fine. When clicking the link, the Angular router navigates to the target component.
Now, I would like to add the same link dynamically. Somewhere in my app I have a "notification component", its single responsibility is to display notifications.
The notification component does something like this:
<div [innerHTML]="notification.content"></div>
Where notification.content is a public string variable in the NotificationComponent class that contains the HTML to display.
The notification.content variable can contain something like:
<div>Click on this <a [routerLink]="['/LoggedIn/Profile']">Dynamic Link</a> please</div>
Everything works fine and shows up on my screen, but nothing happens when I click the dynamic link.
Is there a way to let the Angular router work with this dynamically added link?
PS: I know about DynamicComponentLoader, but I really need a more unrestricted solution where I can send all kinds of HTML to my notification component, with all kind of different links.
routerLink cannot be added after the content is already rendered but you can still achieve the desired result:
Create a href with dynamic data and give it a class:
`<a class="routerlink" href="${someDynamicUrl}">${someDynamicValue}</a>`
Add a HostListener to app.component that listens for the click and uses the router to
navigate
#HostListener('document:click', ['$event'])
public handleClick(event: Event): void {
if (event.target instanceof HTMLAnchorElement) {
const element = event.target as HTMLAnchorElement;
if (element.className === 'routerlink') {
event.preventDefault();
const route = element?.getAttribute('href');
if (route) {
this.router.navigate([`/${route}`]);
}
}
}
}
routerLink is a directive. Directives and Components are not created for HTML that is added using [innerHTML]. This HTML is not process by Angular in any way.
The recommended way is to not use [innerHTML] but DynamicComponentLoaderViewContainerRef.createComponent where you wrap the HTML in a component and add it dynamically.
For an example see Angular 2 dynamic tabs with user-click chosen components
Since angular 9, AOT is the default recommended way to compile angular projects.
Unlike JIT, AOT doesn't hold an instance for the compiler at runtime, which means you can't dynamically compile angular code.
It's possible to disable AOT in angular 9, but it's not recommended as your bundle size will be bigger and your application slower.
The way I solve this is by adding a click listener at runtime using renderer api, preventing the default behavior of urls and calling angular router
import { Directive, ElementRef, OnDestroy, OnInit, Renderer2 } from '#angular/core';
import { Router } from '#angular/router';
#Directive({
selector: '[hrefToRouterLink]'
})
export class HrefToRouterLinkDirective implements OnInit, OnDestroy {
private _listeners: { destroy: () => void }[] = [];
constructor(private _router: Router,
private _el: ElementRef,
private _renderer: Renderer2) {
}
ngOnInit() {
// TODO how to guarantee this directive running after all other directives without setTimeout?
setTimeout(() => {
const links = this._el.nativeElement.querySelectorAll('a');
links.forEach(link => {
this._renderer.setAttribute(link, 'routerLink', link?.getAttribute('href'));
const destroyListener = this._renderer.listen(link, 'click',
(_event) => {
_event.preventDefault();
_event.stopPropagation();
this._router.navigateByUrl(link?.getAttribute('href'));
});
this._listeners.push({ destroy: destroyListener });
});
}, 0);
}
ngOnDestroy(): void {
this._listeners?.forEach(listener => listener.destroy());
this._listeners = null;
}
}
You can find an example here : https://stackblitz.com/edit/angular-dynamic-routerlink-2
Obviously the method explained above work for both JIT & AOT, but If you are still using JIT and want to dynamically compile component (which may help solve other problems) . You can find an example here :
https://stackblitz.com/edit/angular-dynamic-routerlink-1
Used resources :
https://stackoverflow.com/a/35082441/6209801
https://indepth.dev/here-is-what-you-need-to-know-about-dynamic-components-in-angular
Combining some of the other answers - I wanted this as a Directive so I could target specific elements that are being innerHTML'd, but to avoid using querySelector (etc) to keep everything Angulary.
I also found an issue with the approaches above, in that if the href is a full URL (i.e, https://www.example.com/abc) feeding that whole thing to the router would result in navigating to /https.
I also needed checks to ensure we only router'd hrefs that were within our domain.
#Directive({
selector: '[hrefToRouterLink]'
})
export class HrefToRouterLinkDirective {
constructor(private _router: Router){}
private _baseHref = quotemeta(environment.root_url.replace(`^https?://`, ''));
private _hrefRe: RegExp = new RegExp(`^(https?:)?(\\/+)?(www\\.)?${this._baseHref}`, `i`);
#HostListener('click', ['$event'])
onClick(e) {
// Is it a link?
if (!(e.target instanceof HTMLAnchorElement))
return;
let href: string = e.target?.getAttribute('href')
.replace(/(^\s+|\s+$)/gs, '');
// Is this a URL in our site?
if (!this._hrefRe.test(href))
return;
// If we're here, it's a link to our site, stop normal navigation
e.preventDefault();
e.stopPropagation();
// Feed the router.
this._router.navigateByUrl(
href.replace(this._hrefRe, '')
);
}
}
In the above environment.root_url describes our base domain, and quotemeta is a rough implementation of a Perl-ish quotemeta function just to escape special characters.
YMMV and I've definitely missed some edge cases, but this seems to work fine.
Question
How to create child components inside a parent component and display them in the view afterwards using Angular2? How to make sure the injectables are injected correctly into the child components?
Example
import {Component, View, bootstrap} from 'angular2/angular2';
import {ChildComponent} from './ChildComponent';
#Component({
selector: 'parent'
})
#View({
template: `
<div>
<h1>the children:</h1>
<!-- ??? three child views shall be inserted here ??? -->
</div>`,
directives: [ChildComponent]
})
class ParentComponent {
children: ChildComponent[];
constructor() {
// when creating the children, their constructors
// shall still be called with the injectables.
// E.g. constructor(childName:string, additionalInjectable:SomeInjectable)
children.push(new ChildComponent("Child A"));
children.push(new ChildComponent("Child B"));
children.push(new ChildComponent("Child C"));
// How to create the components correctly?
}
}
bootstrap(ParentComponent);
Edit
I found the DynamicComponentLoader in the API docs preview. But I get the following error when following the example: There is no dynamic component directive at element 0
This is generally not the approach I would take. Instead I would rely on databinding against an array that will render out more child components as objects are added to the backing array. Essentially child components wrapped in an ng-for
I have an example here that is similar in that it renders a dynamic list of children. Not 100% the same, but seems like the concept is still the same:
http://www.syntaxsuccess.com/viewarticle/recursive-treeview-in-angular-2.0
Warning: DynamicComponentLoader has been deprecated in RC.4
In Angular 2.0, loadIntoLocation method of DynamicComponentLoader serve this purpose of creating parent-child relationship. By using this approach you can dynamically create relationship between two components.
Here is the sample code in which paper is my parent and bulletin is my child component.
paper.component.ts
import {Component,DynamicComponentLoader,ElementRef,Inject,OnInit} from 'angular2/core';
import { BulletinComponent } from './bulletin.component';
#Component({
selector: 'paper',
templateUrl: 'app/views/paper.html'
}
})
export class PaperComponent {
constructor(private dynamicComponentLoader:DynamicComponentLoader, private elementRef: ElementRef) {
}
ngOnInit(){
this.dynamicComponentLoader.loadIntoLocation(BulletinComponent, this.elementRef,'child');
}
}
bulletin.component.ts
import {Component} from 'angular2/core';
#Component({
selector: 'bulletin',
template: '<div>Hi!</div>'
}
})
export class BulletinComponent {}
paper.html
<div>
<div #child></div>
</div>
Few things you needs to be take care of are mentioned in this answer
You should use ComponentFactoryResolver and ViewElementRef to add component at runtime.Let's have a look at below code.
let factory = this.componentFactoryResolver.resolveComponentFactory(SpreadSheetComponent);
let res = this.viewContainerRef.createComponent(factory);
Put the above code inside your ngOnInit function and replace "SpreadSheetComponent" by your component name.
Hope this will work.
Programmatically add components to DOM in Angular 2/4 app
We need to use ngAfterContentInit() lifecycle method from AfterContentInit. It is called after the directive content has been fully initialized.
In the parent-component.html, add the a div like this:
<div #container> </div>
The parent-component.ts file looks like this:
class ParentComponent implements AfterContentInit {
#ViewChild("container", { read: ViewContainerRef }) divContainer
constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
ngAfterContentInit() {
let childComponentFactory = this.componentFactoryResolver.resolveComponentFactory(childComponent);
this.divContainer.createComponent(childComponentFactory);
let childComponentRef = this.divContainer.createComponent(childComponentFactory);
childComponentRef.instance.someInputValue = "Assigned value";
}
}
Inside src\app\app.module.ts, add the following entry to the #NgModule() method parameters:
entryComponents:[
childComponent
],
Notice that we're not accessing the div#container using the #ViewChild("container") divContainer approach. We need it's reference instead of the nativeElement. We will access it as ViewContainerRef:
#ViewChild("container", {read: ViewContainerRef}) divContainer
The ViewContainerRef has a method called createComponent() which requires a component factory to be passed as a parameter. For the same, we need to inject a ComponentFactoryResolver. It has a method which basically loads a component.
The right approach depends on the situation you're trying to solve.
If the number of children is unknown then NgFor is the right approach.
If it is fixed, as you mentioned, 3 children, you can use the DynamicComponentLoader to load them manually.
The benefits of manual loading is better control over the elements and a reference to them within the Parent (which can also be gained using templating...)
If you need to populate the children with data, this can also be done via injection, the Parent is injected with a data object populating the children in place...
Again, a lot of options.
I have used 'DynamicComponentLoader' in my modal example, https://github.com/shlomiassaf/angular2-modal