reference component variable from ng-content - angularjs

I have the following piece of markup:
<group-header [isOpen]="true">
<div *ngIf="isOpen">{{'PRICE' | resource}}</div>
</group-header>
group-header's template looks like this:
<div (click)="toggleGroups($event);">
<ng-content></ng-content>
</div>
isOpen is defined in my group-header component like this:
#Input()
set isOpen(value: boolean) {
this._isOpen = value;
}
get isOpen() {
return this._isOpen;
}
Apparently I cannot reference isOpen the way I want to (or at all for that matter) as my 'PRICE' resource never displays.
Is there a way to have my ng-content rendered content display conditionally based on a field on my component?
Hope this all makes sense... if not please ask
EDIT: Added setter as per #PierreDuc's comment :-)

You can use a template variable to reference the GroupHeader component:
<group-header [isOpen]="true" #gh>
<div *ngIf="gh.isOpen">{{'PRICE' | resource}}</div>
</group-header>

Related

Hide/show Button in LWC

I'm new to LWC in salesforce and want to hide/show the button based status. I write below code but it is hiding for all statuses. Can you please help me if am wrong.
<template for:each={savedCampaignList} for:item="savedCampaignListvar">
<a name={savedCampaignListvar.Id} >View</a>
|
<a hidden =!IF(savedCampaignListvar.Status === 'Saved')" name={savedCampaignListvar.Id} >Delete</a>
</template>
Expressions are different in LWC than in aura and visualforce
You need to have a getter defined in your controller the template uses for expressions or have a tracked variable you update.
See this for migrating to lwc
You'll end up making a getter like this
get hideLink() {
return this.savedCampaignListvar.Status === 'Saved';
}
and then in your lwc markup you should have this
<template if:false={hideLink}>
<a name={savedCampaignListvar.Id}>Delete</a>
</template>
Note: the hidden attribute is not a boolean attribute. If the attribute exists regardless of setting it to true/false hides the element. See here
Use this code in your HTML template - remember don't put any attribute in your root 'template' element use another div as a holder for for:each directive. You must use a key directive to assign a unique ID to each item. When a list changes, the framework uses the key to rerender only the item that changed. The key in the template is used for performance optimization and isn’t reflected in the DOM at run time.
<template>
<div for:each={savedCampaignList} for:item="savedCampaignListvar" key={savedCampaignListvar.Id}>
<a name={savedCampaignListvar.Id} >View</a>
|
<a if:true={savedCampaignListvar.shouldShow} name={savedCampaignListvar.Id} >Delete</a>
</div>
</template>
We can use connectedCallback function - it's build in LWC function called when the element is inserted into a document. There we can put some condition and add 'shouldShow'(you can call if whatever you want of course;)) attribute to our Objects inside the array. Based on this attribute we will show delete button or not. Your JS should looks like:
import { LightningElement, track } from 'lwc';
export default class App extends LightningElement {
#track savedCampaignList = [
{Id: "1", status: 'Saved'},
{Id: "2", status: 'Not Saved'}
]
connectedCallback() {
this.savedCampaignList.forEach((el)=> {
el.shouldShow = el.status === 'Saved';
})
}
}
You can use if:true or if:hide.
Go through this https://salesforcelightningweb.com/#conditionally-render-html-in-lightning-web-component

How to add a class to a clicked list item

Can someone help me add a class to a list item on a click event? So far I tried it by accessing the refs attribute:
<li className="existing-class" refs={ name } onClick={ this.sort.bind(this, name) }>Foo</li>
#action sort = (name) => {
this.refs.name.className="existing-class another-class"
}
However, this is not working.. can someone help?
Thanks in advance!
Just to patch your existing solution, try properly accessing the dynamic name property: this.refs[name].className="some-class".
But for a more React-y solution, you should store state in the li's parent component as to whether it's been clicked, and then set the className based on the parent's state.
right, the proper way would be to have the element's class controlled by state, rather than by the click event.
e.g. <li className={'existing-class ' + this.state.clicked ? 'some-class' : ''}>Foo</li>
and have your onClick function do setState({clicked: true})

Dynamic instantiation of Components?

Update: To clarify my sad verbiage below...
Given an array of JSON objects, a "Level" component and a "Sprite" component...
I'm stuck on the syntax to loop through the array and init a new "Sprite" for each JSON object and then add that "Sprite" to ... the template (?) of my "Level".
This is my "Level" template. I can hardcode the Sprite in and it works (as the image shows) but I'm scrambling to find the syntax to add an arbitrary number of Sprites.
<div>
<img src={ {imagePath}}/>
</div>
<div>
<app-sprite></app-sprite>
</div>
I'm a refugee Flex/Actionscript developer and am learning Angular 2. I "get" lots of it – but then I get stuck.
My little exercise here: based on some JSON (hard-coded now but later to come from a Service) is to populate a "Level" component with a background image, and then create and position some "Sprites" on the "Level".
Here's the way it looks now:
I've created a plunk of what I am doing. Where I am confused:
How do I dynamically create custom components the "angular" way? I've seen the tutorials with *ngFor to create li elements but the template for my "Sprite" component looks like this:
<div>
<img class="spriteFloater" src={{imagePath}}/>
</div>`
The above works if I hardcode the "imagePath" to a url – but if I try to use "eventData.imagePath" (where event data is a JSON obj) then it fails.
I can hard-code that a reference to that template into my "Level" ("app-sprite" tags) and it works but my attempts to generate "Sprites" on the fly have failed.
Also, I am trying to pass data to each "Sprite" and then use it in my template. I tried doing it through the constructor but that didn't work so a created a custom function to do it – and while it outputs the correct data to the console, the "Sprites" I am creating are not the ones being injected into the DOM.
Geez... sorry this is such a stupid, wordy post.
With Günter's nudge I was able to come up with this. I didn't know the syntax to use *ngFor correctly and then I had to figure out how to apply the passed in data to the style of the newly created sprites.
Guess I am not in Kansas Flex/Actionscript any more. This stuff is crazy... but I like it (I think).
<div>
<app-sprite *ngFor="let event of eventList" [eventData]="event"
></app-sprite>
</div>
import { Component, OnInit, Input } from '#angular/core';
import {Eventdata} from './eventdata'
#Component({
selector: 'app-sprite',
template:`
<div>
<img class = "spriteFloater" [src]="eventData.imagePath"
[style.left] = "eventData.x"
[style.top] = "eventData.y"
[style.background] = "eventData.id"/>
</div>`,
styles [`
.spriteFloater {
position: absolute;
background: black;
left: 50px;
top: 80px;
}
`]
})
export class Sprite implements OnInit {
#Input() eventData: Eventdata;
setEventData(data:EventData){
}
constructor() {
}
ngOnInit() {
}
}
I guess you just need to use
eventData?.imagePath
If you init data in ngOnInit() this is after Angular first tries to resolve bindings in the view. If eventData is null the access to imagePath throws. With ?. Angular accesses imagePath only when eventData is != null

ng-class conditional value not getting reflected

I have a list. I want to show the selected object as active. For that I have written the code as below:
HTML
<li ng-class="{'active' : testObject.selected}" >
<a ng-click="showApp()">
<span>App</span>
</a>
</li>
Controller
$scope.testObject = {
selected: false
};
$scope.showApp = function() {
$scope.testObject['selected'] = true;
//my code
}
Here, testObject.selected is a boolean and I am setting its value (true/false) inside showApp() function. It is not working somehow.
There are no issues with your code... I copied it into here a plnkr here:
http://plnkr.co/edit/pjNBHvCnju4zs7QTZ87C?p=preview
Are you sure the class of active has CSS in it that will be reflected in your view?
Have you verified that the class is actually being added to the element or not via the browser dev tools?
If you work with a form, then make sure you have checked your <form> element.
Each required <input> should have binding with a variable via [(ng-model)] (two-way binding is necessary in this case).
I had the same issue and ng-class worked correctly only after these changes.

Failure to set Ace Editor used via the ui-ace directive into readOnly mode

The ui-ace library I'm using: https://github.com/angular-ui/ui-ace
The markup:
<div class="ace-directive"
ng-model="template.value"
style="width: 100%; font-size:15px;"
ui-ace="{
useWrapMode : true,
showGutter: true,
mode: ace.theme,
onLoad : ace.loaded,
onChange: ace.changed
}">
</div>
The controller code:
$scope.ace.loaded = aceLoaded;
function aceLoaded(editor){
editor.setReadOnly(true);
}
Note: The actual logic for setting the editor's state is conditional on a variable and not literally a boolean 'true'.
Any ideas what could be going wrong?
Making the editor readonly is exposed through a specific html template attribute. From the docs:
<div ui-ace readonly="{{checked}}"></div>
You can change the value of $scope.checked in your controller or then just toggle checked to something truthy elsewhere in your html template.
If you want to change the readonly status like you're trying to in the aceLoaded function, you need to call setOptions:
function aceLoaded(editor){
editor.setOptions({
readOnly: true
});
}
... but this also requires you to keep a reference to the editor around to change the status later on. A scope variable like in the first case is probably easiest, but of course it doesn't offer any other functionality like the latter would, if you actually need it.
Use ng-readonly:
<div ui-ace
ng-readonly="checked">
</div>
where checked is a scope variable.

Resources