1

I am new in AngularJS. I have searched some pages about how to manipulate DOM in directive, but most of them are manipulating the DOM of directive itself. My problem is manipulate the DOM outside of the directive.

There is a Google Map in my project, and there are some markers on the map to show events. What I want to do is that when clicking the marker, the application should show the detailed information of the event in a slide bar. Based on the code below, the requirement is when I click the marker which triggers updateMarkers() in directive, it should change the value of {{detail}} in HTML. What I am doing now is trying to use "=" to give directive access to the values in its controller scope, but this way does not work. So please offer some help if you know, thank you.

HTML:

<div id="wrapper" >
    <div class="container-fluid">
        <div class="row">
            <div class="col-lg-12">                  
                <app-map id="map" center="loc" markers="cities" details="details" show-detail="getDetails(directivedata)" on-init="gotoCurrentLocation(directiveData)"> </app-map>                  
            </div>
            <div>**********{{details}}</div>
        </div>
    </div>
</div>

Part of controller:

app.controller("appCtrl", function ($scope, $http) {
    $scope.nearbyEvent;
    $scope.details = "something";   //should be changed when click markers in map
    //code.....}

Directive:

app.directive("appMap", ['$http', function ($http) {
    return {
        restrict: "E",
        replace: true,
        template: "<div></div>",
        scope: {
            center: "=",        // Center point on the map (e.g. <code>{ latitude: 10, longitude: 10 }</code>).
            markers: "=",       // Array of map markers (e.g. <code>[{ lat: 10, lon: 10, name: "hello" }]</code>).
            details: "=",       //used for resend a REST api and store data in here
            width: "@",         // Map width in pixels.
            height: "@",        // Map height in pixels.
            zoom: "@",          // Zoom level (one is totally zoomed out, 25 is very much zoomed in).
            mapTypeId: "@",     // Type of tile to show on the map (roadmap, satellite, hybrid, terrain).
            panControl: "@",    // Whether to show a pan control on the map.
            zoomControl: "@",   // Whether to show a zoom control on the map.
            scaleControl: "@",   // Whether to show scale control on the map.
            onInit: "&",
            showDetail: "&"
        },
        link: function (scope, element, attrs) {
        //some code.....
             function updateMarkers() {
                if (map && scope.markers.length > 0) {
                    window.alert("get me");
                    // clear old markers
                    if (currentMarkers != null) {
                        for (var i = 0; i < currentMarkers.length; i++) {

                            currentMarkers.setMap(null);
                            window.alert("get here1*****");
                        }
                    }                          
                    // create new markers
                    currentMarkers = [];
                    var markers = scope.markers; 

                    function makeHappen(thi){
                        return function(){
                            scope.details = thi;
                            window.alert("***" + "in directive    " + thi); 
                        }
                    }

                    for (var i = 1; i < markers.length-1; i++) {                    
                        var m = markers[i];
                        if(m.venue == null || m.venue.lat == 0.0)
                            continue;
                        var loc = new google.maps.LatLng(m.venue.lat, m.venue.lon);
                        var eventName = m.description;  
                        var mm = new google.maps.Marker({ position: loc, map: map, title: m.name, id: m.id, count: i});                                                 
                        mm.addListener('click', makeHappen(eventName), false);                     
                        currentMarkers.push(mm);

                    }
                }
            }
tytan
  • 13
  • 5
  • Is it detail or details? I can see both of them are used. – Saad May 06 '16 at 23:31
  • details, sorry. I don't know why the edit here is wrong. But in my code it is all called "details" in both HTML and JS part. I have already changed it here. Thanks – tytan May 07 '16 at 02:15
  • To change the value of the parent scope variable defined by the `details` attribute, simply set it with `scope.details = 'new value';`. – georgeawg May 07 '16 at 06:12

2 Answers2

1

I think this is a similar problem to the one reported here: How to access parent scope from within a custom directive *with own scope* in AngularJS?

Hope it helps!

Community
  • 1
  • 1
John Lee
  • 1,357
  • 1
  • 13
  • 26
  • Thanks for reply, the link you gave me is about using properties in controller at directive, but what I want to do is changing the value of properties in controller at directive. Is there any other links you might know, thank you – tytan May 06 '16 at 22:45
  • I actually tried the methods in the link before. Although I can access controller's properties in directive, I cannot change its value. It is just like I did not $apply() it. – tytan May 06 '16 at 22:47
1

Use . notation to properly enable two way data binding. details is a primitive type in your controller. Though the isolate scope in directive can access details, when you assign something to details in the makeHappen function, it creates a new details in the isolate scope.

In this case, instead of a primitive type $scope.details in your controller, make it an object e.g: $scope.details = {msg: 'something' }. Print it in template i.e: {{details.msg}}. Change details to details.msg in your makeHappen function of your isolate scope.

Update

Sorry for my mistake. Looks like, you don't need to change scope.details from string to object. The below changes would have been enough for it to work.

function makeHappen(thi){
    return function(){
        scope.$apply(function() {
            scope.details = thi;  
        })
    }
}

For isolate scope, whenever a shared variable(controller scope var shared with directive scope) is changed in the directive scope, the variable in controller also gets updated. It doesn't matter whether it is primitive or object because there is no prototypical inheritance between the directive scope and controller scope. However, if the directive's scope property was true(scope: true) rather than isolate scope, then scope.details had to be an object to see the changed value in the controller scope(that I explained earlier).

The only change that's required is to wrap the scope.details = thi inside the scope.$apply(function(){//code}) or to call scope.$apply() at the end of the makeHappen return function.

So, the question is, why do you need to call scope.$apply() or put certain code inside scope.$apply(function() {//code})? The reason is:

$apply() is used to execute an expression in angular from outside of the angular framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).

In your case, you attached an event listener mm.addListener('click', makeHappen(eventName), false);. The makeHappen function gets called whenever marker is clicked. It happens outside angular context. Angular doesn't know that scope.details value is changed. So, the watcher for scope.details doesn't get called and hence, you don't see any update. And, here comes the solution scope.$apply(). Whenever you call scope.$apply(), it internally calls scope.$digest() which actually update any bindings or watchers.

So, when do you need to call scope.$apply()? Behind the scene, Angular wraps almost all of the code within $apply() that you write in context of angular. Event like ng-click or $timeout or $http callbacks are wrapped inside scope.$apply() so that you don't need to call it explicitly. You will get error if you call $apply yourself.

For more info on $apply, you can visit this links: https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply http://jimhoskins.com/2012/12/17/angularjs-and-apply.html

I have made a plunk for you to better understand the different scope scenarios in directive and why $apply is needed in some cases. Please, have a look at this plunk. http://plnkr.co/edit/96a646T6FuBhmjdLgJXq?p=preview

Saad
  • 942
  • 6
  • 15
  • Hi, thanks for your help. Making it as an object works. Just another word, I added scope.$apply() after changing details.msg in makeHappen function.. Anyway, thanks for your help, looking forward to more info/link. – tytan May 07 '16 at 22:34