I have an angular 8 application. I am testing MyCustomComponent with the current template :
<mat-card>
<table>
<another-custom-component></another-custom-component>
</table>
</mat-card>
To get a child component of my current tested component I use :
fixture.debugElement.queryAll(By.directive(AnotherCustomComponent))
But in this example in my fixture.debugElement.childNodes I would find only one node, the MatCard.
How to get nested childs ?
I guess I could try to find the wanted child with recursive function, but does there is any built-in method for that ?
For every directive you should have separate describe and for every describe you can have different beforeEach. You should test one child at a time within it's parent, so it is kind of anti-pattern to test all your child-tree at once.
Related
I have a component named 'stack-item' which have a slot , its html will look like this
<div>
<div>1<div>
<div class='slot-style'><slot></slot></div>
</div>
I have used this component like this
<stack-item>
<another-component></another-component>
</stack-item>
From 'another-component' how do i get the element reference for class ='slot-style' for calculating the slot width and other properties ?
I wrote a workaround
let slotElement =this.shadowRoot.host.parentElement.shadowRoot.querySelector('slot').parentElement;
but is there a clean way of achieving this?
I don't know if this can be considered dramatically simpler, but the scenario you give indicates that you already know the host component is stack-item and that component has a slot wrapper with the class name 'slot-style'. Given that you can obtain a reference like this (this works from within the component as well as the host document):
let slotElement = document.querySelector("stack-item").shadowRoot.querySelector(".slot-style");
//display width
console.log(slotElement.clientWidth);
this.shadowRoot.host is equivalent to this.
So you could try:
this.parentElement.shadowRoot.querySelector( 'div.slot-style' )
We are migrating a medium sized app from polymer 1 to polymer 3. Thus far we are stuck in the intermediate step of getting our hybrid components to work.
We are encounting some difficulties regarding component initialization timing. For example:
<my-app>
<my-component slot='componentslot'><my-component>
</my-app>
It seems there are cases where my-component is initialized before my-app is initialized. It might vary wether my-component is part of shadow- or light-dom.
We have a lot of tightly coupled components which depend on deterministic initialization order. For example there is a tree-like structure where every edge and every leaf uses events to discover it's own depth in the tree. Therefore we need top-level elements to be initialized before inner components.
But what we found so far was essentially: There is no garantuee for any initialization order of the components.
Is there an established pattern for solving this problem? Will this problem be solved in polymer 3 (so we don't need to care about it anyway)?
Edit
I was asked for some more specific examples
Example 1
<my-layout>
<my-complex-component id="1">
<my-reuseable-part/>
</my-complex-component>
<my-complex-component id="2">
<my-reuseable-part/>
</my-complex-component>
<some-other-component>
<my-reuseable-part/>
</some-other-component>
</my-layout>
I have some reuseable components which need to know if they are inside my-complex-component orsome-other-component. my-complex-component uses a context-discovery-behavior which fires an event containing a callback as payload. my-complex-component and some-other-component have context-behaviors which listen to that event and answer it by invoking the callback.
But as my-reusable-part might be attached before my-complex-component or some-other-component is attached, this pattern does not work.
Registration of event listeners as well as firing the disovering event is done in attached (i.e. connectedCallback).
Example 2
<my-tree>
<my-tree-edge>
<my-tree-edge>
<my-leaf/>
<my-tree-edge>
<my-leaf/>
</my-tree-edge>
</my-tree-edge>
<my-tree-edge>
<my-leaf/>
</my-tree-edge>
<my-leaf/>
</my-tree-edge>
</my-tree>
In the example above every leaf and edge needs to know how deep it is nested. Again every elements fires an event and its parent will answer the event. Again listener registration and event-firing is done in attached/connectedCallback. Again the mechanik fails if an inner node is attached before it's parents are attached.
Hope this helps.
You can use dom-if element if you stricly want to be sure first render my-app then you can let render my-component something like:
<my-app ready="{{myAppReady}}>
<template is='dom-if' if="[[myAppReady]]">
<my-component slot='componentslot'><my-component>
</template>
</my-app>
at my-app script:
static get properties(){return {
ready:{type:Boolean,
notify:true,
value:false
}}
at this part, you may add computed:"checkThisValuesToBeSUre(x,[y]..) in order to be sure if depended to some values or you may add various conditions in order to render my-component
Also, you may import my-component.js dynamically like:
At my-app 's parent script:
static get observers(){return ['_checkMyAppReady(myAppReady)']}
_checkMyAppReady(r){
if(r) import('./my-component.js');
}
EDIT
If there are many elements occurs the same problem, then better to use lazy-import.js:
_checkMyAppReady(r){
if(r) import('./lazy-import.js');
}
lazy-import.js
import './my-component.js';
import './my-component2.js';
import './my-component3.js';
...
In some instances, I need to just repeat some html code within my Template to DRY it up, but making a new component and passing a ton of props and dynamic data to it seems like overkill. Is there a way to define a repeatable block of template code that can just be reused?
A good example of this is my vuelidate validation error messages that are repeated. I don't want to create an entire vue component for them because then I need to pass in the validation, validation prop and a few other things so that seems like creating more complexity just to DRY up a little bit of the template.
I have this block of code on three different scenarious in the same template, is there a way I can just define them as a block to reuse. Literally nothing changes so it's very much against DRY principles.
<span
v-if="!$v.initialReplyText.required"
class="error">Your reply cannot be empty.</span>
<span
v-if="!$v.initialReplyText.maxLength"
class="error">Your reply cannot be over 2,000 characters.</span>
you can do dynamic binding using v-bind, that way you don't need to bind all properties individually.
<!-- pass down parent props in common with a child component -->
<child-component v-bind="$props"></child-component>
src: https://v2.vuejs.org/v2/api/#v-bind
You can also use slots, or scoped slots, which are commonly used for things like wrapping error messages in more complex markup.
If all elements are consecutively arranged as in your example, you can use v-for as below:
<span v-for="(criteria, msg) in {'Your reply cannot be empty.': !$v.initialReplyText.required, 'Your reply cannot be over 2,000 characters.': !$v.initialReplyText.maxLength }"
v-if="criteria" class="error">
{{msg}}
</span>
I have a problem with the new ng-content transclusion.
Let's say I have a component my-component that, in its ngOnInit() function does some heavy operation on load (for now, just a console.log()).
I have a wrapper, that displays the content via transclusion (my-wrapper.component.html).
<ng-content></ng-content>
If I set the surroundings up like this, the log statement doesn't show:
<my-wrapper *ngIf="false">
<my-component></my-component>
</my-wrapper>
I assume, the my-wrapper component does not get built, so the content is ignored.
But if I try to move the logic into the my-wrapper component like this (my-wrapper.component.html):
<ng-container *ngIf="false">
<ng-content></ng-content>
</ng-container>
I always see the console.log() output. I guess, the my-component gets built and then stored away until the *ngIf becomes true inside my-wrapper.
The intention was to build a generic "list-item + detail" component. Say I have a list of N overview-elements (my-wrapper), that get rendered in a *ngFor loop. Every of those elements has its own detail component (my-component) that is supposed to load its own data, once I decide to show more infos to a specific item.
overview.html:
<ng-container *ngFor="let item of items">
<my-wrapper>
<my-component id="item.id"></my-component>
</my-wrapper>
</ng-container>
my-wrapper.component.html:
<div (click)="toggleDetail()">Click for more</div>
<div *ngIf="showDetail">
<ng-content></ng-content>
</div>
Is there a way to tell Angular, to ignore the transcluded content until it is necessary to be added to the page? Analogously to how it was in AngularJS.
Based on the comment of #nsinreal I found an answer. I find it to be a bit abstruse, so I'm trying to post it here:
The answer is to work with ng-template and *ngTemplateOutlet.
In the my-wrapper component, set up the template like this (my-wrapper.component.html):
<div (click)="toggleDetail()">Click for more</div>
<div *ngIf="showDetail" [hidden]="!isInitialized">
<ng-container *ngTemplateOutlet="detailRef"></ng-container>
</div>
Note, that the [hidden] there is not really necessary, it hides the "raw" template of the child until it decides it is done loading. Just make sure, not to put it in a *ngIf, otherwise the *ngTemplateOutlet will never get triggered, leading to nothing happening at all.
To set the detailRef, put this in the component code (my-wrapper.component.ts):
import {Â ContentChild, TemplateRef } from '#angular/core';
#Component({ ... })
export class MyWrapperComponent {
#ContentChild(TemplateRef) detailRef;
...
}
Now, you can use the wrapper like this:
<my-wrapper>
<ng-template>
<my-component></my-component>
</ng-template>
</my-wrapper>
I am not sure, why it needs such complicated "workarounds", when it used to be so easy to do this in AngularJS.
By doing this:
<my-wrapper *ngIf="false">
<my-component></my-component>
</my-wrapper>
You are not calling MyComponent component, because the *ngIf is false. that means, that not calling it you are not instancing it and, therefore, not passing through its ngOnInit. And that's why you are not getting the console log.
By doing this:
<ng-container *ngIf="false">
<ng-content></ng-content>
</ng-container>
You are inside the component, you are just limiting what to render in your template, but you already instanced your component and, therefore, you passed through your ngOnInit and you get your console log done.
If, you want to limit something (component call with selector or a ng-content or even a div) until you have some data available, you can do the following:
datasLoaded: Promise<boolean>;
this.getData().subscribe(
(data) => {
this.datasLoaded = Promise.resolve(true); // Setting the Promise as resolved after I have the needed data
}
);
And in your template:
<ng-container *ngIf="datasLoaded | async">
// stuff here
</ng-container>
Or:
<my-component *ngIf="datasLoaded | async">
// Didn't test this one, but should follow the same logic. If it doesn't, wrap it and add the ngIf to the wrapper
</my-component>
It’s because Ng content happens at the build time and when you pass the content it is actually not removed or recreated with the ngIf directive. It is only moved and the component is instantiated .
I encountered this problem recently as well but settled on a different solution than the currently accepted one.
Solution (TL;DR)
(Solution is for AngularDart; I figure it's similar in Angular though)
Use a structural directive; tutorials linked below.
Instead of:
<my-wrapper>
<my-contents></my-contents>
</my-wrapper>
your usage becomes:
<div *myWrapper>
<my-contents></my-contents>
</div>
which is shorthand for the following (in AngularDart; I think Angular uses <ng-template>)
<template myWrapper>
<div>
<my-contents></my-contents>
</div>
</template>
The MyWrapper directive logic is similar to NgIf except it has its own logic to compute the condition. Both of the following tutorials explain how to create an NgIf-like directive and how to pass it your own inputs using the special microsyntax (e.g. *myWrapper="myInput: expression"). Note that the microsyntax doesn't support outputs (#Output), but you can mimic an output by using an input that is a function.
Tutorial for Angular
Tutorial for AngularDart
Caveat: Since this is just a directive, it shouldn't do anything more complicated than instantiating a template ref at the appropriate time and maybe specifying some DI providers. For example, I would avoid trying to apply styles or instantiating a complex tree of components in the directive. If I wanted to create a list component, I would probably take the #ContentChild(TemplateRef) approach described in another answer; you would lose the asterisk shorthand for creating <template> but you would gain the full power of components.
My problem
My team owns an app that's part of a larger web application with other apps owned by other teams. Our components assume they can inject a MyAppConfiguration object, but this object can only be injected after it is loaded with an asynchronous request. In our app this is not a problem: we have a "shell" component that hides everything behind an ngIf until the configuration is loaded.
The problem is when other teams want to reference our components. We don't want them to duplicate the "wait until configuration is loaded" logic every time, so I tried creating a wrapper component that can be used like so:
<my-app-wrapper>
<my-app-component></my-app-component>
</my-app-wrapper>
The wrapper injects a service object and hides its contents behind an ngIf until the service says that the configuration is loaded.
Like the question poster, I discovered that the ng-content approach doesn't work as intended: while the contents are correctly hidden from the DOM, Angular still instantiates the components causing dependency injection to fail.
The solution that I settled on was to rewrite the wrapper component as a structural directive.
I got an error when i put a nested ng-show attributes for custom directive,
one attribute in the markup of the directive and the second inside the root element of the directive template.
My real scenario are complex so i will simplify it to this example:
Suppose i have my-custom-directive below which already contains ng-show:
<my-custom-directive ng-show="someValue >= 5"></my-custom-directive>
And then the template of 'my-custom-directive' look like this:
<div ng-show="options != null">My Custom Directive</div>
Those multiple ng-show together cause an error.
if i remove one of them or move the inner ng-show at least one level deeper in it's dom tree the error gone (it's happen when it's location is on the root template element).
this error tested on angular v1.4.8.
Is this angular bug? or there is a reasonable explanation for this behavior?
here is the Plunker example:
http://embed.plnkr.co/ZTZVcfc5bfmjPo9t0Isw
Thank you in advance,
Menachem
Because the directive has replace: trueit is trying to merge the two ng-show values together resulting in an error. The simplest solution I believe is to just do replace: false
Or you can inject the value via isolate scope and use a single ng-show value within the directive. I believe this is considered the cleaner solution.
Example: http://plnkr.co/edit/5oc8c1Hrz8N1F2klCio7?p=info
scope: {
someValue: '=someValue'
}