Angular4 Template - javascript

I have written a simple code to demonstrate ngTemplate
<div> <ng-container [ngTemplateOutlet] ="template1"> </ng-container></div>
and here are the template
<ng-template #template1> This is 1st template </ng-template>
<ng-template #template2>This is 2nd template </ng-template>
It is working as expected, but my problem is I am not able to pass dynamic value to [ngTemplateOutlet]
I want to achieve something like this
<div> <ng-container [ngTemplateOutlet] ="selectTemplate"></ng-container> </div>
Which will either select template1 or template2.
Whenever I am passing some dynamic value which I have defined in my typescript file it gives is error
ERROR TypeError: templateRef.createEmbeddedView is not a function
My typescript file contains this code
if(some condition) {
this.selectTemplate = "template1";
} else {
this.selectTemplate = "template2";
}

In your first example, you're binding ngTemplateOutlet to the value of the expression template1, which is the first ngTemplate element. So far so good.
In your second example, you're binding ngTemplateOutlet to the value of the expression selectTemplate, which is not a template element - you've set it to be a string!
To fix this, you first need to add the template references to your component as fields using the ViewChild annotation:
#ViewChild('template1') template1;
#ViewChild('template2') template2;
Then set selectTemplate to refer to the element itself rather than its name:
if (some condition) {
this.selectTemplate = this.template1;
}
else {
this.selectTemplate = this.template2;
}
Note that the ViewChild variables will not be set until the component's view has been initialized - you can use the ngViewAfterInithook to wait for that to happen.

update Angular 5
ngOutletContext was renamed to ngTemplateOutletContext
See also https://github.com/angular/angular/blob/master/CHANGELOG.md#500-beta5-2017-08-29
original
You can pass a context
<ng-container [ngTemplateOutlet]="template1; context: {foo: 'foo', bar: 'bar', $implicit: 'implit' }"
In the template you can use it like
<ng-template #template1 let-foo1="foo" let-foo2="bar" let-item>
This is 1st template
<div>foo1: {{foo1}}</div>
<div>foo2: {{foo2}}</div>
<div>foo1: {{item}}</div>
</ng-template>
I hope the names I used are not too confusing. I tried to make the distinction between
the name as the value is passed to the context
the name as it is used inside the template
$implicit which doesn't require ="somename" in the template variable definintion (let-item)

Related

Is PrimeNG's p-pickList changing source and target?

I have an Angular app with PrimeNG. I am using the PickList component like this:
<p-pickList
[source]="source"
[target]="target"
>
<ng-template
let-item
pTemplate="item">
{{item | json}}
</ng-template>
</p-pickList>
<h2>source</h2>
<ul>
<li *ngFor="let s of source">{{s|json}}</li>
</ul>
<h2>target</h2>
<ul>
<li *ngFor="let t of target">{{t|json}}</li>
</ul>
The component itself is straightforward:
#Component({
selector: 'app-hello',
templateUrl: './hello.component.html',
styleUrls: ['./hello.component.css'],
})
export class HelloComponent {
source: string[];
target: string[] = [];
constructor() {
this.source = [
"foo",
"bar",
"baz",
];
}
}
I do not use two-way-binding here, so how does PrimeNG updates source and target property?
On my main project source and target are #Input() properties and thus I don't want some sub component to fiddle with it.
How is it possible that PrimeNG changes those values?
You could replicate the values of source and target in your HelloComponent, then use these copies for the underlying PrimeNG PickList widget. This allows you to pass updates to the HelloComponent that will be communicated down to the PrimeNG widget, but changes to those arrays within the PrimeNG widget won't impact the original input array.
I believe in your original code, the original array that's being passed as an input to HelloComponent, then passed into the PrimeNG widget, is being passed by a "copy of a reference."
private _sourceOptions: string[];
#Input set sourceOptions(options: string[]) {
// Ensure we're not passing a reference to the array down because it may still
// be changed. Create a new array with the same items. This can also help with
// firing change detection in the PrimeNG widget.
this._sourceOptions = options.slice(0);
}
get sourceOptions(): string[] {
return this._sourceOptions;
}
<!-- Component template file -->
<p-pickList [source]="sourceOptions" [target]="target">
<ng-template let-item pTemplate="item">
{{item | json}}
</ng-template>
</p-pickList>
Odds are that within the component for primeNG there is an OnChange listener, and just generally speaking when something changes within one #Input it does trigger an onChange event.
As the Angular doc says (https://angular.io/api/core/OnChanges) anytime a data bound property changes it fires onChange. In this case here, target is a databound property.
Also, what do you mean by changing values? If you select foo it turns into foobar? Under the hood primeNG is not manipulating the data you passed it, it probably has its own internal store of how it displays the data for its picker list component.

Material.Angular.io mat-autocomplete [displayWith] function update scope variables

I'm running into an issue where I can access locally declared variables in the component controller instantiating the mat-autocomplete. The problem I'm facing is the local variables are stuck in this scope and I can't update them.
Any ideas or thoughts on updating the mat-autocomplete scope variables.
Ultimately what I'm doing is concatenating the display string and a variable bound to the input model. This is giving me an autocomplete input that adds helper text for the user, ideally the text is up to date with clearing the input. The text is currently continuously concatenating, creating unusable text pretty quickly
html
<input
[(ngModel)]="filter>
mat-autocomplete
#auto="matAutocomplete"
[displayWith]="displayFn">
<mat-option
*ngFor="let option of filteredOptions | async"
[value]="option">
{{ option }}
</mat-option>
</mat-autocomplete>
component.ts
displayFn(search): string | undefined {
if(!search) return; //check if the search isn't already populated
if(!search.match(/(=|\*)/)){
if(this.filter){
this.filter += ' ' + search + '==*term*';
}else{
this.filter = search +'==*term*';
}
return this.filter; //this isn't persisting across the lifecycle
}
}
You have two options, the first one is just calling [displayWith]="displayFn.bind(this)" which looks weird in the Angular world, but I can confirm that it works (although I got an Error on my WebStorm saying ng: Unknow Method bind)
And the second one is to use an arrow function in order to preserve the context.
Something like this:
displayFn(offer?: Offer): string | undefined {
return offer && offer.name == this.currentOffer.name ? offer.name : undefined;
}
displayFnWrapper() {
return (offer) => this.displayFn(offer);
}
And in the template:
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFnWrapper()" (optionSelected)='assign($event.option.value)'>
<mat-option *ngFor="let offer of filteredOffers$ | async" [value]="offer">{{ offer.name }}</mat-option>
</mat-autocomplete>
If I use an example, MyClass, where
#Input() modeCity = false;
in ngOnInit() I can access the modeCity and change it. It is reflected over other methods in the class.
in HTML,
<mat-autocomplete #auto="matAutocomplete" autoActiveFirstOption
[displayWith]="itemDisplayFn" (optionSelected)="selected($event)">
then for method itemDisplayFn(item: ..) in the ts file, the modeCity is undefined.
I found that somehow the method itemDisplayFn() has static context. Therefore I created the property,
static staticModeCity = false;
staticModeCity can be set in the ngOnInit() like so,
MyClass.staticModeCity = true
and used in the Method itemDisplayFn() like so,
if(MyClass.staticModeCity) ....
I do not know why this is. Of course static can be conflicting, if the same component is used multiple times in the same parent component.

Ember template convention

I'm trying to understand EmberJS template convention in Discourse.
Here's a snippet from app/assets/javascripts/discourse/templates/discovery/categories.hbs
{{#discovery-categories refresh="refresh"}}
{{component controller.categoryPageStyle
categories=model.categories
latestTopicOnly=controller.latestTopicOnly
topics=model.topics}}
{{!-- my-template --}}
{{/discovery-categories}}
What is the meaning of discovery-categories and component?
For example I want to insert my-template to extend categories.hbs, what is the convention I should use to create file with my template?
discovery-categories is the name of the component which is
called statically using the name of the component.
Whereas in the second line 'component' is a template helper which loads the component dynamically using the name specified via property controller.categoryPageStyle.
3.my-template is the yield block , where you can have context of the component discovery-categories if its yield.
for eg. if discovery-categories has a property foo you can write something like
{{#discovery-categories refresh="refresh" foo="Some Text"}}
{{component controller.categoryPageStyle
categories=model.categories
latestTopicOnly=controller.latestTopicOnly
topics=model.topics}}
{{foo}}
{{/discovery-categories}}

How to use templateRef?

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 is the difference between Node.bind() and Template Binding

I am reading the docs of Google Polymer,there are two types of data binding,Node.bind() and Template Binding,so, what is the difference between Node.bind() and Template Binding
They're both ways of achieving data-binding. One in the context of DOM nodes and the other in the context of templates.
Node.bind()
Node.bind() allows you to tell DOM nodes to bind a named property to some data that you provide. So below, we're binding the property value (object.path.to.value) to the .textContent in our textNode.
var obj = {
path: {
to: {
value: 'hi'
}
}
};
var textNode = document.createTextNode('mytext');
textNode.bind('textContent', obj, 'path.to.value');
This is neat because anytime value changes, Node.bind() keeps your .textContent updated.
TemplateBinding
The Polymer docs state that this extends what you can do with the HTML <template> tag - specifically, giving you access to four new attributes: bind, repeat, if and ref.
So let's say that we wanted to pass a propery foo to a <template> which we would like to use in our template content, but want to keep in sync so that anytime foo changes, the template also gets updated. With bind that's as straight-forward as:
<template bind="{{ foo }}">
Creates a single instance with {{ foo }} when singleton model data is provided.
</template>
repeat is really useful for when you're working with lists or collections of data - like users:
<template repeat="{{ user in users }}">
<li>{{user.name}}</li>
</template>
if is really handy for conditional values in templates:
<template if="{{ conditionalValue }}">
Binds if and only if conditionalValue is truthy. (same as *bind if*)
</template>
and so on. You can find more examples of what can be done with these attributes in the TemplateBinding docs.

Categories

Resources