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?
Related
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>
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);
}
What's the best way to load a ko component with JavaScript code instead of defining a custom element in html? I tried with ko.components.defaultLoader.load but my component constructor does not hit.
I double checked and the component appears to be registered.
I believe what you are looking for is function ko.components.get(componentName, callback). What this method does is ask the component loaders to resolve the component name until it finds one. If it doesn't find one, it will call callback(null). If it does fine one, it will call callback(componentDefinition), where componentDefinition is the object used to register the component, like { viewmodel: ..., template: ...}.
As far as I can tell, there isn't a ready made function which returns a "working" component. What you have to do after getting the componentDefinition object is something like:
convert the template into a DOM element
instantiate the viewmodel (if defined)
bind the viewmodel to the DOM element
Note that this is not straight away because templates and view models can be defined in several ways.
I recommend looking at https://github.com/knockout/knockout/blob/master/src/components/componentBinding.js and see how it's done here (from line 38).
I hope this works for you, otherwise you could consider other options, like dynamically creating a div element in code with a component binding where the component name and parameters are bound to properties of a view model. Then bind this view model to the div element you just created. This should work "code only" which much less code than the other route.
I wonder if I can modify a property that is in a component via an external controller.
That is, I have an injected component in index.html as follows:
{{ button-feed }}
This component is used in many views.
This component has to be hidden as I get values in the controller, and what I really want is that since this controller, modify a property that hides or shows the button.
The component has the form:
App.ButtonComponent = Ember.Component.extend ({
hideClass: false
});
The property hideClass is used to display or not the button. What I want is to modify this property but using a controller that does not belong to the component button.
I tried to access the property from outside the component, but it is impossible.
You can pass parameters to your component like this:
{{button-feed hideClass=true}}
{{button-feed hideClass=false}}
Also, you could pass in a controller property too.
{{button-feed hideClass=controllerProperty}}
To answer your comment, you can set the controllerProperty by using the code below. Since controllerProperty is bound to the hideClass on your component, changing controllerProperty will change hideClass.
controller.set('controllerProperty', false);
You can read more about setting properties on a controller here.
I'd like to render a component that I will define within a variable.
I have a variable containing content like that:
page.js
Ember.Controller.extend({
c: {
'pageTitle': 'This is the title with a component {{my-component}}'
}
});
page.hbs
<div>
{{c.pageTitle}}
</div>
The c object is populated from an API call, from a content server.
I would like to provide the capability to inject components from what is defined in the content.
Basically what I need would be to render 2 times, the first time to replace my {{pageTitle}} with the string, and the second time to replace {{my-component}} with the component.
What would be the best solution to do such a thing?
Thanks
You could render the component using the new {{component}} helper (in the latest version of Ember)
{{component 'my-component'}}
{{component c.myPageTitleComponent}}
For the text to be present, you could do two things:
In your Template
You could implement the text directly in your template
{{c.myPageTitleText}} {{component c.myPageTitleComponent}}
Using a Parent Component
You could implement a parent component:
{{my-parent-component text=c.myPageTitleText componentName=c.myPageTitleComponent}}
Then in 'my-parent-component', it would look like this:
{{text}} {{component componentName}}
This doesn't really make sense though unless you needed some custom logic in my-parent-component.