简体   繁体   中英

AngularJS manipulate parent's DOM in directive

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);

                    }
                }
            }

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!

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.

\n

In this case, instead of a primitive type $scope.details in your controller, make it an object eg: $scope.details = {msg: 'something' } . Print it in template ie: {{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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM