is there a way lazy load a component in angular 2 dart? - dart

I have a component that uses another component with a ngIf statement. I would like to only load the second component once the ngIf evaluates to true.
EDIT: found an article that can almost do what I need:
https://medium.com/#matanlurey/lazy-loading-with-angular-dart-14f58004f988. However, after the library loaded, it takes the whole view of the component. In my case, I need to insert it into a specific place in the html of the parent component.
Something like:
import '../other/edit_component.dart' deferred as otherEdit;
#Component(
selector: 'edit-component',
template: '<other-component *ngIf="canOther"></other-component>
<button type="button" (click)="go()"></button>',
directives: const [
otherEdit.OtherComponent
]
)
class EditComponent {
#Input()
bool canOther = false;
go() {
otherEdit.loadLibrary();
canOther = true;
}
}

I do not think you can do it directly. What you can do instead is using DynamicComponent from Angular2_components and pass the type after lazily loading it.

Just made it work. Used the DynamicComponent as example from rkj answer.
// the lib that has the component to be loaded
import 'package:somecomponent.dart' deferred as mycomponent;
class mainComponent {
// <div #holder></div> element that we will append the component
#ViewChild('holder', read: ViewContainerRef)
ViewContainerRef holder;
// this will load the component dynamically
final DynamicComponentLoader _componentLoader;
load() {
// clear the html
holder.clear();
// load the component dynamically
ComponentRef componentRef = await _componentLoader
.loadNextToLocation(componentType, holder);
// set some attributes like you would with [attributes]="somevar"
componentRef.instance
..attribute = somevar;
}
mainComponent(this. _componentLoader){}
}

Related

How to get access to TemplateRef from Outside a Component

From the doc, I can read:
Alternatively you can query for the TemplateRef from a Component or a Directive via Query.
Question:
How to Query a TemplateRef from an external Component?
Here some code from the doc:
#Component(
selector: 'child-cmp',
template: '<p>child</p>',
)
class ChildCmp {
void doSomething() {}
}
#Component(
selector: 'some-cmp',
template: '''
<child-cmp #child1></child-cmp>
<child-cmp #child2></child-cmp>
<child-cmp #child3></child-cmp>
''',
directives: [ChildCmp],
)
class SomeCmp implements AfterViewInit {
#ViewChildren('child1, child2, child3')
List<ChildCmp> children;
#override
void ngAfterViewInit() {
// Initial children are set
for (var child in children) {
child.doSomething();
}
}
}
How from SomeCmp can I get ChildCmp's TemplateRef. What should I do?
I'm not sure how to access a component's template ref but if you figure that out then you can simply make that a public property in the child component and access it from the parent.
A workaround that I did is simply use an <ng-template> to store the child's template and render it in an <ng-container>. I them store the reference to that ng-template in the child component as a public property and it is easily accessible from the parent. I don't believe this is the cleanest solution but it satisfies your question.
You can check out my solution at this stackblitz: https://stackblitz.com/edit/angular-ivy-kahwbz?file=src/app/app.component.ts

Glimmer child component does not re-render when the parent passes it a different argument

To be more explicit, the child component creates a property which is dependent on the argument passed by the parent component. I am not using the parent argument directly in the child template (this case works just fine).
Coming from a React background, my mental model suggests new arguments passed to a component will trigger re-render. But I am aware Glimmer does things differently with its #tracked decorator.
Okay, here is the contrived example. For a demo, head to the Glimmer Playground.
// parent-template.hbs
<button onclick={{action doubleNumber}}>Double Number</button>
<div>
Parent - {{number}}
</div>
<Child #number={{number}} />
// parent-component.ts
import Component, { tracked } from '#glimmer/component';
export default class extends Component {
#tracked number = 2;
doubleNumber() {
this.number = this.number * 2;
}
}
// child-template.ts
<div>
Child will render double of parent {{doubleOfParent}}
</div>
// child-component.ts
import Component, { tracked } from "#glimmer/component";
export default class extends Component {
args: {
number: number;
}
get doubleOfParent () {
return 2 * this.args.number;
}
};
Here the parent displays the doubled number on every click of the button. But the child never re-renders?
My question is do we always need to have the tracked variable inside the template. In this case number. And express the child template like this
<div>
Child will render double of parent {{double #number}}
</div>
Here double is helper which doubles the number.
If it is so what is the reason behind having the tracked properties/argument in the template?
It looks like your doubleOfParent() method is missing a #tracked annotation since its output depends on the args property:
import Component, { tracked } from "#glimmer/component";
export default class extends Component {
args: {
number: number;
}
#tracked('args')
get doubleOfParent() {
return 2 * this.args.number;
}
};
you can find more information on this topic at https://glimmerjs.com/guides/tracked-properties

Writing singleton component that will reference only one element

I need to dynamically insert social embeds and in order to run scripts, I want to insert them into head, similar to how jQuery's .html() is doing. How would I go about writing component that I can inject into other components but will always reference head of document? I tried this:
#Decorator(
selector: 'head'
)
#Injectable()
class ScriptLoader {
Element element;
ScriptLoader(this.element) {
}
loadScript(String url) {
ScriptElement script = new ScriptElement();
script.src = url;
script.setAttribute("async", "");
script.onLoad.listen((Event e) {
script.remove();
});
element.append(script);
}
}
and when page is loaded, this.element is a reference to head but when it is injected into other component, it throws with error No provider found for Element! (resolving ExecuteBindHTML -> ScriptLoader -> ScriptLoader -> Element). How would I go about implementing such component? I don't want to use querySelectors (because Angular).
You can get the head element with querySelector directly in the constructor.
#Injectable()
class ScriptLoader {
Element element;
ScriptLoader() {
element = querySelector("head");
}
}
Then add the ScriptLoader class to the bootstrap method directly.

Angular2: Accessing child nodes from a template

I have a component and I would like accessing some child nodes from the template. I achieved to access the details div, but I don't know why the code works. What exactly does the Future class? And why the first line prints null? Is this the correct way to access child nodes from the template?
#Component(selector: 'hero-detail', template: '<div #details></div>')
class HeroDetailComponent implements OnInit {
Hero hero;
#ViewChild('details')
var details;
Future ngOnInit() async {
// why this command prints null?
print(details);
// why this command prints "Instance of 'ElementRef_'"
new Future(() => print(details));
}
}
#Component(selector: 'hero-detail', template: '<div #details></div>')
class HeroDetailComponent implements OnInit {
Hero hero;
// Angular generates additional code that looks up the element
// from the template that has a template variable `#details
// and assigns it to `var details`
#ViewChild('details')
var details;
// I don't think Future does anything here.
Future ngOnInit() async {
// why this command prints null?
// this is too early. `#ViewChild()` is only set in `ngAfterViewInit`
// at this point the view is not yet fully created and therefore
// `#details can't have been looked up yet
print(details);
// why this command prints "Instance of 'ElementRef_'"
// this delays `print(details)` until the next Dart event loop
// and `details` is then already lookup up and assigned
new Future(() => print(details));
}
// this is the right place
// needs `class HeroDetailComponent implements OnInit, AfterViewInit {
ngAfterViewInit() {
print(details);
}
}

How to decorate a component in angular.dart

I would like to provide a possibility to show my components in a bit different look and feel and thought using the decorator for it. Something like:
<body>
<my-component my-decorator></my-component>
</body>
.
#Component(
selector: 'my-component',
templateUrl: '.../my-component.html',
cssUrl: '.../my-component.css',
publishAs: 'comp',
)
class MyComponent {
MyComponent(final Element element) {
Logger.root.fine("MyComponent(): element = $element, element.attributes = ${element.attributes.keys}");
}
}
#Decorator(selector: '[my-decorator]')
class MyDecorator {
final Element element;
#NgOneWay('my-decorator')
var model; // is not used
MyDecorator(this.element) {
Logger.root.fine("MyDecorator(): element = $element, element.nodeName = ${element.nodeName}");
Logger.root.fine("MyDecorator(): element.shadowRoot = ${element.shadowRoot}, element.parent = ${element.parent}");
}
}
Unfortunately, it seems that my-decorator is processed before my-component so it is getting null shadowRoot property in the injected Element object.
It would be possible to check on existence of the my-decorator attribute within the my-component backing class, but that is clearly polluting the design.
UPDATE: Thanks to replay from Marko Vuksanovic, the following is now returning the :
#Decorator(selector: '[my-decorator]')
class MyDecorator extends AttachAware {
final Element element;
#NgOneWay('my-decorator')
var model; // is not used
MyDecorator(this.element) {
Logger.root.fine("MyDecorator(): element = $element, element.nodeName = ${element.nodeName}");
Logger.root.fine("MyDecorator(): element.shadowRoot = ${element.shadowRoot}, element.parent = ${element.parent}");
}
void attach() {
Logger.root.fine("attach(): element.shadowRoot = ${element.shadowRoot}");
}
}
The question still remains how to modify the styling of the shadow DOM.
Thanks in advance for any comments/ideas/solutions.
You can try using AttachAware and it's attach method. You should implement AttachAware interface in your decorator and/or component.
Here's link to Angular.dart docs - https://docs.angulardart.org/#angular-core-annotation.AttachAware
To change the styling of a ShadowDom component you can use element.shadowRoot to get the root of your web component. Shadow root is almost like 'document' object. You can use shadow root to get reference to any element and then you can easily modify it by applying styles as needed.
You could use something like
this.element.shadowRoot.querySelector('[some-attr]').innerHtml = "Modified by decorator" // disclaimer: not tested, but I hope you get the idea.
You can add a style tag to the shadowDom programmatically:
shadowRoot.append(new StyleElement()..text = ':host{background: red;}');
or
shadowRoot.append(new StyleElement()..text = "#import url('some.css')");

Resources