I have this little component:
.component('obsbox', {
restrict: 'E',
templateUrl: 'app/components/interactions/html/interactions.obsBox.partial.html',
controller: 'InteractionsController',
controllerAs: 'vm',
bindings: {}
});
All it does it produce a box with some text in it. Nothing spectacular. What I'd like to do is pass a few attributes into the tag and then display those attributes when it resolves.
<obsbox title1="Positive" title2="Experience" score="1"></obsbox>
That should give me a box with "1" in at the top and "Positive Experience" in the bottom. I haven't been able to find anything on SO or anywhere else that explains how I would do that in a way I can understand.
There's nothing in my controller or anywhere else that would be helpful to show you. My controllers are literally doing nothing at the moment.
Any advice?
Edit:
here's the html for the component:
<div layout="row" layout-align="start center" flex="11">
<div layout="column" layout-align="center center" flex="100">
<div layout="row" layout-align="center center" flex="100">
1
</div>
<div layout="row" layout-align="center center" flex="100">
<div layout="column" layout-align="center center" flex="100" style="font-size: x-small">
<div layout="row" layout-align="center center" flex="100">
POSITIVE
</div>
<div layout="row" layout-align="center center" flex="100">
ATTITUDE
</div>
</div>
</div>
</div>
</div>
In my ideal world "1", "Positive", and "Attitude" would come from from attributes on the obsbox tag.
Have you seen the example at https://docs.angularjs.org/guide/component ?
You should fill your bindings like this:
bindings: {
title1:"#",
title2="#",
score="#"
}
# denotes text binding. See the link above for more binding options.
You can then use {{ $ctrl.title1 }} and the other bindings in the template.
For an example, see https://plnkr.co/edit/Gv3TofiO0QyjAOkODjPv?p=info
You can load it into scope using a link function in your directive:
.component('obsbox', {
// the other directive parameters
link: (scope, element, attributes, controller) => {
controller.title1 = attributes.title1;
controller.title2 = attributes.title2;
}
});
The link function is executed once, upon directive compilation.
If you need to update the value, you need to add a watch to that attribute. Angular provides a way to observe attributes with the $observe function:
// in the link function body
attributes.$observe('title1', newValue => {
controller.title1 = newValue;
});
Note that this way, it's using plain strings, not Angular expressions. This means that if you want to have an expression, you would need the $parse service in the directive, or use interpolation at usage site.
Related
I'm building an AngularJS app and I'm using UI-Router in order to navigate through the app.
The flow of the web app consist in first registering or login, then it takes you to a main grid that has a lot of elements that are being displayed through ng-repeat and when you click in one of them it takes you to a dashboard that displays information of the element that has been clicked and it also successfully changes the path to index.html#!/dashboard/(here goes the name of the element)
This is the html 5 code of that main grid:
<div class="container">
<div class="row" ng-controller="ribtStateServicesController" >
<div class="col-md-3 ribt_HomeGrid" ng-repeat="estado in estados" >
<div class="img-ribt_overlay" >
<a ng-click="doWork(estado.idState)" ui-sref="dashboard({ribtNameState: estado.idState})">
<img src="{{estado.image}}" class="img-thumbnail">
<div class="ribt_overlay" ></div>
</a>
</div>
</div>
</div>
</div>
However, in that same dashboard I have a side menu, that has the same elements that were used in the main grid before. So I figured that it would take the same logic to be able to use the params that the UI-Router offer in order to change the URL and the content displayed. But it does nothing.
This is the code in the HTML of the dashboard's side menu:
<div class="col-md-6 nopadding" ng-repeat="estado in estados | filter:busqueda">
<md-card md-ink-ripple="#ffffff">
<img ng-src="{{estado.nolabel}}" class="md-card-image ribt_estado_side_img"
ui-sref="dashboard({ribtNameState: estado.idState})"
alt="Estado" ng-click="doWork(estado.idState); toggleLeft()">
<md-card-title class="nopadding">
<md-card-title-text>
<p class="ribt_card_estado_nombre">{{estado.nombre}}</p>
</md-card-title-text>
<md-menu>
<md-button class="md-icon-button float-right" aria-label="Share" ng-click="$mdMenu.open()">
<i class="material-icons">more_vert</i>
</md-button>
<md-menu-content width="4" ng-mouseleave="$mdMenu.close()">
<md-menu-item ng-repeat="item in [1, 2, 3]">
<md-button>
Option {{item}}
</md-button>
</md-menu-item>
</md-menu-content>
</md-menu>
</md-card-title>
</md-card>
</div>
Heres is the code in my app config:
ribtApp.config(function ($stateProvider,$mdThemingProvider,$locationProvider) {
$stateProvider
.state('login', {
url: '',
templateUrl: 'ribtComponents/ribtLogin/ribtLogin.html',
controller: 'ribtLoginController'
})
.state('home', {
url: '/home',
templateUrl: 'ribtComponents/ribtHome/ribtHome.html',
controller: 'ribtStateServicesController'
})
.state('dashboard', {
url: '/dashboard/{ribtNameState}',
templateUrl: 'ribtComponents/ribtDashboard/ribtDashboard.html',
controller: 'ribtStateServicesController',
resolve:{
ribtNameState: ['$stateParams', function($stateParams){
return $stateParams.ribtNameState;
console.log($stateParams.ribtNameState)
}]
}
});
I tried a console log to see if the param was being changed and it is but it does nothing to the URL path. I've tried a lot of code that I've found in stackoverflow but none of them has worked. I would be deeply thankful if you could help me with this.
You need to switch your ng-hrefs to ui-srefs, which is what ui-router uses to set up links to change states. So something like...
<div class="col-md-6 nopadding" ng-repeat="estado in estados | filter:busqueda">
[...]
<a ng-click="doWork(estado.idState)" ng-href="index.html#!/dashboard/{{estado.idState}}">
should be something more like...
<div class="col-md-6 nopadding" ng-repeat="estado in estados | filter:busqueda">
[...]
<a ng-click="doWork(estado.idState)" ui-sref="dashboard({ribtNameState: estado.idOrWhatever})">
with the caveat that I don't know what doWork is actually doing, so I don't know if that is necessary.
If you need to transition in a controller, you can also inject $state and use $state.go(...), but most of the time ui-srefs can get it handled in my experience.
Is it possible to switch between the attributes show and hide by button click (programmatically)? I have for example a card with a map and a list view.
These are usually displayed side by side. On mobile devices, but is for the List view flex = 100 increases. The map is no longer displayed. The user should however have the possibility to switch between the two views. How I can do that?
My example tags:
<md-card flex-gt-xs="40" flex-xs="100">
<list></list>
</md-card>
<md-button>toggle Views</md-button>
<md-card flex="60" hide-xs show-gt-xs >
<leaflet height="40vh" ></leaflet>
</md-card>
Update:
Summary
I would like to have 2 columns that can be switched on mobile devices, and side by side on larger devices.
I'm not 100% sure what you are asking but this CodePen demonstrates the basics of toggling programatically.
md-button has an ng-click attribute that calls the function toggle() which toggles the view value. view is passed to the ng-if of each card.
Markup
<div ng-controller="AppCtrl" ng-cloak="" ng-app="MyApp" style="height:100%" layout="column">
<md-card flex-gt-xs="40" flex-xs="100" ng-if="view">
Card 1
<list></list>
</md-card>
<md-button ng-click="toggle()">toggle Views</md-button>
<md-card flex="60" hide-xs show-gt-xs ng-if="!view">
Card2
<leaflet height="40vh" ></leaflet>
</md-card>
</div>
JS
angular.module('MyApp',['ngMaterial', 'ngMessages', 'material.svgAssetsCache', 'ngDialog'])
.controller('AppCtrl', function($scope) {
$scope.view = true;
$scope.toggle = function () {
$scope.view = !$scope.view;
}
});
You can replace ng-if with ng-show if you need to retain information in elements that you are toggling as ng-if recreates the element each time it is true.
I have found the solution. $mdMedia does all the magic.
js:
$scope.$watch(function () {
return $mdMedia('sm');
}, function (big) {
$scope.screenIsSmall = $mdMedia('sm');
});
View:
<md-card flex="60" ng-hide="screenIsSmall&&!showMap" style="max-height: 40vh">
<md-button ng-show="screenIsSmall" ng-click="showMap = !showMap">toggle</md-button>
<leaflet height="40vh"></leaflet>
</md-card>
app.component.html
<div fxLayout="column" fxLayout.gt-sm="row wrap">
<div fxFlex="50" class="flex-p">
<mat-slide-toggle
[checked]="isSlideChecked"
(change)="toggleChanges($event)"
>Hide/Show - card</mat-slide-toggle
>
</div>
</div>
<mat-card *ngIf="isSlideChecked">Simple card</mat-card>
app.component.ts
isSlideChecked: boolean = false;
toggleChanges($event: MatSlideToggleChange) {
this.isSlideChecked = $event.checked;
}
Edit: Thanks to Simon Schüpbach, I was able to resolve the issue by changing the template. See the end for the solution.
Let's preface this by saying that we are beginner to soft-intermediate in Angular.
On one of our project, we are using angularjs 1.4.x and also ng-cart (https://github.com/snapjay/ngCart). It worked great but then we were confronted with a demand from our client that created new weird issues.
We added fsCounter, as a directive, to the cart page so user can add or remove items. This all work great but the users also have the option to delete an item from the cart view. Deletion works as expected BUT it seems to affect the scope to the item that takes it place.
Let me make it clearer :
Let's say we have 2 products in our cart page, it displays something like that
Product_1 {price} {counter} {total} delete_btn
Product_2 {price} {counter} {total} delete_btn
Each fsCounter is its own scope
return {
restrict: "A",
scope: {
value: "=value",
index: "=index"
},
link: //other logic
However when we delete the first item, visually and in the directives, the data seems to shift. So our second row will now inherit the first row's counter.
Directive's data looks like this:
Product_1:
itemId:3,
quantity:2,
{other data}
Product_2:
itemId:8,
quantity:5,
{other data}
But once we delete the first directive (We get the scope, remove the DOM element, destroy the scope) the second directive will now have this data:
Product_2:
itemId:3,
quantity:2,
{other data}
Here is the template code :
<div class="unItem" ng-repeat="item in ngCart.getCart().items track by $index">
<div class="photo"><img src="{[{ item.getImage() }]}" alt=""></div>
<div class="details">
<h3>{[{ item.getName() }]} <span>{[{ item.getPrice() | currency:$}]}</span></h3>
<md-select ng-model="attributes" placeholder="Attribut" class="select-attribut" ng-show="item.hasAttributes()" ng-change="item.updateSelected(attributes)">
<md-option ng-repeat="attr in item.getAttributes()" ng-selected="attr == item.getSelected()" ng-value="attr">{[{ attr }]}</md-option>
</md-select>
</div>
<div class="quantity">
<div fs-counter-dynamic value="itemQuantity"
data-min="1"
data-max="999"
data-step="1"
data-addclass="add-quantity"
data-width="130px"
data-itemid="{[{ item.getId() }]}"
data-editable
ng-model="itemQuantity"
name="quantity" id="quantity-{[{ item.getId() }]}",
index="{[{ item.getId() }]}"
></div>
</div>
<div class="total">Total : {[{ item.getTotal() | currency }]}</div>
<div class="delete"><a ng-click="ngCart.removeItemById(item.getId());"></a></div>
</div>
Is this normal behavior? Is there any way to force the directive to keeps its own data? From what I've understood, each directive has its own scope, so what I think happens is that, when we remove the first one, it keeps the data stored in some kind of array that says "directive 1 data is : " and when we delete the first directive, the second one becomes the first.
So basically, are we doing anything wrong or is there anyway to remap the data?
Hope it was clear enough,
Thanks!
Edit: added html code
Edit2: Answer :
New FsCounter template looks like this:
<div fs-counter-dynamic value="item._quantity"
data-min="1"
data-max="999"
data-step="1"
data-addclass="add-quantity"
data-width="130px"
data-itemid="{[{ item.getId() }]}"
data-editable
ng-model="item._quantity"
name="quantity" id="quantity{[{ item.getId() }]}"
></div>
Do you know ng-repeat, then you don't have such problems
<div ng-repeat="product in products">
<fs-counter index="product.index" value="product.value"></fs-counter>
</div>
and in your controller
$scope.products = [
{index:1, value:"Cola"},
{index:2,,value:"Fanta"}
]
to remove an element you just have to do
$scope.products.splice(0,1);
Edit:
I suggest to save all necessary data inside the item you use inside ng-repeat. Your problem is, that you mix data from array with other data from your $scope. It is possible to $watch changes in your directive, but if you set them with ng-repeat everything is done automatically.
$scope.products = [
{index:1, name:"Cola", price:1.50, image:"your/path.png", attributes:{...}},
{index:2, name:"Fanta", price:1.40, image:"your/path.png"}
]
And then in your html
<div class="unItem" ng-repeat="item in ngCart.products track by $index">
<div class="photo"><img ng-src="item.image" alt=""></div>
<div class="details">
<h3>{{item.name}} <span>{{item.price | currency}}</span></h3>
</div>
<div class="quantity">
<div fs-counter-dynamic value="item.quantity"
data-min="1"
data-max="999"
data-step="1"
data-addclass="add-quantity"
data-width="130px"
data-itemid="item.index"
data-editable
ng-model="item.quantity"
name="quantity" id="{{'quantity-' + $index}}",
index="item.index"
></div>
</div>
<div class="total">Total : {{ item.price * item.quantity | currency }}</div>
<div class="delete"><a ng-click="ngCart.removeItemById(item.index);"></a></div>
</div>
I'm having the following problem:
I want to use a directive at different places in an app and don't want to specify the parent object and directive object every time i use the directive.
Look at this plnkr:
http://plnkr.co/edit/yUoXXZVJmoesIQNhoDDR?p=preview
Its just a $scope.data object that stores a multilevel array.
$scope.data=
[
{"name": "LEVEL0_A", "subitems":
[
{"name":"Level1_A", "subitems":[{"name":"A"},{"name":"B"}]},
{"name":"Level1_B", "subitems":[{"name":"C"},{"name":"D"}]},
...
...
and so on
and there is a little sample custom directive, called deleteItem, that does exactly that.
.directive('deleteItem', function() {
return {
scope:{
item:'=',
parent:'='
},
template: '<ng-transclude></ng-transclude>Delete',
transclude:true,
controller: function($scope){
$scope.deleteItem=function(currentItem,currentParent){
currentParent.subitems.splice(currentParent.subitems.indexOf(currentItem),1);
};
}
};
});
here you see the html template
<body ng-app="myApp">
<div ng-controller="myController">
<div ng-repeat="level0 in data">
<h2>{{level0.name}}</h2>
<div ng-repeat="level1 in level0.subitems">
<div delete-item parent="level0" item="level1">
{{level1.name}}
</div>
--------------------
<div ng-repeat="level2 in level1.subitems">
<div delete-item parent="level1" item="level2">
Name: {{level2.name}}
</div>
</div>
<hr>
</div>
</div>
</div>
</body>
I mean it works, but actually i feel that there must be some way finding the item and parent without specifically linking them to the scope manually.
I'd be really glad if someone could point me in the right direction.
Thanks
Markus
If you do something like this.
$scope.deleteItem=function(currentItem,currentParent){
currentParent.subitems.splice(currentParent.subitems.indexOf(currentItem),1);
};
Then your directive becomes dependent upon the structure of data outside it's scope. That means that the directive can only delete items if it follows exactly that pattern. What if you want to use the delete button on data that isn't from an array?
The better approach is to use the API feature & to execute an expression on the outer scope.
app.directive('deleteItem', function () {
return {
scope: {
remove: '&deleteItem'
},
template: '<ng-transclude></ng-transclude><a ng-click="remove()">Delete</a>',
transclude: true
};
});
When the user clicks "Delete" the remove() API is called and the template handles how that item is removed.
<div ng-repeat="level0 in data">
<h2>{{level0.name}}</h2>
<div ng-repeat="level1 in level0.subitems">
<div delete-item="level0.splice($index,1)">
{{level1.name}}
</div>
--------------------
<div ng-repeat="level2 in level1.subitems">
<div delete-item="level1.splice($index,1)">
Name: {{level2.name}}
</div>
</div>
<hr>
</div>
</div>
I am using directives to try to replace some of the often-reoccurring template code that i must write with something simpler.
lets say I have the following original markup:
<!-- section with repeating stuff in it -->
<div some-attributes etc="etc" very-long-tag="true">
<p class="lead">Some description text</p>
<div class="row section short" ng-repeat="row in things">
<div class="col-sm-6 col-md-4" ng-repeat="app in row.col">
<div class="thumbnail">
<img ng-src="im/things/{{app.image}}" alt="..." class="img-circle" width="250">
<div class="caption">
<h3>{{app.name}}</h3>
<p>{{app.desc}}</p>
</div>
</div>
</div>
</div>
</div>
and I want to simplify it by doing something like this:
<!-- section with repeating stuff in it -->
<xx title="Some description text">
<!-- this innerHTML gets passed to the directive -->
<div class="row section short" ng-repeat="row in things">
<div class="col-sm-6 col-md-4" ng-repeat="app in row.col">
<div class="thumbnail">
<img ng-src="im/things/{{app.image}}" alt="..." class="img-circle" width="250">
<div class="caption">
<h3>{{app.name}}</h3>
<p>{{app.desc}}</p>
</div>
</div>
</div>
</div>
<!-- end of innerHTML -->
</xx>
...where there are a several attributes that can be used to shorten the overall block, the directive is currently written this way:
_d.directive('xx', function() {
return {
scope: {
'color': '=',
'option': '=',
'title': '=',
'image': '=',
'image-pos': '=',
'image-size': '='
},
restrict: 'E',
transclude: false,
template: function(element, scope) {
var inside = 'x';
var content = element[0].innerHTML;
var title = scope.title;
var color = scope.color ? 'style="background-color: '+scope.color+'"' : "";
var title = scope.title ? '<h2 class="centertext marginBottom20">'+scope.title+'</h2>' : '';
return ['<div class="section row short" '+color+' ng-transclude>',
title,
content, //this may contain {{template code}}, but it always gets omitted
'</div>'
].join("\n");
},
};
});
The problem is that the existing HTML always gets omitted if it contains any {{angular template code}}.
How do I write the directive so that it still honors the template code?
Ive successfully fixed the issue with the directive, but it took several steps.
Use the correct scope properties. instead of using '=', I used '#'
That was based on the following link: What is the difference between '#' and '=' in directive scope in AngularJS?
The thing to note about scope isolation using #, =, and & affects the way you must refer to the variable in the template. for example, using = means that I would refer the variable without brackets while using # would refer to the variable with {{brackets}}.
Like I mentioned in the first point, after adjusting the scope properties, i needed to go back and refer to the variables in the correct way depending on how the scope was defined.
ng-transclude when used with {...transclude: true,...} requires that I actually put a container somewhere in the template for that transcluded content. Here's an example of that:
return ['<div class="section row short" '+color+' ng-transclude>',
title,
'<div ng-transclude>', //this is the container for the original innerHTML, transcluded
content, //this may contain {{template code}}, and gets transcluded
'</div>
'</div>'
].join("\n");
Only then did the directive work as expected. Also, props to #rob for providing me with this introductory link, https://egghead.io/lessons/angularjs-components-and-containers.