I am writing a tree control using Angular2 and ng2-dragula (based on dragula). I am currently using something very similar to the example nested repeat example here. I have no problem loading in my list and getting drag and drop to work as expected. What I need to do is click a button, go back to the http service get fresh data, then update data on page. I know that I can use javascript to find the id and update that way but it doesn't seem like the correcy way to handle it.
Here is my component code - All this does is just over write the view data and completey undo any drags changed etc (im sure that is by design). I have tried a bunch of different different ways to use dragulaModel, but no matter what the whole component is re-rendered no matter what happens. I need to be able just to update the child text
I'm new to angular and want to make sure i follow the correct patterns
item.component.ts
loadItems(){
this._itemsService.getIems();
}
reloadIems(){
this._itemsService.getItems();
}
item.component.html
<div *ngIf="items">
<div class="holder">
<div *ngFor="let item of items | async">
<div (click)="checkCollapsed(item.text)" >
<div *ngIf='item.children' [dragula]='"first-bag"'>
<div *ngFor='let child of item.children' class="item">
<span class="handle">+</span><span id='{{child.id}}' [innerHtml]="child.text"> </span>
</div>
</div>
</div>
</div>
</div>
</div>
Edit
If I add a .subscribe to loadItems() then update this.items manually like this.items[0].children[0].text = "1000" in reloadItems() it works as i would like. Should I be manually updating the whole object this way? Seems like a hack
**Edit 2 **
I managed to get it to work by subscribing in the loadItems() then comparing the 2 objects in reloadItems, then making any necessary changes there. I think due to the subscribe it is autoupdating. Can anyone confirm if im doing it wrong/right (working plnkr here, leaving out the angular dependencies)
Related
A portion of my app consists of dynamically generated components that are created inside a *ngFor loop like so:
<div *ngFor="let actionCategory of actionCategories | keyvalue">
<h2>{{actionCategory.key}}</h2>
<div *ngFor="let action of actionCategory.value | keyvalue">
<app-gearset [action]="action"></app-gearset>
</div>
<button mat-button (click)="addComponent(actionCategory)">Add another set</button>
</div>
The user also has the option of adding more of these components by clicking the button tied to the addComponent() function, which looks as follows:
addGearSetComponent(actionCategory) {
this.actionCategories[actionCategory.key][Object.keys(this.actionCategories[actionCategory.key]).length] = {};
}
This adds a new blank object to the end of the actionCategory object, adding a new component to the appropriate part of the app. The problem is that the state of the components that were already present is reset each time a new one is added.
Is there any way to keep the state of the previous components in the object intact when the object they are tied to is changed? Is there a better way to be dynamically generating components based on a complex object?
After a few more hours of hunting through docs I came across the trackBy functionality. [trackBy][1]
It looks like by handing trackBy the following function I'm able to alter the way the DOM re-renders components:
trackAction(index, action) {
return action ? action.key : undefined;
}
I'll admit I don't fully understand exactly how this works behind the scenes, but it's preventing components that were present on the page from being destroyed and re-rendered when a new one is added. I'll have to keep an eye out for performance issues, but I believe this will work for now! :)
[1]: https://blog.angular-university.io/angular-2-ngfor/
I'm now convinced I am doing this wrong based on the fact that my app doesn't show the menu unless I open dev tools..
On my landing page, I have a menu bar that has the app name/logo as well as menu items and a log in button in my webapp, like this:
I've added that by having my main.html file contain:
<div id="render-target">
<div id="header-container"></div>
<div id="app-container"></div>
<div id="footer-container"></div>
</div>
and my main.jsx file's render() function look like this:
render(
<div className='container'>
<div id="header-container">
<MenuBar />
</div>
<div id="app-container">
<TableSelect />
</div>
<div id="footer-container">
// some footer code
</div>
</div>,
document.getElementById('render-target'));
Now, the user can select a "table" and load the data from that, which takes them to a different view.
So, basically from the TableSelect view, the user clicks a checkbox and loads a TableLoad view, which loads a table for display (in a super not OK way probably, since it calls ReactDOM.render(<TableDisplay tableId={this.props.id}/>,document.getElementById('app-container'));)
Then, in the TableDisplay view I have
componentDidUpdate() {
ReactDOM.render(<MenuBar currentView="tableDisplay" rows={this.props.rows} cols={this.props.cols} tableId={this.props.tableId} />, document.getElementById('header-container'));
}
which calls the MenuBar view with the current data properties (or rather, replaces the DOM). What I want to see is below, including the Edit button:
This seems to only get triggered unreliably... (though without fail if I have Chrome Dev tools open). I think it's just taking some time for the state to pass and I only show the menu if (this.state.currentView !== "loadTable" && this.props.cols !== undefined && this.props.rows !== undefined).
I guess I know that ReactDOM.render is not what I want here, but I want to show the menu even if I don't have column/row data and I need to pass the column/row data once I get it. It feels like a Menu shouldn't need data information, but in order to add data to the table I have to have that data somehow.
It's really clear to me that I'm doing a lot of things the non-react way. My app is currently deployed at ideal-engine.herokuapp.com and the code is at github repo, if anyone who knows React and feels altruistic enough to do a code review let me know.
You might consider using a routing solution to help you out, such as react-router, where you can say what component to show at any given route. You can even receive route params (like table ids and such) that will tell you what data to fetch for display.
Let's suppose a list of 1000 items displayed with infinite scrolling.
Each item displays: a person's firstName, lastName, and mood. (to make it simple)
Initially, I didn't want to listen for updates.
So the great angular-bindonce directive or even better: angular 1.3 one-binding feature made the trick.
Now, I created a pull-to-refresh component, allowing to refresh the whole items.
However, as binding once, (and not reloading the page) my whole list didn't take the updates in account.
Using angular-bindonce, I have this currently:
<div bindonce ng-repeat="person in persons track by person.id">
<span bo-text="person.firstName"></span>
<span bo-text="person.lastName"></span>
<span bo-text="person.currentMood"></span>
</div>
The pull-to-refresh triggers this function:
$scope.refresh() {
Persons.getList(function(response)) {
$scope.persons = response.data; //data being an array
}
}
Question is:
Is there a way to refresh all the data ONLY when the pull-to-refresh is triggered?
In this case, I would be able to keep this one-binding that would greatly improve performance when dealing with huge lists of persons.
Until now, I'm forced to....use two-way binding, the natural way of Angular works.
More generally, how to deal with huge lists with infinite scrolling that needs to be updated only when some events are triggered?
Get angular-bind-notifier.
Use native bindings (with a somewhat modified syntax) and setup your markup like so:
<div ng-repeat="person in persons track by person.id" bind-notifier="{ eventKey:watchedExpression }">
<span>{{:eventKey:person.firstName}}</span>
<span>{{:eventKey:person.lastName}}</span>
<!-- or with ng-bind if you prefer that -->
<span ng-bind=":eventKey:person.currentMood"></span>
</div>
Now, whenever the value of watchedExpression changes - a $broadcast will be sent down through the childscope created by bind-notifier and tell every binding with the :key:expr syntax to re-evaluate.
If you need to, you can also send the $broadcast manually in the following format:
$scope.$broadcast('$$rebind::' + key) // where 'key' === 'eventKey' in the example above.
refresh-on directive could do the trick, found a reference HERE:
<div bindonce="persons" refresh-on="'refresh'" ng-repeat="person in persons track by person.id">
<span bo-text="person.firstName"></span>
<span bo-text="person.lastName"></span>
<span bo-text="person.currentMood"></span>
</div>
Instead of trying to work around not using two-way binding but still have all of its benefits there is more likely and easier solution. You say that there are 1,000 rows, are all 1,000 rows with the viewport / visible to the user at once?
I would assume not, so I would suggest using a buffered view for the list of items. Buffering the rows would mean that the rows that are not visible have no bindings but still take up space in the DOM so the scroll bar is always accurate.
The one major caveat of buffering is that all rows should be the same height, no variable height rows.
Here are some virtual scrolling / buffering directives to take a look at:
https://github.com/EnzeyNet/VirtualScroll
https://github.com/stackfull/angular-virtual-scroll
https://github.com/kamilkp/angular-vs-repeat
I am using Bootstrap UI in my angular application. I have a tooltip in the html page which works fine. I noticed that after the tooltip is displayed and I move my mouse out, the Ui-bootstrap-tpls.js fires a method called "hideTooltipBind" which in turn calls $apply and it triggers the filters in that scope to reload.
Lets say I have 10 filters in the scope which is filtering an array of 100 each. Everytime a tooltip is displayed, all my filters are forced to reload again. How can I avoid this?
I am using
//ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js
jquery-2.0.3.js
ui-bootstrap-tpls-0.11.0.js
I have attached the screenshot of the Call Stack
You can utilise some form of one-time binding. There are multiple options out there:
bind-once by pasvaz
angular-once by tadeuszwojcik
watch-fighters by abourget
fast-bind by me (fork off of Karl Seamons work)
There are some differences to the four (unrelated to your question at hand however):
bind-once is the most popular one seeing the most active development. Requires two directives to do the job (bindonce and bo-*).
angular-once is the minimalist of the four (don't quote me on that).
watch-fighters doesn't handle promise based data.
fast-bind has a notifier system for semi-static bindings, using the event bus in Angular.
Assuming you'd start leveraging either one of them, your bindings could look something like this:
<div bindonce="someData">
<span bo-bind="someData.text | yourFilter"></span>
</div>
<span once-text="someData.text | yourFilter"></span>
<span set-text="someData.text | yourFilter"></span>
<span bind-once="someData.text | yourFilter"></span>
This way, your filters would not reevaluate on Angular calls to $digest. If you are filtering a collection in your view (<li ng-repeat="coll | filter"></div>), I'd suggest you move those filters to the controller to reduce the amount of calls to the filters themselves.
I am currently learning AngularJS and already started a little project, which is basically an older project of mine, done in jQuery.
Everythings fine so far, but the last hours I wrapped my head around this area. In jQuery back then 5 minutes, but I have no idea what's the best way in AngularJS)
Here is my jQuery: A calendar view. It opens and closes days on click. Once a day is clicked it gets the class opened. Also there is the state locked, where I disable to open the day at all.
$('.mod-item').on('click', function(){
if ($(this).find('.mod-item-day').not('.locked')) {
if($(this).find('.mod-item-day').hasClass('open')){
$(this).find('.mod-item-day').removeClass('open').addClass('opened');
}else{
$(this).find('.mod-item-day').addClass('open');
}
}
});
Here is the markup:
<ul class="mod">
<li class="mod-item">
<div class="mod-item-day opened"><span>1</span></div>
<div class="mod-item-content">
<img src="../images/present1_late.jpeg" alt="">
</div>
</li>
So my question is - what's the most AngularJS way to do it?
You do this basically the way shown in the To Do example on the Angular website.
On the day's div, specify ng-click="callbackInYourScopeCode()". In the To Do example, it's ng-click="archive()"
On the day's div's class, include a property from your model. In the To Do example, for instance, there's <span class="done-{{todo.done}}">{{todo.text}}</span>. Note the property todo.done.
In your scope code, have the function (callbackInYourScopeCode) change your model's property (done in the To Do example).
Angular will call your scope code in response to a click, and then update the element based on changes to your model.