I am trying to call jQuery function (i.e. initialize bootstrap selectpicker) at page load in ng2 app:
jQuery(this.elementRef.nativeElement).find("select").selectpicker({
iconBase: 'fa',
tickIcon: 'fa-check',
title: '...'
});
(assume that jQuery and elementRef are defined and it works well in other cases)
I try to call this function in ngOnInit, ngAfterContentInit, ngAfterViewInit but with no luck.
I suspect it calls before element is rendered or so.
Here is my template of select which I try to convert into bootstrap selectpicker:
<select ngControl="hashtags" [(ngModel)]="hashtags" multiple>
<option *ngFor="#hashtag of card.hashtags" [value]="hashtag.id" (click)="selectHashtag(hashtag.id)">{{hashtag.name}}</option>
</select>
Any ideas how to call it right?
UPD: seems like the issue comes from a fact that a form (and my select element) is rendered inside ngIf:
<div *ngIf="card">
...
</div>
so, at the moment of rendering page and calling ngOnInit() form is not rendered yet.
I will try to work it around by applying different approach, but how would you call function AFTER http request and form render are complete?
Wouldn't it be similar to this:
import {Component, ElementRef, OnInit} from 'angular2/core';
declare var jQuery:any;
#Component({
selector: 'jquery-integration',
templateUrl: './components/jquery-integration/jquery-integration.html'
})
export class JqueryIntegration implements OnInit {
elementRef: ElementRef;
constructor(elementRef: ElementRef) {
this.elementRef = elementRef;
}
ngOnInit() {
jQuery(this.elementRef.nativeElement).draggable({containment:'#draggable-parent'});
}
}
More info here: http://www.syntaxsuccess.com/viewarticle/using-jquery-with-angular-2.0
and the right answer is to call it inside ngAfterViewChecked() {}
AfterViewChecked callback triggers everytime page is changed, thus to make sure my functions are called only once I set flags i.e. selectpickerEnabled = true and check them before calling function again.
I believe there must be the right way to do it, but that's how I am going to solve it for now...
Related
I understand angular is one page application with multiple components and use route to interact between pages.
We have an angular app like that. One of the requirements is that on a specific component we need to add an eventListener to DomContentLoaded event.
Since the index.html page has been loaded (hence DomContentLoaded event has been fired) way before, we have a problem.
I cannot find a way to re-trigger the DomContentLoaded event.
The DomContentLoaded event fired before the first component was created. You can however use the ngAfterViewInit() method.
ngAfterViewInit() is a callback method that is invoked immediately after Angular has completed initialization of a component's view. It is invoked only once when the view is instantiated.
class YourComponent {
ngAfterViewInit() {
// Your code here
}
}
If you really need to catch the DomContentLoaded event anyway, do it in the main.ts file.
I don't know exactly, why you need to listen to DomContentLoaded in angular, because there are plenty of functions there, depends on which Version of angular do you use.
export class SomeComponent implements AfterViewInit {
ngAfterViewInit(): void {
}
}
But, you can retrigger an rendering of an component with *ngIf (Angular2+) or ng-if (AngularJS). Just put a boolean there and switch the boolean on an event.
Please try not use such listeners, they are (often) not needed in Angular. Try to use Component lifecycle hooks.
In your case, I recommend using ngAfterViewChecked(), which fires when the component finished rendering.
import { Component, AfterViewChecked } from '#angular/core';
#Component({
...
})
export class WidgetLgFunnelsComponent implements AfterViewChecked {
ngAfterViewChecked() {
console.log('Component finished rendering!')
}
}
If you want to do something with the DOM elements inside the template of the component, and you want to make sure they are there, you should use the ngAfterViewInit hook from angular:
#Component({
// ...
})
export class SomeComponent implements AfterViewInit {
ngAfterViewInit(): void {
// here you can be sure that the template is loaded, and the DOM elements are available
}
}
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>
I have a custom element :
<div formControlName="surveyType">
<div *ngFor="let type of surveyTypes"
(click)="onSelectType(type)"
[class.selected]="type === selectedType">
<md-icon>{{ type.icon }}</md-icon>
<span>{{ type.description }}</span>
</div>
</div>
When I try to add the formControlName, I get an error message:
ERROR Error: No value accessor for form control with name:
'surveyType'
I tried to add ngDefaultControl without success.
It seems it's because there is no input/select... and I dont know what to do.
I would like to bind my click to this formControl in order that when someone clicks on the entire card that would push my 'type' into the formControl. Is it possible?
You can use formControlName only on directives which implement ControlValueAccessor.
Implement the interface
So, in order to do what you want, you have to create a component which implements ControlValueAccessor, which means implementing the following three functions:
writeValue (tells Angular how to write value from model into view)
registerOnChange (registers a handler function that is called when the view changes)
registerOnTouched (registers a handler to be called when the component receives a touch event, useful for knowing if the component has been focused).
Register a provider
Then, you have to tell Angular that this directive is a ControlValueAccessor (interface is not gonna cut it since it is stripped from the code when TypeScript is compiled to JavaScript). You do this by registering a provider.
The provider should provide NG_VALUE_ACCESSOR and use an existing value. You'll also need a forwardRef here. Note that NG_VALUE_ACCESSOR should be a multi provider.
For example, if your custom directive is named MyControlComponent, you should add something along the following lines inside the object passed to #Component decorator:
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => MyControlComponent),
}
]
Usage
Your component is ready to be used. With template-driven forms, ngModel binding will now work properly.
With reactive forms, you can now properly use formControlName and the form control will behave as expected.
Resources
Custom Form Controls in Angular by Thoughtram
Angular Custom Form Controls with Reactive Forms and NgModel by Cory Rylan
You should use formControlName="surveyType" on an input and not on a div
The error means, that Angular doesn't know what to do when you put a formControl on a div.
To fix this, you have two options.
You put the formControlName on an element, that is supported by Angular out of the box. Those are: input, textarea and select.
You implement the ControlValueAccessor interface. By doing so, you're telling Angular "how to access the value of your control" (hence the name). Or in simple terms: What to do, when you put a formControlName on an element, that doesn't naturally have a value associated with it.
Now, implementing the ControlValueAccessor interface can be a bit daunting at first. Especially because there isn't much good documentation of this out there and you need to add a lot of boilerplate to your code. So let me try to break this down in some simple-to-follow steps.
Move your form control into its own component
In order to implement the ControlValueAccessor, you need to create a new component (or directive). Move the code related to your form control there. Like this it will also be easily reusable. Having a control already inside a component might be the reason in the first place, why you need to implement the ControlValueAccessor interface, because otherwise you will not be able to use your custom component together with Angular forms.
Add the boilerplate to your code
Implementing the ControlValueAccessor interface is quite verbose, here's the boilerplate that comes with it:
import {Component, OnInit, forwardRef} from '#angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '#angular/forms';
#Component({
selector: 'app-custom-input',
templateUrl: './custom-input.component.html',
styleUrls: ['./custom-input.component.scss'],
// a) copy paste this providers property (adjust the component name in the forward ref)
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}
]
})
// b) Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor {
// c) copy paste this code
onChange: any = () => {}
onTouch: any = () => {}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
// d) copy paste this code
writeValue(input: string) {
// TODO
}
So what are the individual parts doing?
a) Lets Angular know during runtime that you implemented the ControlValueAccessor interface
b) Makes sure you're implementing the ControlValueAccessor interface
c) This is probably the most confusing part. Basically what you're doing is, you give Angular the means to override your class properties/methods onChange and onTouch with it's own implementation during runtime, such that you can then call those functions. So this point is important to understand: You don't need to implement onChange and onTouch yourself (other than the initial empty implementation). The only thing your doing with (c) is to let Angular attach it's own functions to your class. Why? So you can then call the onChange and onTouch methods provided by Angular at the appropriate time. We'll see how this works down below.
d) We'll also see how the writeValue method works in the next section, when we implement it. I've put it here, so all required properties on ControlValueAccessor are implemented and your code still compiles.
Implement writeValue
What writeValue does, is to do something inside your custom component, when the form control is changed on the outside. So for example, if you have named your custom form control component app-custom-input and you'd be using it in the parent component like this:
<form [formGroup]="form">
<app-custom-input formControlName="myFormControl"></app-custom-input>
</form>
then writeValue gets triggered whenever the parent component somehow changes the value of myFormControl. This could be for example during the initialization of the form (this.form = this.formBuilder.group({myFormControl: ""});) or on a form reset this.form.reset();.
What you'll typically want to do if the value of the form control changes on the outside, is to write it to a local variable which represents the form control value. For example, if your CustomInputComponent revolves around a text based form control, it could look like this:
writeValue(input: string) {
this.input = input;
}
and in the html of CustomInputComponent:
<input type="text"
[ngModel]="input">
You could also write it directly to the input element as described in the Angular docs.
Now you have handled what happens inside of your component when something changes outside. Now let's look at the other direction. How do you inform the outside world when something changes inside of your component?
Calling onChange
The next step is to inform the parent component about changes inside of your CustomInputComponent. This is where the onChange and onTouch functions from (c) from above come into play. By calling those functions you can inform the outside about changes inside your component. In order to propagate changes of the value to the outside, you need to call onChange with the new value as the argument. For example, if the user types something in the input field in your custom component, you call onChange with the updated value:
<input type="text"
[ngModel]="input"
(ngModelChange)="onChange($event)">
If you check the implementation (c) from above again, you'll see what's happening: Angular bound it's own implementation to the onChange class property. That implementation expects one argument, which is the updated control value. What you're doing now is you're calling that method and thus letting Angular know about the change. Angular will now go ahead and change the form value on the outside. This is the key part in all this. You told Angular when it should update the form control and with what value by calling onChange. You've given it the means to "access the control value".
By the way: The name onChange is chosen by me. You could choose anything here, for example propagateChange or similar. However you name it though, it will be the same function that takes one argument, that is provided by Angular and that is bound to your class by the registerOnChange method during runtime.
Calling onTouch
Since form controls can be "touched", you should also give Angular the means to understand when your custom form control is touched. You can do it, you guessed it, by calling the onTouch function. So for our example here, if you want to stay compliant with how Angular is doing it for the out-of-the-box form controls, you should call onTouch when the input field is blurred:
<input type="text"
[(ngModel)]="input"
(ngModelChange)="onChange($event)"
(blur)="onTouch()">
Again, onTouch is a name chosen by me, but what it's actual function is provided by Angular and it takes zero arguments. Which makes sense, since you're just letting Angular know, that the form control has been touched.
Putting it all together
So how does that look when it comes all together? It should look like this:
// custom-input.component.ts
import {Component, OnInit, forwardRef} from '#angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '#angular/forms';
#Component({
selector: 'app-custom-input',
templateUrl: './custom-input.component.html',
styleUrls: ['./custom-input.component.scss'],
// Step 1: copy paste this providers property
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}
]
})
// Step 2: Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor {
// Step 3: Copy paste this stuff here
onChange: any = () => {}
onTouch: any = () => {}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
// Step 4: Define what should happen in this component, if something changes outside
input: string;
writeValue(input: string) {
this.input = input;
}
// Step 5: Handle what should happen on the outside, if something changes on the inside
// in this simple case, we've handled all of that in the .html
// a) we've bound to the local variable with ngModel
// b) we emit to the ouside by calling onChange on ngModelChange
}
// custom-input.component.html
<input type="text"
[(ngModel)]="input"
(ngModelChange)="onChange($event)"
(blur)="onTouch()">
// parent.component.html
<app-custom-input [formControl]="inputTwo"></app-custom-input>
// OR
<form [formGroup]="form" >
<app-custom-input formControlName="myFormControl"></app-custom-input>
</form>
More Examples
Example with Input: https://stackblitz.com/edit/angular-control-value-accessor-simple-example-tsmean
Example with Lazy Loaded Input: https://stackblitz.com/edit/angular-control-value-accessor-lazy-input-example-tsmean
Example with Button: https://stackblitz.com/edit/angular-control-value-accessor-button-example-tsmean
Nested Forms
Note that Control Value Accessors are NOT the right tool for nested form groups. For nested form groups you can simply use an #Input() subform instead. Control Value Accessors are meant to wrap controls, not groups! See this example how to use an input for a nested form: https://stackblitz.com/edit/angular-nested-forms-input-2
Sources
https://angular.io/api/forms/ControlValueAccessor
https://www.tsmean.com/articles/angular/angular-control-value-accessor-example/
For me it was due to "multiple" attribute on select input control as Angular has different ValueAccessor for this type of control.
const countryControl = new FormControl();
And inside template use like this
<select multiple name="countries" [formControl]="countryControl">
<option *ngFor="let country of countries" [ngValue]="country">
{{ country.name }}
</option>
</select>
More details ref Official Docs
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);
}
}
});
});
}
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