I have this html template file, range-details-dialog.tpl.html
<div class="modal-header clearfix text-left">
<h5>Update Range</h5>
</div>
<div class="modal-body">
<form name="form" role="form" class="ng-pristine ng-valid" novalidate ng-submit="updateRange()">
<div class="form-group-attached">
<div class="row">
<div class="col-sm-12">
<div class="form-group form-group-default input-group p-l-10 p-r-10" ng-class="{ 'has-error' : form.$invalid }">
<p ng-show="form.rangeDaily.$error.min" class="help-block">Daily range more than £5</p>
</div>
</div>
</div>
</div>
</form>
<div class="row">
<div class="col-sm-8"></div>
<div class="col-sm-4 m-t-10 sm-m-t-10">
<button type="button" class="btn btn-primary btn-block m-t-5"
ng-disabled="form.$invalid || promise" promise-btn="promise" ng-click="updateRange()">Update</button>
</div>
</div>
</div>
Then I want to have another file forced-range-details-dialog.tpl.html
These two files could be one file instead with dynamically populated placeholders.
These are the places were substitution would be needed:
<h5>Update Range</h5> would become <h5>Update Forced Range</h5>
<p ng-show="form.rangeDaily.$error.min" class="help-block">Daily range more than £5</p>
would become:
<p ng-show="form.forcedRangeDaily.$error.min" class="help-block">Forced Daily range more than £5</p>
ng-disabled="form.$invalid || promise" promise-btn="promise" ng-click="updateRange()">Update</button>
, ng-disabled="form.$invalid || promise" promise-btn="promise" ng-click="updateForcedRange()">Update</button>
Is there a way to avoid having two separate template files for the above? Could you please provide some examples, links, or pointers as to how that can be achieved?
Also, I see in the answers that a solution would be to add a boolean parameter inside the component and then call it twice. I am not sure how to call the component though. I have pasted my component below:
angular.module('app.investment.rangeDetails')
.component('pxForcedLimitAmount', {
templateUrl: '/assets/js/apps/range/range-details-dialog.tpl.html',
bindings: {
amount: '<',
isRequest: '<?',
requestedAt: '<?',
#Input() isForced: boolean //<<----I added this based on answers below
},
controller: [function () {
var ctrl = this;
ctrl.$onInit = function () {
ctrl.isRequest = ctrl.isRequest === true || false;
};
}],
});
Seems like only the placeholders need to change, so you can use a variable to decide what placeholder to display on the template. For example:
isForced: boolean;
ngOnInit() {
this.isForced = true; // decide how you want to toggle this
}
on the template:
<h5 *ngIf="!isForced">Update Range</h5>
<h5 *ngIf="isForced">Update Forced Range</h5>
and
<p *ngIf="!isForced" ng-show="form.rangeDaily.$error.min" class="help-block">
Daily range more than £5</p>
<p *ngIf="isForced" ng-show="form.forcedRangeDaily.$error.min" class="help-block">
Forced Daily range more than £5</p>
you can do the same for other tags as well.
From the comments, one way to "determine" the value for isForced is to introduce an input property to the component i.e.
#Input() isForced: boolean;
and invoke the component from elsewhere like:
<app-user [isForced]="true"></app-user>
You can use inputs.Write a component which takes input, and render it in html. then call this component in desired places with its selector
For events use output
See the doc https://angular.io/guide/inputs-outputs
Related
I have parent component with all teams and child component for displaying each of team in box using *ngFor and #Input. All child components are displayed fine but when I want to change each of them opening modal I always get the data from first object in array of teams.
team.component.html
<app-team-box *ngFor="let team of teams" [team]="team"></app-team-box>
team-box.component.html
<div class="ibox">
<div class="ibox-title">
<h5>{{this.team.name}}</h5>
<a class="btn btn-xs" (click)="openEditTeamModal(this.team)">Edit</a>
</div>
</div>
<div class="modal fade" id="edit_team_modal">
<div class="modal-dialog">
<div class="modal-content">
<form role="form" (ngSubmit)="editTeam()" novalidate>
<div class="modal-body">
<div class="row" *ngIf="this.team">
<label>Team name:</label>
<input type="text" id="name" name="name" [(ngModel)]="this.team.name" #name="ngModel">
</div>
</div>
<div class="modal-footer">
<button type="submit">Save</button>
</div>
</form>
</div>
</div>
team-box.component.ts
export class TeamBoxComponent implements OnInit {
#Input() team;
constructor(private teamService: TeamService) {}
openEditTeamModal(selectedTeam): void {
this.team = selectedTeam;
$("#edit_team_modal").modal();
$(".modal").appendTo("html");
}
editTeam(): void {
this.teamService.update(this.team).subscribe((team: Response) => {
$("#edit_team_modal").modal("hide");
});
}
}
The problem is when I change first one everything is fine, modal is populated with name and after changing it's get saved. But when I click on edit button for example second team, in modal I get the data for first team. Why this.team is always referencing to first object in array of teams?
I'm developing an app using Meteor Framework.
One of the features I am looking to implement is having a marquee text (like a scrolling bottom text).
I have added the package meteor-jquery-marquee and it works great with a single string. But whenever I try to modify the string, nothing happens, and it stays the same.
It's worth mentioning that I did try sessions, and it changes the text, however, the marquee animation stops, which defeats the purpose.
I have been stuck for hours trying to get it to work, some help would really save my butt here.
I've initialized the global variable in the client/main.js as
globalMessage = "Welcome to my proJECT";
And it scrolls with the marquee just fine.
Thank you in advance!
My code:
My body template
<template name="App_Body">
{{> Header}}
{{>Template.dynamic template=main}}
{{> Footer}}
<div style="color: white;" class="ui center aligned container">
<div class='marquee'>{{globalMessage}}</div>
</div>
</template>
body.js
Template.App_Body.helpers({
globalMessage () {
return globalMessage;
},
});
where I'm trying to edit the marquee:
<template name="dailyMessageControl">
<div class="container">
<br>
<br>
<div class="info pull-right"> <!-- column div -->
<div class="panel panel-default">
<div class="panel-heading clearfix">
<h1 class="panel-title text-center panel-relative"> Modify Daily Message</h1>
</div>
<div class="list-group">
<div class="list-group-item">
<p style="font-size: 30px;">Current Message: <br>{{globalMessage}}</p>
</div>
<div class="panel-footer">
<form>
<div class="form-group">
<label for="exampleInputEmail1">Enter new messages</label>
<input type="text" name="newMsg" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="New Message">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
</div><!-- end column div -->
</div>
</template>
the .js
Template.dailyMessageControl.helpers({
globalMessage () {
return globalMessage;
},
});
Template.dailyMessageControl.events({
'submit form': function(){
event.preventDefault();
var newMsg = event.target.newMsg.value;
globalMessage = newMsg;
}
});
Your code clearly lacks reactivity, let's fix that.
Fist, initialize globalMessage as ReactiveVar instance (client/main.js):
globalMessage = new ReactiveVar('Welcome to my proJECT');
Next, code to react to its value change (body.js):
Remove globalMessage() helper
Add code that will track globalMessage variable and re-create $.marquee:
Template.App_Body.onRendered(function appBodyOnRendered() {
this.autorun(() => {
const value = globalMessage.get();
const $marquee = this.$('.marquee');
$marquee.marquee('destroy');
$marquee.html(value);
$marquee.marquee(); // add your marquee init options here
});
});
And, lastly, update code in dailyMessageControl template to work with ReactiveVar instance:
Template.dailyMessageControl.helpers({
globalMessage () {
return globalMessage.get(); // changed line
},
});
Template.dailyMessageControl.events({
'submit form': function(){
event.preventDefault();
var newMsg = event.target.newMsg.value;
globalMessage.set(newMsg); // changed line
}
});
I have use the following tutorial to create reactive forms in Angular 2 and it works well.
https://scotch.io/tutorials/how-to-build-nested-model-driven-forms-in-angular-2
However, I am now trying to add an array within an array. Using the tutorial above, I have created an 'Organisation' form, which can contain an array of 'Contact' groups. But I am unable to successfully adapt the setup to allow each 'Contact' group to contain an array of 'Email' groups.
I have been unable to find a tutorial or example that covers this and would be grateful for any pointers.
Using the tutorial above, I have created an 'Organisation' form, which
can contain an array of 'Contact' groups. But I am unable to
successfully adapt the setup to allow each 'Contact' group to contain
an array of 'Email' groups.
The tutorial above gives you all what you need.
I suppose you want structure like this.
Firstly you need some component (AppComponent in my case) where you declare root FormGroup. I called it trustForm below.
app.component.ts
export class AppComponent {
trustForm: FormGroup;
constructor(private fb: FormBuilder) { }
ngOnInit() {
this.trustForm = this.fb.group({
name: '',
contracts: this.fb.array([])
});
this.addContract();
}
initContract() {
return this.fb.group({
name: '',
emails: this.fb.array([])
});
}
addContract() {
const contractArray = <FormArray>this.trustForm.controls['contracts'];
const newContract = this.initContract();
contractArray.push(newContract);
}
removeContract(idx: number) {
const contractsArray = <FormArray>this.trustForm.controls['contracts'];
contractsArray.removeAt(idx);
}
}
In this component you have also some methods that help you to manipulate the first level FormArray - contracts
app.component.html
<div class="container">
<form [formGroup]="trustForm">
<h3>Add trust</h3>
<div class="form-group">
<label>Name</label>
<input type="text" class="form-control" formControlName="name">
</div>
<!--contracts-->
<div formArrayName="contracts">
<div *ngFor="let contract of trustForm.controls.contracts.controls; let i=index" class="panel panel-default">
<div class="panel-heading">
<span>Contract {{i + 1}}</span>
<span class="glyphicon glyphicon-remove pull-right" *ngIf="trustForm.controls.contracts.controls.length > 1" (click)="removeContract(i)"></span>
</div>
<div class="panel-body" [formGroupName]="i">
<contract [group]="trustForm.controls.contracts.controls[i]"></contract>
</div>
</div>
</div>
<div class="margin-20">
<button (click)="addContract()" class="btn btn-primary">
Add another contract +
</button>
</div>
</form>
<h5>Details</h5>
<pre>{{ trustForm.value | json }}</pre>
</div>
There is no different from root html from the tutorial except different FormArray name.
Then you need to build contract component that will be similar to AppComponent
contract.component.ts
export class ContractComponent {
#Input('group') contractGroup: FormGroup;
constructor(private fb: FormBuilder) { }
addEmail() {
const emailArray = <FormArray>this.contractGroup.controls['emails'];
const newEmail = this.initEmail();
emailArray.push(newEmail);
}
removeEmail(idx: number) {
const emailArray = <FormArray>this.contractGroup.controls['emails'];
emailArray.removeAt(idx);
}
initEmail() {
return this.fb.group({
text: ''
});
}
}
contract.component.html
<div [formGroup]="contractGroup">
<div class="form-group">
<label>Name</label>
<input type="text" class="form-control" formControlName="name">
</div>
<!--emails-->
<div formArrayName="emails">
<div *ngFor="let email of contractGroup.controls.emails.controls; let i=index" class="panel panel-default">
<div class="panel-heading">
<span>Email {{i + 1}}</span>
<span class="glyphicon glyphicon-remove pull-right" *ngIf="contractGroup.controls.emails.controls.length > 1" (click)="removeEmail(i)"></span>
</div>
<div class="panel-body" [formGroupName]="i">
<email [group]="contractGroup.controls.emails.controls[i]"></email>
</div>
</div>
</div>
<div class="margin-20">
<button (click)="addEmail()" class="btn btn-primary">
Add another email +
</button>
</div>
</div>
As you can see we just replace contracts to emails FormArray and we are also passing FormGroup to email component
And finally you will only need to fill EmailComponent with desired fields.
email.component.ts
export class EmailComponent {
#Input('group') emailGroup: FormGroup;
}
email.component.html
<div [formGroup]="emailGroup">
<div class="form-group">
<label>Text</label>
<input type="text" class="form-control" formControlName="text">
</div>
</div>
Completed version you can find at Plunker Example
If you think that this solution doesn't seems right because the parent component holds the description of the child component like initContract and initEmails you can take a look at more complex
Plunker Example
where each component is responsible for its functionality.
If you're looking for solution for template driven forms read this article:
Angular: Nested template driven form
I'm trying to create a means to toggle dynamically created rows of information. I've tried using ng-init, and then passing it to a function, but I'm screwing up somewhere and I can't seem to wrap my head around how or if this is possible. The gap, I believe, is in getting the concatenated scope variable to be referenced elsewhere. I'm using Bootstrap 3 and AngularJS 1.5.
The HTML:
<div class="row" data-ng-repeat="equipment in task.equipment">
<div class="col-md-12">
<h4 class="green-text">
{{ equipment.equipId }}
<small class="green-text">
<i class="glyphicon"
data-ng-class="{'glyphicon-triangle-bottom': field{{ $index }}, 'glyphicon-triangle-right': !field{{ $index }}}"
data-ng-init="equipment['field' + $index] = true"
data-ng-click="toggleTaskEquip('field{{ $index }}')">
field{{ $index }}: I WANT THIS TO WORK</i>
</small>
</h4>
</div>
<div data-ng-show="field{{ $index }}">
...stuff here...
</div>
</div>
The JS:
$scope.toggleTaskEquip = function(toggleBool)
{
if (toggleBool === true)
$scope.isTaskEquipOpen = false;
else if (toggleBool === false)
$scope.isTaskEquipOpen = true;
};
If I understand the problem correctly, you want to be able to toggle the boolean created in the ng-init with a click.
I think you need this:
<div class="container-fluid">
<div ng-controller="MyCtrl">
<div class="row" data-ng-repeat="equipment in task.equipment">
<div class="col-md-12">
<h4 class="green-text">
{{equipment.equipId}}
<small class="green-text">
<i class="glyphicon"
data-ng-class="{'glyphicon-triangle-bottom': isVisible, 'glyphicon-triangle-right': !isVisible}"
data-ng-init="isVisible = true"
data-ng-click="isVisible = !isVisible">I WANT THIS TO WORK</i>
</small>
</h4>
</div>
<div data-ng-show="isVisible">
...stuff here...
</div>
</div>
</div>
</div>
You don't even need the function toggleTaskEquip on the $scope.
JSFiddle here.
ng-repeat creates a new scope for each template instance, so you can just create a separate isVisible for each equipment with isVisible = true in the ng-init.
I have two divs - the first contains the second. The contained div has its own controller. When I click an icon button in the container, I change a variable which then affects the visibility of the contained div.
It looks like this:
<div ng-controller="BarController">
<div class="navbar navbar-default navbar-fixed-top">
<div class="container-fluid">
<div class="col-lg-2 page-title">My Page</div>
<div class="col-lg-10">
<span class="actions">
<i class="fa fa-lg fa-download fa-inverse" tooltip="Download"
ng-click="showSecondaryBar=!showSecondaryBar"></i>
</span>
</div>
</div>
</div>
<div class="download navbar download-in download-out"
ng-class="{'myhidden': !showSecondaryBar}"
ng-cloak>
<div class="col-lg-offset-4 col-lg-4 form-inline form-group" ng-controller="TagsController">
<div class="download-label col-lg-6">
<label>Download by tags:</label>
</div>
<div class="download-tags col-lg-6">
<tags-input class="bootstrap" spellcheck="false" min-length="1" ng-model="tags" add-from-autocomplete-only="true">
<auto-complete source="loadTags($query)" min-length="1" load-on-down-arrow="true"
load-on-focus="true" max-results-to-show="5"
highlight-matched-text="false"></auto-complete>
</tags-input>
</div>
</div>
</div>
</div>
The <tags-input> is taken from ng-tags-input and I would like to reset the tags that were already typed to it whenever the icon button is clicked (which changes the visilibyt of the div that contains the ng-tags-input).
Problem is, because I have the TagsController which contains the data (tags) and this data is not visible in the BarController, I'm not sure how I can reset the tags array to become empty.
I thought of using a service but it fills like too much of a coupling. I would prefer to have a function in TagsController which is called upon click. But I can't figure out how to do it from another controller
You are right you have to use a service.
Why don't you use a broadcast as your TagsController is included in BarController?
You can include a scope.broadcast("Event") in BarController
Then a "on" listener on TagsController who will reset the tags array when "Event" Occur.
I would personnaly to this.
https://docs.angularjs.org/api/ng/type/$rootScope.Scope
You can use $broadcast on $rootScope to send an event to TagsController. So TagsController can receive this event by registering an event listener for it. See following example.
Refer to $rootScope API docs
angular.module('app',[])
.controller('ParentController', function($rootScope) {
var parentCtrl = this;
parentCtrl.someFlag = true;
parentCtrl.changeFlag = function() {
parentCtrl.someFlag = !parentCtrl.somFlag;
$rootScope.$broadcast('resettags', {'defaultTags': 'whatever_tag'});
}
})
.controller('ChildController', function($rootScope){
var childCtrl = this;
childCtrl.tags = "Some tags entered by user";
$rootScope.$on('resettags', function(event, args) {
childCtrl.tags = args.defaultTags;
});
});
.myHidden {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div id="main" ng-controller="ParentController as parentCtrl">
<button type="button" ng-click="parentCtrl.changeFlag()">Toggle</button>
<div ng-class="{'myHidden' : !parentCtrl.someFlag}">
<div ng-controller="ChildController as childCtrl">
<h1>{{childCtrl.tags}}</h1>
</div>
</div>
</div>
</div>